8. Scrapy爬虫框架案例实战
任务:爬取腾讯网中关于指定条件的所有
社会招聘
信息,搜索条件为北京
地区,Python
关键字的就业岗位,并将信息存储到MySql数据库中。网址:https://hr.tencent.com/position.php?keywords=python&lid=2156
实现思路:首先爬取每页的招聘信息列表,再爬取对应的招聘详情信息
① 创建项目
- 在命令行编写下面命令,创建项目tencent
scrapy startproject tencent
- 项目目录结构:
tencent
├── tencent
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py # Items的定义,定义抓取的数据结构
│ ├── middlewares.py # 定义Spider和DownLoader的Middlewares中间件实现。
│ ├── pipelines.py # 它定义Item Pipeline的实现,即定义数据管道
│ ├── settings.py # 它定义项目的全局配置
│ └── spiders # 其中包含一个个Spider的实现,每个Spider都有一个文件
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg #Scrapy部署时的配置文件,定义了配置文件路径、部署相关信息等内容
② 进入tencent项目目录,创建爬虫spider类文件(hr招聘信息)
- 执行genspider命令,第一个参数是Spider的名称,第二个参数是网站域名。
scrapy genspider hr hr.tencent.com
$ tree
├── tencent
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-36.pyc
│ │ └── settings.cpython-36.pyc
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ ├── __pycache__
│ │ └── __init__.cpython-36.pyc
│ └── hr.py #在spiders目录下有了一个爬虫类文件hr.py
└── scrapy.cfg
# hr.py的文件代码如下:
# -*- coding: utf-8 -*-
import scrapy
class HrSpider(scrapy.Spider):
name = 'hr'
allowed_domains = ['hr.tencent.com']
start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156']
def parse(self, response):
#解析当前招聘列表信息的url地址:
detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract()
#遍历url地址
for url in detail_urls:
#fullurl = 'http://hr.tencent.com/' + url
#构建绝对的url地址,效果同上(域名加相对地址)
fullurl = response.urljoin(url)
print(fullurl)
- 测试一下获取第一页的招聘详情url地址信息
③ 创建Item
Item是保存爬取数据的容器,它的使用方法和字典类型,但相比字典多了些保护机制。
创建Item需要继承scrapy.Item类,并且定义类型为scrapy.Field的字段:
- 职位id号,名称、位置、类别、要求、人数、工作职责、工作要求
具体代码如下:(创建一个类名为HrItem)
import scrapy
class TencentItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class HrItem(scrapy.Item):
'''
人事招聘信息封装类
(职位id号,名称、位置、类别、要求、人数、职责和要求)
'''
table = "hr" #表名
id = scrapy.Field()
title = scrapy.Field()
location = scrapy.Field()
type = scrapy.Field()
number = scrapy.Field()
duty = scrapy.Field()
requirement = scrapy.Field()
④ 解析Response
在hr.py文件中,parse()方法的参数response是start_urls里面的链接爬取后的结果。
提取的方式可以是CSS选择器、XPath选择器或者是re正则表达式。
# -*- coding: utf-8 -*-
import scrapy
from tencent.items import HrItem
class HrSpider(scrapy.Spider):
name = 'hr'
allowed_domains = ['hr.tencent.com']
start_urls = ['https://hr.tencent.com/position.php?keywords=python&lid=2156']
def parse(self, response):
#解析当前招聘列表信息的url地址:
detail_urls = response.css('tr.even a::attr(href),tr.odd a::attr(href)').extract()
#遍历url地址
for url in detail_urls:
#fullurl = 'http://hr.tencent.com/' + url
#构建绝对的url地址,效果同上(域名加相对地址)
fullurl = response.urljoin(url)
#print(fullurl)
# 构造请求准备爬取招聘详情信息,并指定由parse_page()方法解析回调函数
yield scrapy.Request(url=fullurl,callback=self.parse_page)
#获取下一页的url地址
next_url = response.css("#next::attr(href)").extract_first()
#判断若不是最后一页
if next_url != "javascript:;":
url = response.urljoin(next_url)
#构造下一页招聘列表信息的爬取
yield scrapy.Request(url=url,callback=self.parse)
# 解析详情页
def parse_page(self,response):
#构造招聘信息的Item容器对象
item = HrItem()
# 解析id号信息,并封装到Item中
item["id"] = response.selector.re_first('onclick="applyPosition\(([0-9]+)\);"')
#标题
item["title"] = response.css('#sharetitle::text').extract_first()
#位置
item["location"] = response.selector.re_first('<span class="lightblue l2">工作地点:</span>(.*?)</td>')
#类别
item["type"] = response.selector.re_first('<span class="lightblue">职位类别:</span>(.*?)</td>')
#人数
item["number"] = response.selector.re_first('<span class="lightblue">招聘人数:</span>([0-9]+)人</td>')
#工作职责
duty = response.xpath('//table//tr[3]//li/text()').extract()
item["duty"] = ''.join(duty)
#工作要求
requirement = response.xpath('//table//tr[4]//li/text()').extract()
item["requirement"] = ''.join(requirement)
#print(item)
#交给管道文件
yield item
⑤、创建数据库和表:
- 在mysql中创建数据库
mydb
和数据表hr
CREATE TABLE `hr` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`location` varchar(32) DEFAULT NULL,
`type` varchar(32) DEFAULT NULL,
`number` varchar(32) DEFAULT NULL,
`duty` text DEFAULT NULL,
`requirement` text DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
⑥、使用Item Pipeline
- 在Item管道文件中,定义一个MysqlPipeline,负责连接数据库并执行信息写入操作
import pymysql
class TencentPipeline(object):
def process_item(self, item, spider):
return item
class MysqlPipeline(object):
def __init__(self,host,user,password,database,port):
self.host = host
self.user = user
self.password = password
self.database = database
self.port = port
@classmethod
def from_crawler(cls,crawler):
return cls(
host = crawler.settings.get("MYSQL_HOST"),
user = crawler.settings.get("MYSQL_USER"),
password = crawler.settings.get("MYSQL_PASS"),
database = crawler.settings.get("MYSQL_DATABASE"),
port = crawler.settings.get("MYSQL_PORT"),
)
def open_spider(self, spider):
'''负责连接数据库'''
self.db = pymysql.connect(self.host,self.user,self.password,self.database,charset="utf8",port=self.port)
self.cursor = self.db.cursor()
def process_item(self, item, spider):
'''执行数据表的写入操作'''
#组装sql语句
data = dict(item)
keys = ','.join(data.keys())
values=','.join(['%s']*len(data))
sql = "insert into %s(%s) values(%s)"%(item.table,keys,values)
#指定参数,并执行sql添加
self.cursor.execute(sql,tuple(data.values()))
#事务提交
self.db.commit()
return item
def close_spider(self, spider):
'''关闭连接数据库'''
self.db.close()
⑦ 修改配置文件
打开配置文件:settings.py 开启并配置ITEM_PIPELINES信息,配置数据库连接信息
当有CONCURRENT_REQUESTS,没有DOWNLOAD_DELAY 时,服务器会在同一时间收到大量的请求。
- 当有CONCURRENT_REQUESTS,有DOWNLOAD_DELAY 时,服务器不会在同一时间收到大量的请求。
# 忽略爬虫协议
ROBOTSTXT_OBEY = False
# 并发量
CONCURRENT_REQUESTS = 1
#下载延迟
DOWNLOAD_DELAY = 0
ITEM_PIPELINES = {
#'educsdn.pipelines.EducsdnPipeline': 300,
'educsdn.pipelines.MysqlPipeline': 301,
}
MYSQL_HOST = 'localhost'
MYSQL_DATABASE = 'mydb'
MYSQL_USER = 'root'
MYSQL_PASS = ''
MYSQL_PORT = 3306
⑧、运行爬取:
- 执行如下命令来启用数据爬取
scrapy crawl hr