“
阅读本文大概需要 3 分钟。
”当我们从一些代理 IP 供应商购买代理 IP 时,他们可能是提供一个网址供我们查询当前可用的代理 IP。我们周期性访问这个网址,拿到最新的 IP,再分给爬虫使用。
最正确的做法,是单独有一个代理池程序,它负责请求这个网址,获取所有的代理 IP,然后维护到一个池子里面。爬虫只需要从这个池子里面拿就可以了。
但有时候,因为某些原因,我们可能暂时无法或者暂时没有时间开发代理池程序,于是不得不直接让爬虫去请求代理 IP 供应商提供的网址获取代理 IP。这个时候我们就面临了一个问题——爬虫应该怎么去请求代理网址?特别是当你使用的是 Scrapy,那么这个问题变得尤为麻烦。
我们一般在 Scrapy 的下载器中间件里面设置爬虫的代理,但问题来了,在下载器中间件里面,你怎么发起网络请求?
于是很多人退而求其次,在下载器中间件里面,用 requests 来请求代理供应商的网址,例如:
import requests
import json
import random
class ProxyMiddleware:
def process_request(self, request, spider):
ip_info = requests.get('代理供应商的网址').json()
ip_list = ip_info['proxy']
ip = random.choice(ip_list)
request.meta['proxy'] = ip
我们知道,Scrapy 是一个异步爬虫框架,而 requests 是一个同步网络库。在 Scrapy 里面运行 requests,会在 requests 等待请求的时候卡死整个 Scrapy 所有请求,从而拖慢整个爬虫的运行效率。
当然,你可以在 Scrapy 的爬虫里面,每次发起待爬请求前,先yield scrapy.Request('代理供应商网址')
,请求一次代理供应商的网址,并在对应的回调函数里面拿到代理IP再发正常的请求。但这样的写法,会让爬虫代码变得很混乱。
为了避免这种混乱,在下载器中间件里面获取代理 IP 当然是最好的,但又不能用 requests,应该如何是好呢?
实际上,我们可以在 Scrapy 里面,使用 aiohttp,这样既能拿到代理IP,又能不阻塞整个爬虫。
Scrapy 现在官方已经部分支持 asyncio 异步框架了,所以我们可以直接使用async def
重新定义下载器中间件,并在里面使用 aiohttp 发起网络请求。
为了说明如何编写代码,我们用 Scrapy 创建一个示例爬虫。正常情况下,这个爬虫使用 5 个并发,每个请求延迟1秒访问http://exercise.kingname.info/exercise_middleware_ip/<page>
并打印网站返回的结果,如下图所示。
请求频率和延迟如下图所示:
请求频率接近1秒钟一次。
现在,我们创建一个中间件,在这个中间件里面,使用 requests 请求一个需要延迟 5 秒钟才会返回的网址:
启动这个中间件,可以看到爬虫的速度明显变慢,几乎每 5 秒才能有一次返回,如下图所示:
从图中可以知道,requests 卡住了整个 Scrapy。在请求这个延迟 5 秒的网址时,Scrapy 无法发起其他的请求。
现在,我们把 requests 替换为 aiohttp,看看效果。
import asyncio
import aiohttp
class TestAiohttp:
async def get_ip(self):
async with aiohttp.ClientSession() as client:
resp = await client.get('http://httpbin.org/delay/5')
result = await resp.json()
print(result)
async def process_request(self, request, spider):
print('请求一个延迟5秒的网址开始')
await asyncio.create_task(self.get_ip())
如下图所示:
现在,我们直接运行这个爬虫,理论上应该会遇到一个报错,如下图所示:
这是正常现象,要在 Scrapy 里面启用 asyncio,需要额外在settings.py
文件中,添加一行配置:
TWISTED_REACTOR = 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'
添加了这一行配置以后,再次运行爬虫,可以看到效果如下图所示:
刚刚启动的时候,爬虫会瞬间启动 5 个并发,所以会同时打印出请求一个延迟5秒的网址开始
5次。然后稍稍停 5 秒,这 5 个请求几乎同时完成,于是同时打印出这个延迟网址的返回信息。接下来,后面的请求就是每秒一个。
这说明,Scrapy 的异步机制成功启动了。首先第一个请求延迟网址发起以后,由于当前请求数还没有达到最大并发 5,所以立刻就会利用这个等待时间发起第二个请求。由于现在请求数还不够 5 个,于是马上又会发起第三个请求,直到凑够 5 个并发请求为止。
当第一个请求延迟网站返回以后,Scrapy 去请求正式的第一页。在等待第一页返回的过程中,第二个延迟请求完成并返回,于是 Scrapy 去请求正式网址的第二页……
总之,从 Scrapy 打印出的信息可以看出,现在 Scrapy 与 aiohttp 协同工作,异步机制正常运转。
推荐阅读
1
2
3
在GitHub上很火的996.ICU,现如今到底怎么样了?结果有些出乎意外!
4
崔庆才
静觅博客博主,《Python3网络爬虫开发实战》作者
隐形字
个人公众号:进击的Coder
长按识别二维码关注
好文和朋友一起看~