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 对象的状态码已经被修改了
本文来自博客园,作者:chuangzhou,转载请注明原文链接:https://www.cnblogs.com/czzz/p/16974435.html