一、爬虫中间件简介

图 1-1

图 1-2

开始这一张之前需要先梳理一下这张图, 需要明白下载器中间件和爬虫中间件所在的位置

  1. 下载器中间件是在引擎(ENGINE)将请求推送给下载器(DOWNLOADER)时会执行到的
  2. 当下载器(DOWNLOADER)完成下载后, 将下载的Response对象传回给引擎(ENGLIE)时也会经过下载器中间件
  3. 当爬虫(SPIDER)把任务给引擎(ENGINE)的时候, 会经过爬虫中间件
  4. 当引擎(ENGINE)把数据传回给爬虫(SPIDER)的时候, 会经过爬虫中间件

此外:

图1-2中,4、5表示下载器中间件,6、7表示爬虫中间件。爬虫中间件会在以下几种情况被调用。

  1. 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的process_spider_output()方法被调用。
  2. 当爬虫本身的代码出现了Exception的时候,爬虫中间件的process_spider_exception()方法被调用。
  3. 当爬虫里面的某一个回调函数parse_xxx()被调用之前,爬虫中间件的process_spider_input()方法被调用。
  4. 当运行到start_requests()的时候,爬虫中间件的process_start_requests()方法被调用。

所以, 下载器中间件是引擎和下载器之间处理数据所使用的, 爬虫中间件是爬虫和引擎之间处理数据时使用的

二、爬虫中间件类与方法

process_spider_input(response,spider)

当response通过spider中间件时,该方法被调用,处理该response。

应该返回一个None或者抛出一个异常(exception)。

  • 如果其返回None,Scrapy将会继续处理该response,调用所有其他中间件直到spider处理该response。
  • 如果其抛出一个异常(exception),Scrapy将不会调用任何其他中间件的process_spider_input()方法,并调用request的errback。errback的输出将会以另一个方向被输入到中间链中,使用process_spider_output()方法来处理,当其抛出异常时则带调用process_spider_exception()

参数:
response(Response对象) - 被处理的response
spider(Spider对象) - 该response对应的spider

process_spider_out(response, result, spider)

当Spider处理response返回result时,该方法被调用。(即spider处理response返回结果后)

process_spider_output()必须返回包含Request或Item对象的可迭代对象(iterable)。

参数:
response(Response对象) - 生成该输出的response
result(包含Reques或Item对象的可迭代对象(iterable)) - spider返回的result
spider(Spider对象) - 其结果被处理的spider

process_spider_exception(response, exception, spider)

当spider或(其它spider中间件的)process_spider_input()抛出异常时,该方法被调用

process_spider_exception()必须要么返回None,要么返回一个包含Response或Item对象的可迭代对象(iterable)。

通过其返回None,Scrapy将继续处理该异常,调用中间件链中的其它中间件的process_spider_exception()
如果其返回一个可迭代对象,则中间件链的process_spider_output()方法被调用,其他的process_spider_exception()将不会被调用。

参数:

response(Response对象) - 异常被抛出时被处理的response
exception(Exception对象) - 被抛出的异常
spider(Spider对象) - 抛出异常的spider

process_start_requests(start_requests, spider)
该方法以spider启动的request为参数被调用,执行的过程类似于process_spider_output(),只不过其没有相关联的response并且必须返回request(不是item)。

其接受一个可迭代的对象(start_requests参数)且必须返回一个包含Request对象的可迭代对象。

当在您的spider中间件实现该方法时,您必须返回一个可迭代对象(类似于参数start_requests)且不要遍历所有的start_requests。
该迭代器会很大(甚至是无限),进而导致内存溢出。
Scrapy引擎再其具有能力处理start_requests时将会拉起request,因此start_requests迭代器会变得无限,而由其它参数来停止spider(例如时间限制或者item/page计数)。

参数:


start_requests(b包含Request的可迭代对象)

- start requests
spider(Spider对象)

- start request所属的spider

三、编写自己的爬虫中间件

编写一个简单的爬虫, 实现两个需求

  1. 将所有返回的Response(响应)的响应码改为500
  2. 统计解析出来的item的数量
# spider.py
# -*- coding: utf-8 -*-
import scrapy
from ccidcom.items import CcidcomItem class CcidcomspiderSpider(scrapy.Spider):
name = 'ccidcomSpider' def start_requests(self):
self.logger.info('-----------spider.start_requests----------')
yield scrapy.Request('http://www.ccidcom.com/yaowen/index.html') def parse(self, response):
self.logger.info('--------spider.parse--------')
article_list = response.css('div.article-item')
for article in article_list:
item = CcidcomItem()
item['title'] = article.css('a font::text').get()
item['url'] = response.url
yield item def parse_baidu(self, response):
pass # items.py
import scrapy class CcidcomItem(scrapy.Item):
title = scrapy.Field()
url = scrapy.Field()

编写爬虫中间件

# middlewares.py
from scrapy import signals
from ccidcom.items import CcidcomItem class CcidcomSpiderMiddleware(object):
item_number = 0 ... def process_spider_input(self, response, spider):
spider.logger.info('--------spider_input-----------')
# 将响应码强制改为500
response.status = 500
return None def process_spider_output(self, response, result, spider):
spider.logger.info('--------spider_output-start-----------')
for i in result:
if type(i) == CcidcomItem:
self.item_number += 1
yield i
spider.logger.info('--------spider_output-end-----------')
spider.logger.info('item抓取到的数量:{}'.format(self.item_number)) def process_spider_exception(self, response, exception, spider):
pass def process_start_requests(self, start_requests, spider):
spider.logger.info('--------start_requests-----------')
for r in start_requests:
yield r
...

启用爬虫中间件

# settings.py
SPIDER_MIDDLEWARES = {
'ccidcom.middlewares.CcidcomSpiderMiddleware': 543,
}

执行的顺序

    1. 1.爬虫中间件的process_start_requests方法
    2. 2.爬虫的start_requests方法
    3. 3.爬虫中间件的process_spider_input方法
    4. 4.爬虫中间件的process_spider_output方法
    5. 5.爬虫的parse方法
    6. 6.爬虫中间件的process_spider_output方法

好像有点乱, 为啥process_spider_output会被运行了两次呢?而且还是第一次只输出了spider_output-start, 最后才输出了spider_output-end?

其实咱们跟着代码走一遍,就很好理解了

      1. 代码执行到了爬虫中间件的process_start_requests方法, 输出了start_requests, 但是参数start_requests其实是spider的start_requests方法的生成器, 所以只有执行到for r in start_requests, 才真正开始执行spider的start_requests方法
      2. 交给下载器完成下载
      3. 下载后引擎将Response传给爬虫中间件的process_spider_input方法
      4. process_spider_input方法会调用process_spider_output方法, 并且将发起请求时Requestcallback指定的方法, 变为process_spider_outputresult参数, 这时也输出了spider_output-start
      5. process_spider_output方法运行到for i in result:时, 则真正开始执行Request对象指定的callback方法, 并且变为了生成器, yield回引擎
      6. process_spider_output执行完result后, 就输出了spider_output-end.

以上就是整个爬虫中间件的运行流程。

# middlewares.py
from scrapy import signals
from ccidcom.items import CcidcomItem class CcidcomSpiderMiddleware(object):
item_number = 0 ... def process_spider_input(self, response, spider):
spider.logger.info('--------spider_input-----------')
# 将响应码强制改为500
response.status = 500
return None def process_spider_output(self, response, result, spider):
spider.logger.info('--------spider_output-start-----------')
for i in result:
if type(i) == CcidcomItem:
self.item_number += 1
yield i
spider.logger.info('--------spider_output-end-----------')
spider.logger.info('item抓取到的数量:{}'.format(self.item_number)) def process_spider_exception(self, response, exception, spider):
pass def process_start_requests(self, start_requests, spider):
spider.logger.info('--------start_requests-----------')
for r in start_requests:
yield r
...
 

scrapy 基础组件专题(三):爬虫中间件的更多相关文章

  1. scrapy 基础组件专题(八):scrapy-redis 框架分析

    scrapy-redis简介 scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目的分布式开发和部署. 有如下特征:  分布式爬取 您可以启动多个spider工 ...

  2. scrapy 基础组件专题(一):scrapy框架中各组件的工作流程

    Scrapy 使用了 Twisted 异步非阻塞网络库来处理网络通讯,整体架构大致如下(绿线是数据流向): Scrapy主要包括了以下组件: 引擎(Scrapy)用来处理整个系统的数据流处理, 触发事 ...

  3. scrapy 基础组件专题(七):scrapy 调度器、调度器中间件、自定义调度器

    一.调度器 配置 SCHEDULER = 'scrapy.core.scheduler.Scheduler' #表示scrapy包下core文件夹scheduler文件Scheduler类# 可以通过 ...

  4. scrapy 基础组件专题(二):下载中间件

    下载器中间件是介于Scrapy的request/response处理的钩子框架,是用于全局修改Scrapy request和response的一个轻量.底层的系统. 1.激活Downloader Mi ...

  5. scrapy 基础组件专题(九):scrapy-redis 源码分析

    下面我们来看看,scrapy-redis的每一个源代码文件都实现了什么功能,最后如何实现分布式的爬虫系统: connection.py 连接得配置文件 defaults.py 默认得配置文件 dupe ...

  6. scrapy 基础组件专题(五):自定义扩展

    通过scrapy提供的扩展功能, 我们可以编写一些自定义的功能, 插入到scrapy的机制中 一.编写一个简单的扩展 我们现在编写一个扩展, 统计一共获取到的item的条数我们可以新建一个extens ...

  7. scrapy 基础组件专题(四):信号运用

    一.scrapy信号使用的简单实例 import scrapy from scrapy import signals from ccidcom.items import CcidcomItem cla ...

  8. scrapy 基础组件专题(十四):scrapy CookiesMiddleware源码

    一 Scrapy框架--cookie的获取/传递/本地保存 1. 完成模拟登陆2. 登陆成功后提取出cookie,然后保存到本地cookie.txt文件中3. 再次使用时从本地的cookie.txt中 ...

  9. scrapy 基础组件专题(十二):scrapy 模拟登录

    1. scrapy有三种方法模拟登陆 1.1直接携带cookies 1.2找url地址,发送post请求存储cookie 1.3找到对应的form表单,自动解析input标签,自动解析post请求的u ...

随机推荐

  1. DML_Data Modification_INSERT

    Data Modification (INSERT.DELETE.UPDATE.MERGE)之INSERT(基础知识,算是20年来第2次学习MSSQL吧,2005年折腾过一段时间的Oracle)INS ...

  2. 2019-02-08 Python学习之Scrapy的简单了解

    今天遇到的问题和昨天差不多,一个Scrapy装了好久,anaconda卸了又装,pycharm卸了又装,环境变量配置一堆,依赖包下载一堆.查了一堆资料总算是搞好了. Scripy: 先放个框架结构图( ...

  3. Python SimpleHTTPServer (python3 -m http.server 6789)

    搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享.但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方.比如你想快速共享Linux系统的某个目录给整个项目 ...

  4. centos7搭建EFK

    环境: system: CentOS Linux release 7.7.1908 elasticsearch: elasticsearch-7.5.1-1.x86_64 kibana: kibana ...

  5. 【大厂面试08期】谈一谈你对HashMap的理解?

    摘要 HashMap的原理也是大厂面试中经常会涉及的问题,同时也是工作中常用到的Java容器,本文主要通过对以下问题进行分析讲解,来帮助大家理解HashMap的原理. 1.HashMap添加一个键值对 ...

  6. navicat 出现 mysql远程连接问题 Lost connection to MySQL server at ‘reading initial communication packet', system error: 0

    今天做服务器上的东西需要看数据库时,突然发现有这个报错,然后自己也查了很多资料 我最后找到一个在my,cnf配置文件中mysqld下加入一条 max_allowed_packet = 500M 也就是 ...

  7. gdb基本命令总结

    本文介绍使用gdb调试程序的常用命令.  主要内容: [简介] [举例] [其他] [简介]  ============= GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具.如果你是在 ...

  8. .Net Core微服务入门全纪录(五)——Ocelot-API网关(下)

    前言 上一篇[.Net Core微服务入门全纪录(四)--Ocelot-API网关(上)]已经完成了Ocelot网关的基本搭建,实现了服务入口的统一.当然,这只是API网关的一个最基本功能,它的进阶功 ...

  9. I/O格式化与运算符

    I/O格式化与运算符 输出函数 Python3 - print() 在Python3中.print()的使用方法如下: >>> # ==== Python3 print() ==== ...

  10. 算法岗面试题:模型的bias和variance是什么?用随机森林举例

    校招在即,准备准备一些面试可能会用到的东西吧.希望这次面试不会被挂. 基本概念 说到机器学习模型的误差,主要就是bias和variance. Bias:如果一个模型的训练错误大,然后验证错误和训练错误 ...