关于爬虫 retry 机制的思考
背景:
最近在爬某网站,发现其反爬机制是这样的:
如果一段时间访问次数比较多,就会禁止访问几分钟,然后恢复正常。
对于爬虫端,这其实很难针对写:
发生禁止访问时,爬虫并不知道这是突发性错误(临时网络挂了,过几秒可能就好了),还是资源本身不能访问(永远会 fail),还是网站反爬机制起作用了。
如果能检测反爬虫机制是否生效,倒是很好解决这个问题,但是这不好写(不同网站机制不一样)。
假设我们不能检测反爬虫机制,有没有什么通用的算法策略呢?
如果我们只采用 retry 的方法:
比如说 retry 3 次,但是反爬机制生效的这段时间,怎么都是访问不了的,所以会错过资源。
如果我们采用 sleep + retry 的方法,有 2 个问题:
- 不知道要 sleep 多长时间
- 如果对每个访问不了资源都 sleep 再 retry,那么每个本身不能访问的资源都会浪费 sleep 时间 * retry 次数
策略:
下面介绍一个我想到的算法策略,可以适配于并发的爬虫程序,效率也比较高:
我们维护一个队列 Q:队列存访问失败的资源,和尝试过的次数。
假设我们有一个爬虫的协程池(并发池),
当某个爬虫访问一个资源失败时,加入队列 Q。
当某个爬虫访问一个资源成功时,挂起协程池(访问完当前资源就清空)。然后 retry 一轮队列 Q。
一轮 retry :
某个资源成功了,pop。
如果队列 Q 里的 head 资源访问次数达到上限,pop。
爬虫任务结束时,如果 Q 不为空,一轮一轮 retry Q,直到 Q 为空。
retry 每一轮之间设置一个间隔(比如说 2 分钟,可以观察之前 fail 到 success 的间隔来设置)
策略需求:
没有 retry 机制时,连续 fail 长度 + 协程池大小 < 连续 success 长度
不然一轮 retry 到中间时,又被反爬了,就会导致队列 Q 的 tail 部分直接尝试次数 + 1。
代码参考:
Codeforces 的 blog 爬虫:
https://github.com/coldchair/Codeforces_blog_crawler