写点什么

Scrapy Spider 中间件,你学会了吗?本篇博客有一案例

作者:梦想橡皮擦
  • 2021 年 12 月 15 日
  • 本文字数:3107 字

    阅读完需:约 10 分钟

本篇博客补充一下 scrapy 中的 SpiderMiddlerware 相关用法。

scrapy 架构知识补充

在 scrapy 中所有的中间件都被当做类处理(管理这些中间件的类是 MiddlerwareManager),其大概可以分为四种。


  • DownloaderMiddlerware:下载中间件,其中包括方法 process_request()process_response()process_exception()

  • SpiderMiddlerware:爬虫中间件,其中包括方法 process_spider_input()process_spider_output()process_spider_exception()process_start_requests()

  • ItemPipeline:数据管道,包括 process_item()open_spider()close_spider()

  • Extension:其它扩展。


其中各部分被使用的场景依次如下:


  • DownloaderMiddlerware:执行请求响应相关问题,例如修改用户代理;

  • SpiderMiddlerware:修改或删除特定请求;

  • ItemPipeline:修改或保存数据

  • Extension:其它扩展。

SpiderMiddlerware

SpiderMiddlerware 是一个钩子框架,其存在如下三个作用。


  1. Downloader 产生 Response 之后,可以使用;

  2. Spider 产生 Request 之后,可以使用;

  3. Spider 产生 Item 之后,可以使用。


这些中间件都存在 4 个核心方法,各个方法被调用的时间点如下所示:


  • process_spider_input(response, spider):当 Response 对象被 SpideMiddleware 处理时,process_spider_input() 方法被调用,控制响应内容;

  • process_spider_output(response, result, spider):当 Spider 处理 Response 对象返回结果时,process_spider_output() 被调用,控制响应输出;

  • process_spider_exception(response, exception, spider):当 Spider 或 SpideMiddleware 的 process_spider_input() 方法抛出异常时,process_spider_exception()方法被调用,异常捕捉;

  • process_start_requests(start_requests, spider):当 Spider 产生 Request 请求时被调用,执行类似 process_spider_output()


现在,你已经了解了核心方法,并大概知道了基本的使用位置,通过这些内容可以辅助我们理解 SpideMiddleware,下面通过代码进行测试。


middlewares.py 文件中的 CncnSpiderMiddleware 类内部,都增加输出函数,然后运行 scrapy 爬虫,获得如下输出。可以看到 spider_opened() 方法被优先调用,然后依次是 _requests_spider_input_spider_ouput



process_start_requests(start_requests, spider)当 spider 运行到 start_requests()方法时,SpiderMiddlerware 调用 process_start_requests() 方法,在 start_requests 参数中并且必须返回另一个可迭代的 Request 对象,默认代码如下所示:


for r in start_requests:    yield r
复制代码


process_spider_input(response, spider)每个进入 SpiderMiddlerware 的 Response 响应对象,都会调用此方法,process_spider_input() 返回 None 或抛出异常,例如修改响应对象的状态码。


def process_spider_input(self, response, spider):     print("----process_spider_input--- 被调用")     response.status = 201     return None
复制代码


process_spider_output(response, result, spider)在处理完响应对象之后,使用 Spider 返回的结果调用此方法,process_spider_output() 可返回可迭代的 Request 或者 Item 对象。


当你了解 SpiderMiddlerware 之后,会发现其功能与 DownloaderMiddlerware 非常相似,具体场景可能还需要打磨。


系统内置了一些 SpiderMiddlerware ,在 default_settings.py 文件中可进行查阅。


SPIDER_MIDDLEWARES_BASE = {    # Engine side    'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware': 50,    'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': 500,    'scrapy.spidermiddlewares.referer.RefererMiddleware': 700,    'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware': 800,    'scrapy.spidermiddlewares.depth.DepthMiddleware': 900,    # Spider side}
复制代码

DepthMiddleware

限制爬取深度。

HttpErrorMiddleware

过滤出所有失败/错误的 Response

OffsiteMiddleware

过滤出所有域名不在 spider 属性 allowed_domainsRequest

RefererMiddleware

用于填充 Request 请求头的 Referer 属性。

UrlLengthMiddleware

过滤出 URL 长度比 URLLENGTH_LIMIT 长的 Request

scrapy 爬虫时间

又到了编写爬虫代码的时刻了,本次目标站点:中国社区网,该类网站数据不能在本地存储哦,爬取完毕就删除。



由于网站设计 3 层数据,所以代码嵌套逻辑比较多,cncn.py 文件代码如下,其中 parse 方法获取省份,parse_c 获取城市,parse_s 获取街道,这里如果城市中还分区,省略了各个区县,当然你可以自己扩展出来。(其实这个地方最后一个层级我偷懒了,没有写),重点学习数据在各个 Request 之间的传递。


import scrapyfrom cncn.items import CncnItemfrom urllib import parseimport json

class CnSpider(scrapy.Spider): name = 'cn' allowed_domains = ['cncn.org.cn'] start_urls = ['http://www.cncn.org.cn/map/']
def parse(self, response): lis = response.css('ul.sub_maps li') for p in lis: p_name = p.css('a::text').get() p_url_id = p.css('a::attr(href)').re_first('pid=(\d+)')
yield scrapy.Request(url=f'http://cms.cncn.org.cn/api/map_province_index.php?pid={p_url_id}&callback=', meta={'p_name': p_name}, callback=self.parse_c)
def parse_c(self, response): p_name = response.meta['p_name']
map_list = json.loads(response.text) cs = map_list["map_list"][0]["province_items"]
for c in cs.values(): c_name = c["city_name"] c_id = c["city_id"] yield scrapy.Request(url=f'http://cms.cncn.org.cn/api/map_city_index.php?cid={c_id}&callback=', meta={'p_name': p_name, 'c_name': c_name}, callback=self.parse_s)
def parse_s(self, response): p_name = response.meta['p_name'] c_name = response.meta['c_name'] map_list = json.loads(response.text) map_list = map_list["map_list"][0] city_items = None if "city_items" in map_list: city_items = map_list["city_items"]
if city_items is not None: # 迭代城市 for d in city_items.values(): # 如果区下面有街道,获取街道数据 if "district_items" in d and len(d["district_items"]) > 0: for sub_d in d["district_items"]: d_name = sub_d["community_name"] # 存储数据 item = CncnItem() item["p_name"] = p_name item["c_name"] = c_name item["d_name"] = d_name print(item) yield item
else: return None
复制代码



数据保存到 CSV 文件中,如下所示。


写在后面

今天是持续写作的第 <font color=red>255</font> / 365 天。期待 <font color=#04a9f4>关注</font>,<font color=#04a9f4>点赞</font>、<font color=#04a9f4>评论</font>、<font color=#04a9f4>收藏</font>。


发布于: 3 小时前阅读数: 5
用户头像

爬虫 100 例作者,蓝桥签约作者,博客专家 2021.02.06 加入

6 年产品经理+教学经验,3 年互联网项目管理经验; 互联网资深爱好者; 沉迷各种技术无法自拔,导致年龄被困在 25 岁; CSDN 爬虫 100 例作者。 个人公众号“梦想橡皮擦”。

评论

发布
暂无评论
Scrapy Spider中间件,你学会了吗?本篇博客有一案例