Scrapy(二) Downloader Middleware 的使用

介绍

Downloader Middleware 即下载中间件,它是处于Scrapy 的Engine和Downloader之间的处理模块。在Engine把Scheduler 获取的Request
发送给Downloader 的过程中,以及Downloader把Request发送回Engine的过程中,Request 和 Response 都会经过Middlerware的处理

在整个架构中起作用的是以下两个位置:

  • Engine 从 Scheduler 获取 Request 发送给 Downloader ,在Request 被 Engine 发送给Downloader 执行下载之前,Downloader Middlerware
    可以对Request 进行修改

  • Downloader 执行Request 后生成Response,在Response 被Engine 发送给Spider之前,也就是在Response被Spider解析之前,Downloader Middlerware
    可以对Response 进行修改

功能十分强大:修改User-Agent、处理重定向、设置代理、失败重试、设置Cookie等功能都需要借助它来实现

使用说明

Scrapy 默认提供的DOWNLOADER_MIDDLEWARE 如下:
值是一个代表优先级的数字,值越小月越靠近Engine,值越大代表越靠近Downloader

RedirectMiddleware: 自动处理重定向
RetryMiddleware: 自动重试功能

DOWNLOADER_MIDDLEWARES_BASE = {
    # Engine side
    '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,
    # Downloader side
}

Downloader Middlerware 工作流程:

每个Downloader Middlerware都可以定义process_request 和 process_response 方法来分别处理request和response
被开启的Downloader Middlerware 的process_request 和 process_response 方法会根据优先级被顺序调用

process_request: 由 Engine => Downloader ,配置的数字越小,越靠近Engine, 越优先被调用
process_response:由Downloader => Engine ,数字越大,越靠近Downloader,越优先被调

核心方法

在项目实战中往往需要自定义Downloader Middler,过程非常简单,只需要实现以下至少一个方法

  • process_request(request,spider)
  • process_response(request,request,spider)
  • process_exception(request,exception,spider)

process_request(request,spider)

这个方法的返回值必须为None、Response对象、Request对象三者之一,或者抛出IgonreRequest异常

  • request: 即被处理的Request对象
  • spider: 即此Request 对应的Spider 对象

返回类型不同,产生的效果也不同。

  • 当返回的是None时,Scrapy 将继续处理该Request,接着执行其他Downloader Middlerware的process_request方法,一直到Downloader 把Request
    执行得到Response才结束。这个过程其实就是修改Request的过程,不同的Downloader Middlerware 按照设置的优先级顺序依次对Request进行修改
    ,最后送至Downloader执行

  • 当返回为Response时,更低优先级的Downlader Middlerware的process_request 和 process_expextion 方法就不会被继续调用,每个Downloader
    Middlerware 的 process_response 方法转而被依次调用。调用完毕后直接将Response对象发送给Spider处理

  • 当返回为Request 对象时,更低优先级的Downlader Middlerware的process_request方法会停止执行。这个Request会重新放到调度队列里,其实它
    就是一个全新的Request,等待被调度。如果被Scheduler调度了,那么所有的Downloader Middlerware 的process_request 方法会被重新按照顺序执行

  • 如果抛出IgonreRequest异常,则所有的Downloader Middlerware的process_exception方法会依次执行。如果没有一个方法处理这个异常,那么Request的
    errorback方法就会回调。如果该异常还没有被处理,那么它便会被忽略

process_response(request,response,spider)

该方法返回值必须为Request和Response对象两者之一,或者抛出IgonreRequest异常

  • 当返回Request对象时,更低优先级的Downloader Middlerware的process_response方法不会继续调用。这个Request会重新放到调度队列里,其实它
    就是一个全新的Request,等待被调度。如果被Scheduler调度了,那么所有的Downloader Middlerware 的process_request 方法会被重新按照顺序执行

  • 当返回为Response 对象时,更低优先级的Downloader Middler 的 process_response 方法会继续被调用,对该Response对象进行处理

  • 如果抛出IngoreRequest异常,则Request的errorback方法会回调。如果该异常还没有被处理,那么它会被忽略

process_exception(request,exception,spider)

方法的返回值必须为None,Response对象、Request对象三者之一

  • 当返回为None时,更低优先级的Downloader Middlerware的process_exception 会被继续顺次调用,知道所有的方法都被调用完毕

  • 当返回为Rponse对象时,更低优先级的Downloader Middler的process_exception方法不再被继续调用,每个Downloader Middler
    的 process_response 方法转而被依次调用

  • 当返回Request对象时,更低优先级的Downloader Middlerware的process_exception方法不会继续调用。这个Request会重新放到调度队列里,其实它
    就是一个全新的Request,等待被调度。如果被Scheduler调度了,那么所有的Downloader Middlerware 的process_request 方法会被重新按照顺序执行

实战一

利用 Downloader Middleware 修改请求头的User-Agent

方法一:
直接在settings.py 中加入USER_AGENT 的配置,如

USER_AGENT = 'scrapyhttpbindemo (+http://www.yourdomain.com)'

方法二:

# 1.middlewares.py 中定义如下类
class RandomUserAgentMiddleware(object):
    def __init__(self):
        self.user_agents = [
            'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36',
            'jemter'
        ]

    def process_request(self,request,spider):
        request.headers['User-Agent'] = random.choice(self.user_agents)

# 2. settings.py 中加入配置
DOWNLOADER_MIDDLEWARES = {
   'scrapyhttpbindemo.middlewares.RandomUserAgentMiddleware': 543,
}

输出:

# before:
'''
text: {
  "args": {},
  "data": "{\"age\": \"26\", \"name\": \"wangcai\"}",
  "files": {},
  "form": {},
  "headers": {
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en",
    "Content-Length": "32",
    "Content-Type": "application/json",
    "Host": "www.httpbin.org",
    "User-Agent": "Scrapy/2.7.1 (+https://scrapy.org)",
    "X-Amzn-Trace-Id": "Root=1-63953c12-0558fede42d9a64d5a632a8b"
  },
  "json": {
    "age": "26",
    "name": "wangcai"
  },
  "origin": "120.229.34.25",
  "url": "https://www.httpbin.org/post"
}
'''



# after, 可以看到User-Agent 已经发生变化了:
'''
{
  "args": {},
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "en",
    "Host": "www.httpbin.org",
    "User-Agent": "jemter",
    "X-Amzn-Trace-Id": "Root=1-6395d166-219f615432bf128f1bcac524"
  },
  "origin": "120.229.34.25",
  "url": "https://www.httpbin.org/get"
}

'''

总结:
一般推荐方式一,比较简单。但是想要更加灵活那就需要借助Downloader Middlerware

实战二

利用Downloader Middlerware 修改代理, 加入以下配置即可


#1. middlewares.py:
class ProxyMiddleware(object):

    def process_request(self,request, spider):
        request.meta['proxy'] = 'http://127.0.0.1:8888'

#2.settings.py:
DOWNLOADER_MIDDLEWARES = {
   'scrapyhttpbindemo.middlewares.RandomUserAgentMiddleware': 543,
    'scrapyhttpbindemo.middlewares.ProxyMiddleware': 544
}

process_request 返回request

class ProxyMiddleware(object):
    def process_request(self, request, spider):
        request.meta['proxy'] = 'http://127.0.0.1:8888'
        return request  # 造成重复循环调用

out: RecursionError: maximum recursion depth exceeded

该请求被返回后,后续的process_request 就不在执行,这个requst会直接
发送给Engine并加回到Scheduler,等待下一次被调度。因为只有一个requst请求,
所以下一次被调度的还是该request,依次造成递归

process_request 返回response

class ProxyMiddleware(object):
    # def process_request(self, request, spider):
    #     request.meta['proxy'] = 'http://127.0.0.1:8888'
    #     return request

    def process_request(self, request, spider):
        return HtmlResponse(url=request.url,status=200, encoding='utf-8',body='Test Downloader Middler')

out: 返回 test: Test Downloader Middler

可以看到原本 request 应该请求 https://www.httpbin.org/get 得到返回结果,但是这里Response的内容直接变成了我们所定义的
HtmlResponse 的内容,丢弃了原来的Request。因此如果我们在原来process_request 方法中直接返回Request 对象,原先的Request
就会被直接丢弃,该Resquest 经过process_response 方法处理后会直接传递给Spider解析

利用process_response 修改response

#1. middlerwares.py
class ChangeResponseMiddlerware:
    def process_response(self,request,response,spider):
        response.status = 201
        return response
#2. 修改settings.py 配置
#3. parse方法中加入
print('返回 response.status:', response.status)

输出:

返回 response.test:  {                                                                                                                                   
  "args": {},                                                                                                                                            
  "headers": {                                                                                                                                           
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",                                                                         
    "Accept-Encoding": "gzip, deflate",                                                                                                                  
    "Accept-Language": "en",                                                                                                                             
    "Host": "www.httpbin.org",                                                                                                                           
    "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Mobile Safari/537.36", 
    "X-Amzn-Trace-Id": "Root=1-6399da51-11db07d669ae4d5577046040"                                                                                        
  },
  "origin": "120.235.172.81",
  "url": "https://www.httpbin.org/get"
}

返回 response.status:  201

可以看到 Response 对象的状态码已经被修改了

posted @ 2022-12-11 20:54  chuangzhou  阅读(160)  评论(0编辑  收藏  举报