给自己的爬虫做一个简单的动态代理池

网络爬虫

浏览数:304

2019-2-3

AD:资源代下载服务

使用代理服务器一直是爬虫防BAN最有效的手段,但网上的免费代理往往质量很低,大部分代理完全不能使用,剩下能用的代理很多也只有几分钟的寿命,没法直接用到爬虫项目中。
下面简单记录一下我用scrapy+redis实现动态代理池的过程。

我对“动态代理池” 的需求

我的爬虫项目需要7*24小时监控若干个页面,考虑了一下希望代理池能满足下面几个要求:

  • 始终保持一个相对稳定的代理数量
  • 始终保持池内代理的高可靠率(希望90%的代理都能用)
  • 尽可能减少对爬虫项目代码的更改

参考过的项目

找过一些现成的轮子,但发现或多或少都不太符合我的需求

kohn/HttpProxyMiddleware
一个“被动”选择代理的方式。在scrapy middleware中加入大量代码,让爬虫在代理失效后再去代理网站找代理,会有下面几个问题:

  • 大多数情况下始终使用一个代理去访问,造成流量集中在一个IP上,可能导致代理IP被BAN。
  • 切换代理的过程比较耗时,导致爬虫性能下降比较厉害
  • 所有逻辑都嵌套在了爬虫项目里面,有兼容性和可移植性方面的问题(有多个爬虫的话每个都要改一遍..)
  • 基于python2写的,我的项目在python3上..

jhao104/proxy_pool
这个几乎和我想要的一样,但还是因为下面几点原因没有使用:

  • 基于SSDB,这边没有现成的SSDB,因为已经在用Redis了不想专门去装一个
  • 只管定时收集代理,在收集过程中做一次验证。但事实上收集到的代理即便通过了验证,也可能活不过10分钟,时间一长代理池内就会有很多过期的代理,需要在爬虫代码中处理这些过期代理(爬虫代码调用delete删除)。

设计思路和代码

看了那么多方案,我就在想:能不能让代理池具有自我检查自我修复的功能?这样我们的爬虫就只需随机拿一个代理用就可以了,就算恰巧拿到一个失效代理,只要做一次retry,代理池的修复机制可以保证retry的时候失效的代理已经被移走了,不会再次取到。

简单规划一下:

  • 我们需要一个自检程序,它每10S跑一次,保证代理池内所有代理至少在10S内有效
  • 我们需要一个代理获取的程序,当代理池内代理数量过低时(阈值定为<5),访问免费代理网站补充新代理
  • 我们需要一个调度程序,用来监控代理池数量并调用上面两个程序,维持代理池平衡。
  • 作为私有代理池就维护在redis了(SET),让爬虫直接从redis里取代理。

对应的代码就有这么三个部分:

  • 一个scrapy爬虫去爬代理网站,获取免费代理,验证后入库 (proxy_fetch)
  • 一个scrapy爬虫把代理池内的代理全部验证一遍,若验证失败就从代理池内删除 (proxy_check)
  • 一个调度程序用于管理上面两个爬虫 (start.py)

强行画个流程图:

爬虫部分全部用scrapy写了,其实没必要,用requests就够了..但做的时候刚学scrapy,就顺便练练手了..
start.py的调度方式比较粗暴,直接起两个线程,在线程内用os.system调用scrapy爬虫(毕竟轻量级代理池嘛..好吧其实就是我懒图个方便..)

另外还有几个调度策略需要说一下:

  • 每次调用proxy_fetch后会自动设定一个“保护时间”10分钟,在保护期内除非代理池只剩一个代理了,否则不会触发proxy_fetch,避免频繁调用。
  • 加入了一个“刷新时间”24小时,保证每24小时内至少执行proxy_fetch一次
  • 验证程序设定timeout为5秒,5秒内没访问到测试页面就认为验证失败

其他细节可以看源码:arthurmmm/hq-proxies

部署和使用

需要先改一下配置文件hq-proxies.yml,把Redis的地址密码之类的填上,改完后放到/etc/hq-proxies.yml下。
在配置文件中也可以调整相应的阈值和免费代理源和测试页面。
测试页面需要频繁访问,为了节省流量我在某云存储上丢了个helloworld的文本当测试页面了,云存储有流量限制建议大家换掉。。验证方式很粗暴,比较一下网页开头字符串。。

另外写了个Dockerfile可以直接部署到Docker上(python3用的是Daocloud的镜像),跑容器的时候记得把hq-proxies.yml映射到容器/etc/hq-proxies.yml下。
手工部署的话跑pip install -r requirements.txt安装依赖包

在scrapy中使用代理池的只需要添加一个middleware,每次爬取时从redis SET里用srandmember随机获取一个代理使用,代理失效和一般的请求超时一样retry,代理池的自检特性保证了我们retry时候再次拿到失效代理的概率很低。middleware代码示例:

class DynamicProxyMiddleware(object):
    def process_request(self, request, spider):
        redis_db = StrictRedis(
            host=LOCAL_CONFIG['REDIS_HOST'], 
            port=LOCAL_CONFIG['REDIS_PORT'], 
            password=LOCAL_CONFIG['REDIS_PASSWORD'],
            db=LOCAL_CONFIG['REDIS_DB']
        ) 
        proxy = redis_db.sismember(PROXY_SET, proxy):
        logger.debug('使用代理[%s]访问[%s]' % (proxy, request.url))
        request.meta['proxy'] = proxy

在其他爬虫框架下使用也是类似的。

最后放一张萌萌哒日志菌触发proxy_fetch时候的截图:

原文地址:https://www.jianshu.com/p/6cd4f1876b31