前一个版本的微博爬虫每发送一个HTTP请求就需要等待若干秒,模拟人类操作,避免引起服务器的注意,以至于每个请求平均耗时高达3秒。为了防止被服务器封禁,显然应该使用代理,伪装自己HTTP请求的来源。至于如何获取代理,如何使用这些代理,我进行了一些思考与探索,并完善上个版本的微博爬虫工具包,完成了新版的微博爬虫。
我的第一个思路是,找到提供大量免费代理的网站(自己找,本文不点明),爬取代理,然后用多线程的方式成组发送请求。虽然获取代理的过程很顺利,但是我很快发现了这个思路的一些问题。
首先,成组多线程并发请求的话,下一页链接没法使用同一个序列,即可能需要先获取页数然后做页码区间划分,再把每个区间分配给不同的代理。可是如果某个代理中途失效了怎么办?就算不考虑这种情况,理论上,每组请求之间还是需要加平均3秒的延迟,实际上,受IO上限的影响,16线程的成组请求,等到全部响应完大概需要10+秒,这样一来组间延迟省掉也无所谓,可是即使在只使用优质代理的情况下每次请求的均摊时间开销也要1.+秒,虽然效率比不使用代理有所提高,但是我仍然不满意。
随后我放弃了对多线程并发的执念,提出并完善了一个新思路——单线程爬虫+带时间戳的代理池+代理竞争。
先解释单线程爬虫,有了代理池其实已经基本不用担心代理失效的问题,但是为了实现方便,我还是暂且选择了单线程爬虫。下一个版本也许会在整个爬虫外面套一层并发,也许会考虑使用协程(coroutine)技术。
接下来是带时间戳的代理池和代理竞争。我们知道,每一个代理在发出一个请求后需要等待一个平均3秒的随机时间后才可以再次发送请求。那么,如果我们使用过一个代理之后就暂时给它上锁(实际上是打上一个时间戳),到时间后(当前时间的时间戳与代理时间戳差值超过随机延时)则给它解锁就可以解决单个代理的使用问题。同时,我们在所有代理之间轮询寻找没上锁的代理来使用,而每次请求的响应时间会积累下来,考虑到使用代理的请求响应时间比较长,顶多五六个代理之后旧代理就已经解锁了。而实际上,我的代理池维护的代理数通常不会少于6。即使代理全都上了锁,我也可以等待1秒后重新轮询。于是,只要保证代理池足够大,同时维护代理池中代理的质量,我们就可以实现连续发送请求,事实上,经过实验,代理响应时间受代理质量影响有所浮动,平均请求周期最长达到过6/7秒,最短达到过5/3秒。
至于维护代理池的方法,我从爬虫进程fork了一个守护进程用来更新代理池。定时(暂定30秒)爬取代理网站某页面上的100个代理,然后用多线程(暂定16线程)测试每个代理的响应时间,过滤掉响应太慢(暂定超时时间为3秒)的代理,用同样的方法过滤代理池中不在新代理列表中的代理,最后合并代理池和新代理列表,同时为新代理打上很小的时间戳(实际上是0),已有的代理时间戳不变。这样可以在代理损失的过程中不断扩充代理,使代理池的规模稳定甚至递增(经过实验,基本能使代理池大小保持稳定,没有出现代理池暴增的情况,暂时不对代理池大小加以限制)。
关于新思路的实现,我在重构weibo-crawler后新建了weibo-crawler2项目,按惯例已挂git。下面是一些weibo-crawler2的使用截图:
悲报,weibo.cn登录需要验证码了。刚开始验证码还比较弱,我正想着过完年要不要用深度学习破了它,然后验证码就变得更变态了…模拟登录模块报废。