scrapy数据建模与请求
学习目标:
- 应用 在scrapy项目中进行建模
- 应用 构造Request对象,并发送请求
- 应用 利用meta参数在不同的解析函数中传递数据
- scrapy构造post请求
1. 数据建模
通常在做项目的过程中,在items.py中进行数据建模
1.1 为什么建模
- 定义item即提前规划好哪些字段需要抓,防止手误,因为定义好之后,在运行过程中,系统会自动检查
- 配合注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替
1.2 如何建模
在items.py文件中定义要提取的字段:
# Define here the models for your scraped items
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class DoubanItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field() # 名字
content = scrapy.Field() # 内容
link = scrapy.Field() # 链接
txt = scrapy.Field() #详情介绍
1.3 如何使用模板类
模板类定义以后需要在爬虫中导入并且实例化,之后的使用方法和使用字典相同
爬虫文件.py
# -*- coding:utf-8 -*-
import scrapy
from douban.items import DoubanItem
....
def parse(self, response):
name = response.xpath('//h2[@class="clearfix"]/a/text()').extract()
content = response.xpath('//p[@class="subject-abstract color-gray"]/text()').extract()
link = response.xpath('//h2[@class="clearfix"]/a/@href').extract()
for names, contents, links in zip(name, content, link):
item = DoubanItem() # 实例化后拿到模板类就可直接使用 本质是一个字典
item['name'] = names
item['content'] = contents.strip()
item['link'] = links
注意:
1. from douban.items import DoubanItem这一行代码中 注意item的正确导入路径,忽略pycharm标记的错误
2. python中的导入路径要诀:从哪里开始运行,就从哪里开始导入
1.4 开发流程总结
1. 创建项目
scrapy startproject 项目名
2. 明确目标
在items.py文件中进行建模 (一般来说在开发流程里建模是必须的,但如果字段特别少也可以选择忽略)
3. 创建爬虫
3.1 创建爬虫
scrapy genspider 爬虫名 允许的域
3.2 完成爬虫
修改start_urls
检查修改allowed_domains
在parse方法里编写解析方法
4. 保存数据
在pipelines.py文件中定义对数据处理的管道
在settings.py文件中注册启用管道
2. 翻页请求的思路
- 找到下一页的url地址
- 把url地址构造成请求对象,传递给引擎
3. 构造Request对象,并发送请求
3.1 实现方法
- 确定url地址
- 构造请求,scrapy.Request(url,callback)
- callback:指定响应体解析的函数名称,表示该请求返回的响应使用哪一个函数进行解析(callback不赋值的话默认是给parse方法解析)
- 把请求交给引擎:yield scrapy.Request(url,callback)
3.2 豆瓣新书速递爬虫
通过爬取豆瓣新书速递的页面信息,学习如何实现翻页请求
地址:豆瓣新书速递
思路分析:
- 获取首页的响应数据(因为里面有我们想要的翻页链接)
- 寻找下一页的地址,进行翻页,获取数据
注意:
-
可以在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.3 代码实现
在爬虫文件的parse方法中:
....
# 1,构造翻页
# 提取下一页url
part_url = response.xpath('//span[@class="next"]//a/@href').extract_first()
# 2,判断是否为下一页的条件
if '?subcat=' in part_url:
# 构造完整的url
next_url = response.urljoin(part_url)
print("下一页参数信息:", part_url)
print("下一页链接:", next_url)
# 构造scrapy.Request对象,并yield给引擎,利用callback参数指定该Request对象之后获取的响应用哪个函数进行解析
yield scrapy.Request(url=next_url, callback=self.parse)
3.4 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请求时使用(在下面内容中会介绍post请求)
4. meta参数的使用
meta的作用:meta可以实现数据在不同的解析函数中的传递
使用场景:常用在数据分散在不同结构页面中
在爬虫文件的parse方法中,提取详情页增加之前callback指定的parse_detail函数:
# 爬虫默认自带的解析方法
def parse(self,response):
yield scrapy.Request(url=item['link'],callback=self.parse_detail, meta={'item': item})
# 新建一个解析方法 用于解析详情页 里面一定要有resposne参数
def parse_detail(self,response):
# 获取meta传递过来的参数给item字典接收
item = resposne.meta["item"]
特别注意
- meta参数是一个字典
- meta字典中有一个固定的键
proxy
,表示代理ip,关于代理ip的使用我们将在scrapy的下载中间件进行介绍
scrapy中间件的使用
学习目标:
- 应用 scrapy中使用间件使用随机UA的方法
- 应用 scrapy中使用代理ip的的方法
1.1 scrapy中间件的分类
根据scrapy运行流程中所在位置不同分为:
- 下载中间件
- 爬虫中间件
scrapy默认情况下,两中中间件都是在middlewares.py一个文件中,爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件
1.2 scrapy中间件的作用:预处理request和response对象
- 对header以及cookie进行更换和处理
- 使用代理ip等
- 对请求进行定制化操作
2. 下载中间件的使用方法:
接下来我们对豆瓣爬虫进行修改完善,通过下载中间件来学习如何使用中间件
编写一个Downloader Middlewares和我们编写一个pipeline一样,定义一个类,然后在setting中开启
Downloader Middlewares默认的方法:
-
process_request(self, request, spider):
- 当每个request通过下载中间件时,该方法被调用。
2. 返回None值:没有return也是返回None,该request对象传递给下载器,或通过引擎传递给其他权重低的process_request方法
3. 返回Response对象:不再请求,把response返回给引擎
4. 返回Request对象:把request对象通过引擎交给调度器,此时将不通过其他权重低的process_request方法
- 当每个request通过下载中间件时,该方法被调用。
-
process_response(self, request, response, spider):
- 当下载器完成http请求,传递响应给引擎的时候调用
2. 返回Resposne:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的process_response方法
3. 返回Request对象:通过引擎交给调取器继续请求,此时将不通过其他权重低的process_request方法
- 当下载器完成http请求,传递响应给引擎的时候调用
-
在settings.py中配置开启中间件,权重值越小越优先执行
3. 定义实现随机User-Agent的下载中间件
3.1 在middlewares.py中完善代码 middlewares.py中自带的代码可以删除掉
import random
class UserAgentDownloadMiddleware(object):
user_agent = [
'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:77.0) Gecko/20190101 Firefox/77.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
'Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16.2'
]
# 方法名是scrapy规定的方法 (协商机制)
# 每个交给下载器的request对象都会经过该方法,并期望返回response
def process_request(self, request, spider):
# 获取随机请求头
u_a = random.choice(self.user_agent)
# 设置请求头
request.headers['User-Agent'] = u_a
3.2 在settings中设置开启自定义的下载中间件,设置方法同管道
settings文件所写参数的详细说明可参考以下博客:
https://blog.csdn.net/Lan_cer/article/details/87554025
DOWNLOADER_MIDDLEWARES = {
'douban.middlewares.UserAgentDownloadMiddleware': 200,
}
4. 代理ip的使用
4.1 思路分析
- 代理添加的位置:request.meta中增加
proxy
字段 - 获取一个代理ip,赋值给
request.meta['proxy']
- 代理池中随机选择代理ip
- 像代理ip的api发送请求获取一个代理ip
4.2 具体实现
class RandomProxy(object):
ip_list = [
'116.26.39.23:4215',
'42.56.239.136:4278',
'115.234.192.226:4275',
]
def process_request(self, request, spider):
proxy = random.choice(self.ip_list)
# 需要加上https://,否则报错
# 修改请求的元数据字典 用于给框架中其他组件传递信息 比如给其添加一个代理
request.meta['proxy'] = 'https://' + proxy
同理要在settings.py中开启该中间件
DOWNLOADER_MIDDLEWARES = {
'douban.middlewares.RandomProxy': 100,
'douban.middlewares.UserAgentDownloadMiddleware': 200,
}
scrapy管道的使用
学习目标:
1. 掌握 scrapy管道(pipelines.py)的使用
之前我们在scrapy入门使用一节中学习了管道的基本使用,接下来我们深入的学习scrapy管道的使用
process_item(self,item,spider):
- 管道类中必须有的函数
- item指引擎传过来的数据 实现对item数据的处理
- 必须return item
- spide指的是使用这个管道的爬虫
2. 管道文件的修改
继续完善豆瓣爬虫,在pipelines.py代码中完善
# -*- coding:utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
import json
import logging
from itemadapter import ItemAdapter
import pymysql
class DoubanPipeline:
def __init__(self):
self.file = open('douban.json', 'a', encoding='utf-8')
def process_item(self, item, spider):
data = dict(item)
json_data = json.dumps(data, ensure_ascii=False) + ',\n'
self.file.write(json_data)
# 不return的情况下,另一个权重较低的pipeline将不会获得item
return item
# 整个程序生命周期结束 内存销毁 该方法才会执行结束
def __del__(self):
self.file.close()
class DoubansqlPipeline:
def __init__(self):
# 连接数据库 用户名 密码 数据库名 编码
self.db = pymysql.connect(user='root', password='admin', database='xiaoxiao', charset='utf8')
self.cursor = self.db.cursor() # 获取操作游标
def process_item(self, item, spider):
# 此时item对象必须是一个字典,再插入,如果此时item是BaseItem则需要先转换为字典:dict(BaseItem)
item = dict(item)
# print(item)
try:
sql = 'insert into db_data(name,content,link,txt) values(%s,%s,%s,%s)' # SQL语句
self.cursor.execute(sql, [item['name'], item['content'], item['link'], item['txt']]) # 执行sql语句
self.db.commit() # 提交
except Exception as e:
logging.error(f'数据存储异常,原因:{e}')
# 不return的情况下,另一个权重较低的pipeline将不会获得item
return item
# 当所属类运行完成 这个方法就会关闭掉
def close_spider(self, spider):
self.db.close()
3. 开启管道
在settings.py设置开启pipeline:
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300, # 300表示权重
'douban.pipelines.DoubansqlPipeline': 301, # 权重值越小,越优先执行!
}
4. pipeline使用注意点
- 使用之前需要在settings中开启
- pipeline在setting中键表示位置(即pipeline在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过:权重值小的优先执行
- 有多个pipeline的时候,process_item的方法必须return item,否则后一个pipeline取到的数据为None值
- pipeline中process_item的方法必须有,否则item没有办法接受和处理
- process_item方法接受item和spider,其中spider表示当前传递item过来的spider
- def init(self): (或者可以写def open_spider(spider) 😃 都表示能够在爬虫开启的时候执行一次
- def close_spider(self, spider): 能够在爬虫关闭的时候执行一次
- 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接
小结
- 管道能够实现数据的清洗和保存,能够定义多个管道实现不同的功能,其中有个三个方法
- process_item(self,item,spider):实现对item数据的处理
- open_spider(self, spider): 在爬虫开启的时候仅执行一次
- close_spider(self, spider): 在爬虫关闭的时候仅执行一次
scrapy.Request发送post请求
我们可以通过scrapy.Request()指定method、body参数来发送post请求;也可以使用scrapy.FormRequest()来发送post请求
1 发送post请求
注意:scrapy.FormRequest()能够发送表单和ajax请求,参考阅读 https://www.jb51.net/article/146769.htm
2 思路分析
-
找到post的url地址:然后定位url地址为https://www.bydauto.com.cn/api/comom/search_join_shop
-
找到请求体的规律:分析post请求的请求体(参数)
-
start_urls中的url地址是交给parse处理的,如有必要,我们需要重写start_request这个定制方法:
爬虫文件
import scrapy
import json
from jsonpath import jsonpath
class BydSpiderSpider(scrapy.Spider):
name = 'byd_spider'
# 1.检查域名
allowed_domains = ['bydauto.com']
# 2.修改请求url
# start_urls = ['https://www.bydauto.com.cn/api/comom/search_join_shop']
# 注意post请求的起始url发请求的那一刻要求是str类型
city_url = 'https://www.bydauto.com.cn/api/comom/search_join_shop'
# post请求的参数
payload = {"type": 2, "province": 430000, "city": 430100, "network": 'null'}
# 3,构造起始方法:start_requests(self),此方法是spider模块中的定制方法,是一个重写方法,不能修改名字和参数
# 作用:爬虫从该方法开始,此时start_urls 和 parse( ) 函数可删除,可在该start_requests函数中写入多种请求
def start_requests(self):
# 4.将请求信息打包成一个请求对象 并将返回的响应数据交给parse方法处理
yield scrapy.Request(url=self.city_url,method='POST',body=json.dumps(self.payload),callback=self.parse)
# 5.解析比亚迪地址和电话信息
def parse(self, response):
json_data = response.json()
address = jsonpath(json_data,'$..address')
print(address)
tel = jsonpath(json_data,'$..tel')
print(tel)