process_request
在request对象传往downloader的过程中调用。当返回不同类型的值的时候,行为也不一样:
返回值 | 行为 |
---|---|
None | 一切正常,继续执行其他的中间件链 |
Response | 停止调用其他process_request和process_exception函数,也不再继续下载该请求,然后走调用process_response的流程 |
Request | 不再继续调用其他process_request函数,交由调度器重新安排下载。 |
IgnoreRequest | process_exception函数会被调用,如果没有此方法,则request.errback会被调用,如果errback也没有,则此异常会被忽略,甚至连日志都没有。 |
process_response
在将下载结果返回给engine过程中被调用
返回值 | 行为 |
---|---|
Response | 续续调用其他中间件的process_response |
Request | 不再继续调用其他process_request函数,交由调度器重新安排下载。 |
IgnoreRequest | 则request.errback会被调用,如果errback也没有,则此异常会被忽略,甚至连日志都没有。 |
process_exception
在下载过程中出现异常,或者在process_request中抛出IgnoreRequest异常的时候调用。
返回值 | 行为 |
---|---|
Response | 开始中间件链的process_response处理流程 |
Request | 不再继续调用其他process_request函数,交由调度器重新安排下载。 |
None | 继续调用其他中间件里的process_exception函数 |
from_crawler(cls, crawler)
如果存在该函数,则调用该函数创建中间件的实例。如果要写这个函数,一定要返回一个中间件的对象。
循环动态代理
settings
这是一个代理IP集合
PROXIES = [
# "http://54.243.170.209:8080",
"http://165.225.210.96:10605"
]
# 749是有讲究的,因为系统的默认代理中间件是750,我们自定义的要运行在默认的前面,而系统会先运行数字小的中间件
# 而为了防止影响其他的中间件,所以紧挨着750就可以了
DOWNLOADER_MIDDLEWARES = {
'qianmu.middlewares.RandomProxyMiddleware': 749,
}
middlewares
定义一个类创建一个中间件实例
class RandomProxyMiddleware(object):
def __init__(self, settings):
# 初始化变量和配置
self.proxies = settings.getlist("PROXIES")
self.state = defaultdict(int)
self.max_failed = 3
@classmethod
def from_crawler(cls, crawler):
# 1.创建中间件对象
if not crawler.settings.getbool("HTTPPROXY_ENABLED"):
raise NotConfigured
return cls(crawler.settings)
def process_request(self, request, spider):
# 3.为每个request对象分配一个随机的ip代理
if self.proxies and not request.meta.get("proxy"):
request.meta["proxy"] = random.choice(self.proxies)
def process_response(self, request, response, spider):
# 4.请求成功,调用process_response
cur_proxy = request.meta.get("proxy")
# 判断是否被对方封禁
if response.status in (401, 403):
print(f"{cur_proxy} got wrong code {self.state[cur_proxy]} times")
# 给相应的IP失败次数+1
self.state[cur_proxy] += 1
# 当某个IP的失败次数累计到一定数量
if self.state[cur_proxy] >= self.max_failed:
print("got wrong http code {%s} when use %s" % (response.status, request.get("proxy")))
# 可以认为该IP已经被对方封禁了,从代理池中将该IP删除
self.remove_proxy(cur_proxy)
del request.meta["proxy"]
# 重新请求重新安排调度下载
return request
return response
def process_exception(self, request, exception, spider):
# 4.请求失败,调用process_exception
cur_proxy = request.meta.get("proxy")
# 如果本次请求使用了代理,并且网络请求报错,认为该IP出现问题了
if cur_proxy and isinstance(exception, (ConnectionRefusedError, TimeoutError)):
print(f"error occur where use proxy {exception} {cur_proxy}")
self.remove_proxy(cur_proxy)
del request.meta["proxy"]
return request
def remove_proxy(self, proxy):
"""在代理IP列表中删除指定代理"""
if proxy in self.proxies:
self.proxies.remove(proxy)
print(f"remove {proxy} from proxy list")
内置中间件
-
scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware
请求robots.txt文件,并解析其中的规则。
-
scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware
执行带Basic-auth验证的请求
-
scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware
下载请求超时最大时长
-
scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware
设置默认的请求头信息
-
scrapy.downloadermiddlewares.useragent.UserAgentMiddleware
设置请求头信息里的User-Agent
-
scrapy.downloadermiddlewares.retry.RetryMiddleware
如果下载失败,是否重试,重试几次
-
scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware
实现Meta标签重定向
-
scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware
实现压缩内容的解析(比如gzip)
-
scrapy.downloadermiddlewares.redirect.RedirectMiddleware
实现30x的HTTP code的重定向
-
scrapy.downloadermiddlewares.cookies.CookiesMiddleware
实现对cookies的设置管理
-
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware
实现IP代理
-
scrapy.downloadermiddlewares.stats.DownloaderStats
下载信息的统计
-
scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware
下载结果的缓存