• Scrapy 教程(七)-架构与中间件


    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 关键字

    DOWNLOADER_MIDDLEWARES = {
       'qiushi.middlewares.UserAgent':1,
       'qiushi.middlewares.ProxyMiddleware':100,
       '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 结合实现代理中间件的例子;

    headers = {
        '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',
        # 'Connection': 'close'
    }
    
    class Proxy_Middleware():
    
        def __init__(self):
            self.s = requests.session()
    
        def process_request(self, request, spider):
            try:
                xdaili_url = spider.settings.get('XDAILI_URL')    # 应该是返回代理ip的url,买的代理
                r = self.s.get(xdaili_url, headers= headers)
                proxy_ip_port = r.text
                request.meta['proxy'] = 'http://' + proxy_ip_port
            except requests.exceptions.RequestException:
                print('***get xdaili fail!')
                spider.logger.error('***get xdaili fail!')
    
        def process_response(self, request, response, spider):
            if response.status != 200:
                try:
                    xdaili_url = spider.settings.get('XDAILI_URL')
    
                    r = self.s.get(xdaili_url, headers= headers)
                    proxy_ip_port = r.text
                    request.meta['proxy'] = 'http://' + proxy_ip_port
                except requests.exceptions.RequestException:
                    print('***get xdaili fail!')
                    spider.logger.error('***get xdaili fail!')
    
                return request
            return response
    
        def process_exception(self, request, exception, spider):
    
            try:
                xdaili_url = spider.settings.get('XDAILI_URL')
    
                r = self.s.get(xdaili_url, headers= headers)
                proxy_ip_port = r.text
                request.meta['proxy'] = 'http://' + proxy_ip_port
            except requests.exceptions.RequestException:
                print('***get xdaili fail!')
                spider.logger.error('***get xdaili fail!')
    
            return request

    重试中间件

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

    from scrapy.downloadermiddlewares.retry import RetryMiddleware
    from scrapy.utils.response import response_status_message
    
    class My_RetryMiddleware(RetryMiddleware):
    
        def process_response(self, request, response, spider):
            if request.meta.get('dont_retry', False):
                return response
    
            if response.status in self.retry_http_codes:
                reason = response_status_message(response.status)
                try:
                    xdaili_url = spider.settings.get('XDAILI_URL')
    
                    r = requests.get(xdaili_url)
                    proxy_ip_port = r.text
                    request.meta['proxy'] = 'https://' + proxy_ip_port
                except requests.exceptions.RequestException:
                    print('获取讯代理ip失败!')
                    spider.logger.error('获取讯代理ip失败!')
    
                return self._retry(request, reason, spider) or response
            return response
    
    
        def process_exception(self, request, exception, spider):
            if isinstance(exception, self.EXCEPTIONS_TO_RETRY) and not request.meta.get('dont_retry', False):
                try:
                    xdaili_url = spider.settings.get('XDAILI_URL')
    
                    r = requests.get(xdaili_url)
                    proxy_ip_port = r.text
                    request.meta['proxy'] = 'https://' + proxy_ip_port
                except requests.exceptions.RequestException:
                    print('获取讯代理ip失败!')
                    spider.logger.error('获取讯代理ip失败!')
    
                return self._retry(request, exception, spider)

    selenium 中间件

    用 selenium 实现中间件

    from scrapy.http import HtmlResponse
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from gp.configs import *
    
    
    class ChromeDownloaderMiddleware(object):
    
        def __init__(self):
            options = webdriver.ChromeOptions()
            options.add_argument('--headless')  # 设置无界面
            if CHROME_PATH:
                options.binary_location = CHROME_PATH
            if CHROME_DRIVER_PATH:
                self.driver = webdriver.Chrome(chrome_options=options, executable_path=CHROME_DRIVER_PATH)  # 初始化Chrome驱动
            else:
                self.driver = webdriver.Chrome(chrome_options=options)  # 初始化Chrome驱动
    
        def __del__(self):
            self.driver.close()
    
        def process_request(self, request, spider):
            try:
                print('Chrome driver begin...')
                self.driver.get(request.url)  # 获取网页链接内容
                return HtmlResponse(url=request.url, body=self.driver.page_source, request=request, encoding='utf-8',
                                    status=200)  # 返回HTML数据
            except TimeoutException:
                return HtmlResponse(url=request.url, request=request, encoding='utf-8', status=500)
            finally:
                print('Chrome driver end...')

    process_response

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

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

    process_exception

    处理异常

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

    from_crawler(cls, crawler)

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

     @classmethod
     def from_crawler(cls, crawler):
         return cls(
                mysql_host = crawler.settings.get('MYSQL_HOST'),
                mysql_db = crawler.settings.get('MYSQL_DB'),
                mysql_user = crawler.settings.get('MYSQL_USER'),
                mysql_pw = crawler.settings.get('MYSQL_PW')
            )

    scrapy 自带下载中间件

    {
        'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100,
        'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300,
        'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350,
        'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400,
        'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500,
        'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
        'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560,
        'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580,
        'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590,
        'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600,
        'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700,
        'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,
        'scrapy.downloadermiddlewares.stats.DownloaderStats': 850,
        'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900,
    }

    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登录

  • 相关阅读:
    数据库分库分表之后,你是如何解决事务问题?
    数据库周刊31丨openGauss 正式开源;7月数据库排行榜发布;浙江移动国产数据库AntDB迁移;oracle ADG跨版本搭建;PG解决社保问题;mysqlbinlog解析……
    2020年7月国产数据库排行:华为、腾讯发新品,中兴、阿里结硕果
    47%的MongoDB数据库遭黑客比特币勒索,你中招了吗?中招怎么办?
    数据库周刊30丨数据安全法草案将亮相;2020数据库产业报告;云南电网上线达梦;达梦7误删Redo Log;Oracle存储过程性能瓶颈;易鲸捷实践案例……
    数据库周刊29│2020数据库研究报告;Oracle取消今年技术大会;腾讯云DBbridge发布支持一键迁库;饿了么迁至阿里云;PG数组查询;Oracle被比特币勒索;DM8 安全管理…
    数据库周刊28│开发者最喜爱的数据库是什么?阿里云脱口秀聊程序员转型;MySQL update误操作;PG流复制踩坑;PG异机归档;MySQL架构选型;Oracle技能表;Oracle文件损坏处理……
    怎么查看HBase表的创建时间
    Hadoop/HBase Kerberos认证失败:Clock skew too great
    java线程莫名异常退出时,如何捕获异常信息
  • 原文地址:https://www.cnblogs.com/yanshw/p/10871040.html
Copyright © 2020-2023  润新知