1、 翻页请求的思路
(1)request请求的思路
找到下一页的URl——调用request.get(url)
(2)scrapy实现翻页
找到下一页的URL——构造URL地址的请求对象——传递给引擎
2、构造request对象并发送请求
(1)实现步骤
①确定url地址
②构造请求,scrapy.Request(url,callback)
③把请求交给引擎:yield scrapy.Request(url,callback)
callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析
(2)案例实现:网易招聘爬虫(爬取网易招聘的页面的招聘信息)
地址:https://hr.163.com/position/list.do
①创建项目:scrapy startproject wangyi
②数据建模:items.py
import scrapy
class WangyiItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field() # 职位名称
link = scrapy.Field() # 职位详情
depart = scrapy.Field() # 所属部门
category = scrapy.Field() # 职位类别
type = scrapy.Field() # 工作类型
address = scrapy.Field() # 工作地点
num = scrapy.Field() # 招聘人数
date = scrapy.Field() # 发布时间
③完成爬虫
创建爬虫项目:
cd wangyi
scrapy genspider job 163.com
编辑爬虫
import scrapy
# 把建模的数据导入进来
from wangyi.items import WangyiItem
class JobSpider(scrapy.Spider):
name = 'job'
# 2. 检查域名
allowed_domains = ['163.com']
# 1. 修改起始url
start_urls = ['https://hr.163.com/position/list.do']
# 3. 在parse方法中实现爬取逻辑
def parse(self, response):
# 1.提取数据
# ①提取所有职位节点列表
node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')
# ②遍历职位节点列表
for num,node in enumerate(node_list):
# 设置过滤,将目标节点获取出来
if num % 2 == 0:
item = WangyiItem()
item['name'] = node.xpath('./td[1]/a/text()').extract_first()
# response.urljoin()用于拼接相对路径的URL
item['link'] = response.urljoin(node.xpath('./td[1]/a/@href').extract_first())
item['depart'] = node.xpath('./td[2]/text()').extract_first()
item['category'] = node.xpath('./td[3]/text()').extract_first()
item['type'] = node.xpath('./td[4]/text()').extract_first()
item['address'] = node.xpath('./td[5]/text()').extract_first()
# strip() 消除空格
item['num'] = node.xpath('./td[6]/text()').extract_first().strip()
item['date'] = node.xpath('./td[7]/text()').extract_first()
yield item
# 2.模拟翻页
part_url = response.xpath('/html/body/div[2]/div[2]/div[2]/div/a[last()]/@href').extract_first()
# 判断终止条件
if part_url !='javascript:void(0)':
next_url = response.urljoin(part_url)
# 构造请求对象,并且返回给引擎
yield scrapy.Request(url=next_url,callback=self.parse)
④保存数据
./pipelines
import json
class WangyiPipeline:
def __init__(self):
self.file = open('Amen.json','w')
def process_item(self, item, spider):
item = dict(item)
# 将字典数据序列化成字符串
str_data = json.dumps(item,ensure_ascii=False)+',\n'
# 将数据写入文件
self.file.write(str_data)
return item
def __del__(self):
self.file.close()
./setting
ITEM_PIPELINES = {
'wangyi.pipelines.WangyiPipeline': 300,
}
⑤运行程序:scrapy crawl job
注意:
- 可以在settings中设置ROBOTS协议
# False表示忽略网站的robots.txt协议,默认为True
ROBOTSTXT_OBEY = False
- 可以在settings中设置User-Agent:
# scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'
3、scrapy.Request()的其他参数
scrapy.Request(url[,callback,method="GET",headers,body,cookies,meta,dont_filter=False])
参数解释
- 中括号里的参数为可选参数
- callback:表示当前的url的响应交给哪个函数去处理
- meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,比如下载延迟,请求深度等
- dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动
- method:指定POST或GET请求
- headers:接收一个字典,其中不包括cookies
- cookies:接收一个字典,专门放置cookies
- body:接收json字符串,为POST的数据,发送payload_post请求时使用
- meta参数的使用
meta的作用:meta可以实现数据在不同的解析函数中的传递
在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:
def parse(self,response):
...
yield scrapy.Request(detail_url, callback=self.parse_detail,meta={"item":item})
...
def parse_detail(self,response):
#获取之前传入的item
item = resposne.meta["item"]
- meta参数是一个字典
- meta字典中有一个固定的键
proxy
,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件的学习中进行介绍
案例解析:对前面的网易招聘进行完善详情页面的爬取拼接数据
①建模:
class WangyiItem(scrapy.Item):
……
duty = scrapy.Field() # 岗位描述
require = scrapy.Field() # 岗位要求
②完善爬取
class JobSpider(scrapy.Spider):
def parse(self, response):
……
for num,node in enumerate(node_list):
# 设置过滤,将目标节点获取出来
if num % 2 == 0:
……
# yield item
# 构建详情页面的请求
yield scrapy.Request(url=item['link'],callback=self.parse_detail,meta={'item':item})
……
def parse_detail(self):
item = response.meta['item']
item['duty'] = response.xpath('/html/body/div[2]/div[2]/div[1]/div/div/div[2]/div[1]/div/text()').extract()
item['require'] = response.xpath('/html/body/div[2]/div[2]/div[1]/div/div/div[2]/div[2]/div/text()').extract()
yield item
4、管道的使用
(1)管道中常用的方法
①process_item(self,item,spider):
- 管道类中必须有的函数
- 实现对item数据的处理
- 必须return item
②open_spider(self, spider): 在爬虫开启的时候仅执行一次
③close_spider(self, spider): 在爬虫关闭的时候仅执行一次
(2)三个方法中的spider参数的作用:用于判断执行哪个爬虫执行哪个管道
(3)案例分析:网易招聘
①重新建一个数据模
class WangyiItem2(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field() # 职位名称
link = scrapy.Field() # 职位详情
depart = scrapy.Field() # 所属部门
category = scrapy.Field() # 职位类别
type = scrapy.Field() # 工作类型
address = scrapy.Field() # 工作地点
num = scrapy.Field() # 招聘人数
date = scrapy.Field() # 发布时间
②新建一个网易爬虫job2,没有详情页面的情况:
import scrapy
# 导入新建的数据模
from wangyi.items import WangyiItem2
class Job2Spider(scrapy.Spider):
name = 'job2'
# 2. 检查域名
allowed_domains = ['163.com']
# 1. 修改起始url
start_urls = ['https://hr.163.com/position/list.do']
# 3. 在parse方法中实现爬取逻辑
def parse(self, response):
# 1.提取数据
# ①提取所有职位节点列表
node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')
# ②遍历职位节点列表
for num, node in enumerate(node_list):
# 设置过滤,将目标节点获取出来
if num % 2 == 0:
item = WangyiItem2()
item['name'] = node.xpath('./td[1]/a/text()').extract_first()
# response.urljoin()用于拼接相对路径的URL
item['link'] = response.urljoin(node.xpath('./td[1]/a/@href').extract_first())
item['depart'] = node.xpath('./td[2]/text()').extract_first()
item['category'] = node.xpath('./td[3]/text()').extract_first()
item['type'] = node.xpath('./td[4]/text()').extract_first()
item['address'] = node.xpath('./td[5]/text()').extract_first()
# strip() 消除空格
item['num'] = node.xpath('./td[6]/text()').extract_first().strip()
item['date'] = node.xpath('./td[7]/text()').extract_first()
yield item
# 2.模拟翻页
part_url = response.xpath('/html/body/div[2]/div[2]/div[2]/div/a[last()]/@href').extract_first()
# 判断终止条件
if part_url != 'javascript:void(0)':
next_url = response.urljoin(part_url)
# 构造请求对象,并且返回给引擎
yield scrapy.Request(url=next_url, callback=self.parse)
③现在存在两个爬虫,但是管道保存的通道只有一个,进行判断管道保存数据
class WangyiPipeline:
def open_spider(self,spider):
if spider.name == 'job':
self.file = open('Amen.json','w')
def process_item(self, item, spider):
if spider.name == 'job':
item = dict(item)
# 将字典数据序列化成字符串
str_data = json.dumps(item,ensure_ascii=False)+',\n'
# 将数据写入文件
self.file.write(str_data)
return item
def close_spider(self,spider):
if spider.name == 'job':
self.file.close()
class Wangyi2Pipeline:
def open_spider(self,spider):
if spider.name == 'job2':
self.file = open('Amen2.json','w')
def process_item(self, item, spider):
if spider.name == 'job2':
item = dict(item)
# 将字典数据序列化成字符串
str_data = json.dumps(item,ensure_ascii=False)+',\n'
# 将数据写入文件
self.file.write(str_data)
return item
def close_spider(self,spider):
if spider.name == 'job2':
self.file.close()
④在setting中开启管道
ITEM_PIPELINES = {
'wangyi.pipelines.WangyiPipeline': 300,
'wangyi.pipelines.Wangyi2Pipeline': 301,
}
(4)管道数据保存到数据库
①创建一个数据保存管道,连接上MongoDB数据库
class MongoPipeline:
def open_spider(self,spider):
self.client = MongoClient('127.0.0.1', 27017)
self.db = self.client['wangyidata']
self.col = self.db['wangyi']
def process_item(self, item, spider):
data = dict(item)
self.col.insert(data)
return item
def close_spider(self,spider):
self.client.close()
②开启管道
ITEM_PIPELINES = {
……
'wangyi.pipelines.MongoPipeline': 302,
}