Scrapy 使用 Twisted 这个异步框架来处理网络通信,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。

Scrapy 架构

其实之前的教程都有涉及,这里再做个系统介绍

  • Engine :Scrapy 引擎,即控制中心,负责控制数据流在系统的各个组件中流动,并根据相应动作触发事件;引擎首先从爬虫获取初始request请求(1)
  • Scheduler : 调度器,调度器从引擎接收request请求(2),并存入队列,在需要时再将request请求提供给引擎(3)
  • Downloade : 下载器,负责下载页面;从引擎获取request请求(4),并将下载内容返回给引擎(5)
  • Spider: 爬虫,解析html,提取数据;从引擎获取下载器返回的页面内容(6),提取item数据返回给引擎(7)
  • Item Pipeline: 数据处理管道,负责处理提取好的item,如清洗,存储等;从引擎获取item,并将新的url传递给调度器(8)
  • 下载器中间件: 引擎与下载器之间特定钩子,其提供了一个简单机制,通过插入自定义代码来扩展Scrapy;可自定义 request 请求的方式及 response 的处理
  • 爬虫中间件: 引擎与爬虫之间的特点钩子,机制同上;处理 输入 request 和输出 response【如给url加个数字,response 编码等】

中间件详解

Scrapy 自带很多中间件,这些中间件都可以被重写,重写的中间件需要手动激活。

中间件激活

下载中间件激活方法是设定 settings 文件中 DOWNLOADER_MIDDLEWARES 关键字

  1. DOWNLOADER_MIDDLEWARES = {
  2. 'qiushi.middlewares.UserAgent':1,
  3. 'qiushi.middlewares.ProxyMiddleware':100,
  4. 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None}

数字代表优先级,通常取值为1-1000,数字越小,越靠近引擎,故数字越小,request 优先处理,数字越大,response 优先处理

爬虫中间件激活方式类,只是设定关键字为 SPIDER_MIDDLEWARES

自定义下载中间件

在创建爬虫项目时,框架自动生成了一个 middlewares.py 文件,里面定义了两个中间件,XXXSpiderMiddleware 和  XXXDownloaderMiddleware,但这两个中间件基本只是个空架子;

我们可以直接修改这两个中间件来实现自定义,也可以在这个文件中重新写,也可以自己创建文件重新写;

重写中间件时方法名是固定的;

process_request(self, request, spider)          处理请求的中间件,最常用,如代理,伪装浏览器,headers等

process_response(self, request, response, spider):   处理响应的中间件

process_exception(self, request, exception, spider):处理异常的中间件

process_request

每次request请求都会自动调用这个方法;

该方法一般返回None,也可以返回 Response 对象、request 对象、IgnoreRequest对象

  • None:继续处理该 request,执行其他中间件中的该方法,
  • Response:请求结束,不再执行其他中间件的该方法,
  • request:结束本请求,重新执行返回的request
  • IgnoreRequest:执行 process_exception 方法;如果没有相应的方法处理异常,则调用 request 的 errback 方法;如果没有代码处理该异常,则忽略

具体例子可参考我的博客  https://www.cnblogs.com/yanshw/p/10858087.html

这里再展示一个 scrapy 与 requests 结合实现代理中间件的例子;

  1. headers = {
  2. 'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
  3. # 'Connection': 'close'
  4. }
  5.  
  6. class Proxy_Middleware():
  7.  
  8. def __init__(self):
  9. self.s = requests.session()
  10.  
  11. def process_request(self, request, spider):
  12. try:
  13. xdaili_url = spider.settings.get('XDAILI_URL')    # 应该是返回代理ip的url,买的代理
  14. r = self.s.get(xdaili_url, headers= headers)
  15. proxy_ip_port = r.text
  16. request.meta['proxy'] = 'http://' + proxy_ip_port
  17. except requests.exceptions.RequestException:
  18. print('***get xdaili fail!')
  19. spider.logger.error('***get xdaili fail!')
  20.  
  21. def process_response(self, request, response, spider):
  22. if response.status != 200:
  23. try:
  24. xdaili_url = spider.settings.get('XDAILI_URL')
  25.  
  26. r = self.s.get(xdaili_url, headers= headers)
  27. proxy_ip_port = r.text
  28. request.meta['proxy'] = 'http://' + proxy_ip_port
  29. except requests.exceptions.RequestException:
  30. print('***get xdaili fail!')
  31. spider.logger.error('***get xdaili fail!')
  32.  
  33. return request
  34. return response
  35.  
  36. def process_exception(self, request, exception, spider):
  37.  
  38. try:
  39. xdaili_url = spider.settings.get('XDAILI_URL')
  40.  
  41. r = self.s.get(xdaili_url, headers= headers)
  42. proxy_ip_port = r.text
  43. request.meta['proxy'] = 'http://' + proxy_ip_port
  44. except requests.exceptions.RequestException:
  45. print('***get xdaili fail!')
  46. spider.logger.error('***get xdaili fail!')
  47.  
  48. return request

重试中间件

有时候代理会被远程拒绝或者超时,此时需要换个代理重新请求,就是重写中间件 scrapy.downloadermiddlewares.retry.RetryMiddleware

  1. from scrapy.downloadermiddlewares.retry import RetryMiddleware
  2. from scrapy.utils.response import response_status_message
  3.  
  4. class My_RetryMiddleware(RetryMiddleware):
  5.  
  6. def process_response(self, request, response, spider):
  7. if request.meta.get('dont_retry', False):
  8. return response
  9.  
  10. if response.status in self.retry_http_codes:
  11. reason = response_status_message(response.status)
  12. try:
  13. xdaili_url = spider.settings.get('XDAILI_URL')
  14.  
  15. r = requests.get(xdaili_url)
  16. proxy_ip_port = r.text
  17. request.meta['proxy'] = 'https://' + proxy_ip_port
  18. except requests.exceptions.RequestException:
  19. print('获取讯代理ip失败!')
  20. spider.logger.error('获取讯代理ip失败!')
  21.  
  22. return self._retry(request, reason, spider) or response
  23. return response
  24.  
  25. def process_exception(self, request, exception, spider):
  26. if isinstance(exception, self.EXCEPTIONS_TO_RETRY) and not request.meta.get('dont_retry', False):
  27. try:
  28. xdaili_url = spider.settings.get('XDAILI_URL')
  29.  
  30. r = requests.get(xdaili_url)
  31. proxy_ip_port = r.text
  32. request.meta['proxy'] = 'https://' + proxy_ip_port
  33. except requests.exceptions.RequestException:
  34. print('获取讯代理ip失败!')
  35. spider.logger.error('获取讯代理ip失败!')
  36.  
  37. return self._retry(request, exception, spider)

selenium 中间件

用 selenium 实现中间件

  1. from scrapy.http import HtmlResponse
  2. from selenium import webdriver
  3. from selenium.common.exceptions import TimeoutException
  4. from gp.configs import *
  5.  
  6. class ChromeDownloaderMiddleware(object):
  7.  
  8. def __init__(self):
  9. options = webdriver.ChromeOptions()
  10. options.add_argument('--headless') # 设置无界面
  11. if CHROME_PATH:
  12. options.binary_location = CHROME_PATH
  13. if CHROME_DRIVER_PATH:
  14. self.driver = webdriver.Chrome(chrome_options=options, executable_path=CHROME_DRIVER_PATH) # 初始化Chrome驱动
  15. else:
  16. self.driver = webdriver.Chrome(chrome_options=options) # 初始化Chrome驱动
  17.  
  18. def __del__(self):
  19. self.driver.close()
  20.  
  21. def process_request(self, request, spider):
  22. try:
  23. print('Chrome driver begin...')
  24. self.driver.get(request.url) # 获取网页链接内容
  25. return HtmlResponse(url=request.url, body=self.driver.page_source, request=request, encoding='utf-8',
  26. status=200) # 返回HTML数据
  27. except TimeoutException:
  28. return HtmlResponse(url=request.url, request=request, encoding='utf-8', status=500)
  29. finally:
  30. print('Chrome driver end...')

process_response

当请求发出去返回时会调用该方法;

  • 返回 Response,由下个中间件或者解析器parse处理;
  • 返回 Request,说明没有成功返回,中间链停止,重新request;
  • 返回 IgnoreRequest,回调函数 Request.errback将会被调用处理,若没处理,将会忽略

process_exception

处理异常

  • 返回 None:调用其他中间件的该方法处理异常
  • 返回 Response:异常已被处理,交给中间件的 process_response 方法处理
  • 返回 Request:结束该 request,重新执行新的 request

from_crawler(clscrawler)

这个函数通常是 访问 settings 和 signals 的入口;类方法

  1. @classmethod
  2. def from_crawler(cls, crawler):
  3. return cls(
  4. mysql_host = crawler.settings.get('MYSQL_HOST'),
  5. mysql_db = crawler.settings.get('MYSQL_DB'),
  6. mysql_user = crawler.settings.get('MYSQL_USER'),
  7. mysql_pw = crawler.settings.get('MYSQL_PW')
  8. )

scrapy 自带下载中间件

  1. {
  2. 'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
  3. 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
  4. 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
  5. 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
  6. 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
  7. 'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
  8. 'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
  9. 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
  10. 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
  11. 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
  12. 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
  13. 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
  14. 'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
  15. 'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
  16. }

RobotsTxtMiddleware 这个中间件会查看 settings 中 ROBOTSTXT_OBEY 是True还是False,如果是True,表示要遵守 Robots.txt 协议;

中间件验证

你可以通过自己认为可行的方式验证;

这里介绍一个网站,可以验证中间件是否生效

http://exercise.kingname.info

http://exercise.kingname.info/exercise_middleware_ua  验证User-Agent

http://exercise.kingname.info/exercise_middleware_ip        验证代理

都可以翻页的,只需在url后加/page,如exercise.kingname.info/exercise_middleware_ip/3

自定义爬虫中间件

不是很常用,也是实现几个固定方法

process_spider_input(response, spider)

response 通过爬虫中间件时,该方法被调用

process_spider_output(response, result, spider)

爬虫处理完 response 时,调用该方法,必须返回 Request或者 Item对象的可迭代对象

process_spider_exception(response, exception, spider)

当spider中间件抛出异常时,这个方法被调用,返回None或可迭代对象的Request、dict、Item

参考资料:

https://zhuanlan.zhihu.com/p/42498126

https://www.jianshu.com/p/70af817db7df

http://www.cnblogs.com/xieqiankun/p/know_middleware_of_scrapy_1.html

https://blog.csdn.net/on_the_road_2018/article/details/80985524    这个教程有详细的cookie登录

Scrapy 教程(七)-架构与中间件的更多相关文章

  1. CRL快速开发框架系列教程七(使用事务)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  2. Laravel教程 七:表单验证 Validation

    Laravel教程 七:表单验证 Validation 此文章为原创文章,未经同意,禁止转载. Laravel Form 终于要更新这个Laravel系列教程的第七篇了,期间去写了一点其他的东西. 就 ...

  3. 无废话ExtJs 入门教程七[登陆窗体Demo:Login]

    无废话ExtJs 入门教程七[登陆窗体Demo:Login] extjs技术交流,欢迎加群(201926085) 在这节我们通过前几节讲的内容做一个登陆页面,把前几节讲的内容贯穿一下. 1.代码如下: ...

  4. ASP.NET 5系列教程(七)完结篇-解读代码

    在本文中,我们将一起查看TodoController 类代码. [Route] 属性定义了Controller的URL 模板: [Route("api/[controller]") ...

  5. 黄聪:Microsoft Enterprise Library 5.0 系列教程(七) Exception Handling Application Block

    原文:黄聪:Microsoft Enterprise Library 5.0 系列教程(七) Exception Handling Application Block 使用企业库异常处理应用程序模块的 ...

  6. scrapy中的下载器中间件

    scrapy中的下载器中间件 下载中间件 下载器中间件是介于Scrapy的request/response处理的钩子框架. 是用于全局修改Scrapy request和response的一个轻量.底层 ...

  7. webpack4 系列教程(七): SCSS提取和懒加载

    教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步>>> (原文)webpack4 系列教程(七): SCSS 提取和懒加载. 个人技术小站: https://god ...

  8. Miniconda安装scrapy教程

    一.背景说明 前两天想重新研究下Scrapy,当时的环境是PyCharm社区版+Python 3.7.使用pip安装一直报错 “distutils.errors.DistutilsPlatformEr ...

  9. 第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP

    第三百四十八节,Python分布式爬虫打造搜索引擎Scrapy精讲—通过自定义中间件全局随机更换代理IP 设置代理ip只需要,自定义一个中间件,重写process_request方法, request ...

随机推荐

  1. anaconda历史版本下载

    anaconda历史版本安装: anaconda所有版本链接:https://repo.continuum.io/archive/ 清华大学开源软件镜像站:https://mirrors.tuna.t ...

  2. 13. ClustrixDB 基于时间点恢复

    在不太可能发生灾难的情况下,可以在特定数据库.表或整个集群上执行ClustrixDB集群的某个时间点恢复.应该非常小心地处理这一问题. 先决条件 在你可以使用时间点恢复之前,你的集群应该有几个先决条件 ...

  3. js判断条件为“假”的情况

    以下6种情况判断结果为"假": 1.false(布尔类型) 2.null(用于定义空的或者不存在的引用) 3.undefined(未定义) 4.0(数值0) 5.''(空字符串) ...

  4. floor函数用法

    floor(x),也写做Floor(x),其功能是“向下取整”,或者说“向下舍入”,即取不大于x的最大整数(与“四舍五入”不同,下取整是直接取按照数轴上最接近要求值的左边值,即不大于要求值的最大的那个 ...

  5. max pool实现

    题目 二维矩阵(nm) 求每个(lw)的子矩阵的最大元素, 就是一维滑动窗口的升级版 自己瞎掰的题解 #include <bits/stdc++.h> using namespace st ...

  6. linux socket设置阻塞与非阻塞

    非阻塞IO 和阻塞IO: 在网络编程中对于一个网络句柄会遇到阻塞IO 和非阻塞IO 的概念, 这里对于这两种socket 先做一下说明:       基本概念: 阻塞IO:: socket 的阻塞模式 ...

  7. 2019.9.16JAVA课堂作业

    public class TestDouble {     public static void main(String args[]) {        System.out.println(&qu ...

  8. 南京网络赛 E K Sum

    K Sum 终于过了这玩意啊啊啊==== 莫比乌斯反演,杜教筛,各种分块,积性函数怎么线性递推还很迷==,得继续研究研究 #include<bits/stdc++.h> using nam ...

  9. 安装mariadb报错: Job for mariadb.service failed because the control process exited with error code. See "systemctl status mariadb.service" and "journalctl -xe" for details.

    卸载和删除都使用过了,没有起到效果,然后用了如下的方案,进行解决: CentOS 从 Yum 源安装配置 Mariadb 2017.03.01 WangYan 学习笔记  热度 7℃ 一.安装 Mar ...

  10. Oracle开发:常用的数据库字段类型[转]

    Oracle常用的数据库字段类型如下: 字段类型 中文说明 限制条件 其它说明 CHAR 固定长度字符串 最大长度2000 bytes VARCHAR2 可变长度的字符串 最大长度4000 bytes ...