在编写爬虫的过程中我们经常会遇到需要代理的情况,代理可以到网上找免费的也可以用付费的。付费的使用网站提供的API就可以轻松获取代理,免费的就只能到处找然后采集而且代理质量还不高(付费的也不一定好)。但是喜欢动手的我还是更偏向后者,即自己找代理,搭建起一个代理池然后提供API给爬虫使用。
在我准备动手搭建的时候我发现Github上已经有一个优秀的代理池项目了proxy_pool。试用了一下感觉不错然后就看了下源码,读源码的过程中发现这个项目有很多值得学习的地方,于是决定仿照它写一个,这样既能把代理池搭建起来也能学到知识,于是便有了ProxyPool。
项目通过爬虫方式持续抓取代理网站公布的免费代理IP,实时校验,维护部分可以使用的代理,并通过api的形式提供外部使用
包含ProxyManager和ProxyGetter,ProxyManager用于在DB和Api间进行数据传输。ProxyGetter用于获取代理,目前有4个免费代理源,每调用一次就会抓取这个4个网站的最新代理放入DB,支持自定义扩展额外的代理获取函数
计划任务,定时去检测DB中的代理可用性,删除不可用的代理。同时也会主动通过ProxyGetter去获取最新代理放入DB;
api接口相关代码,目前api是由Flask实现,代码也非常简单。客户端请求传给Flask,Flask调用ProxyManager中的实现,包括get/get_all/get_status
数据库相关代码,目前数据库支持Redis。代码用工厂模式实现,方便日后扩展其他类型数据库
ProxyManager:get/refresh/get_all/get_status等接口的具体实现类,负责管理proxyProxyGetter:获取代理的类,支持自己扩展获取代理接口
存放一些公共的类或函数,包含Config:读取配置文件config.ini的类,Singleton:实现单例,LazyProperty:实现类属性惰性计算。等等
配置文件:Config.ini数据库配置和代理获取接口配置,在ProxyGetter中添加新的代理获取方法,需在Config.ini中注册方可使用
项目中会涉及大量的配置信息,为了便于管理我们应该将配置信息统一存放并且设计一个类进行统一管理。
配置文件采用ini文件,Python内置的ConfigParser类可以解析ini文件,我们只需继承内置的ConfigParser类即可定制配置解析类
到这里我们完成了简单的通用的获取配置信息的类以及外部接口,当然了现在类是不完善的,往下的内容涉及的类如无特殊说明都是未完善的类,但是到本篇慢慢结束时都会是完善的。毕竟我当初开始仿照时就是一步步由不完善到完善的。
数据库主要有两个表useful和raw,raw存放刚采集的proxy,useful存放验证有效的proxy
数据库相对来说也比较简单只要放出进行数据存取的接口就行。在这里主要考虑一个问题:数据库扩展
所以我们应该设计一个工厂类,和特定数据库类。特定数据库类完成数据存储的真正工作并放出接口。工厂类根据配置文件使用特定的数据库类,工厂类只放出接口并不实现,全部调用指定的数据库类的接口
运行ProxyApi.py,浏览器输入127.0.0.1:/get可以获取到预先存入的数据,说明外部接口,数据库,配置获取等都正常工作。
我们要对多个不同的网站进行采集,而每个网站的数据处理方式又不一样,所以针对每个网站设计一个函数进行采集。为了方便管理,这些采集函数应该由一个类进行统一管理,即采集函数作为类的静态函数。
考虑到采集函数会有一些共同点,例如获取页面,所以设计一些公共函数来处理通用的事物,这里暂时只考虑获取页面的公共函数
对于获取页面的公共函数它涉及到一些需要定制的情况,例如某些网站可能对于请求头有特殊的要求或者需要某些特殊的参数等等,所以公共函数要考虑可定制性。
前面我们已经完成了代理采集,代理存储和外部接口,现在它们都是独立工作的,为了把它们联系起来创建一个代理管理类:
运行ProxyPool/Proxy/ProxyManager.py,根据输出可以知道代理部分已经整合并且正常工作
运行ProxyPool/Api/ProxyApi.py,在浏览器中访问可以得到数据,说明代理部分和外部接口整合成功。
现在我们需要项目能够定时获取代理到raw中,并定时将raw中的可用代理放入useful中。
首先我们要确保获取到的新代理格式正确再放入raw中,然后要确保raw中取出来的代理可用才放入到useful中,为此增加两个公共函数。
上面已经完成代理格式和可用性的检查,也完成了代理的获取和放入raw中,现在可以设计调度类让以上工作定期执行同时将raw中可用代理放入useful
现在项目每隔一分钟就会获取新代理存入raw,然后检查raw中的代理将可用的代理存入useful。
代理都是有时效性的,也许放进useful的时候还能用但过了几秒它就失效了,所以要定期的检查useful中的代理,将不可用的剔除。
对于useful的检查和调度检查类你可能会觉得奇怪,ProxyCheck的run方法不是死循环吗?还有调度类为什么不像ProxyRefreshSchedule那样使用BlockingSchedule?
首先解释为什么不用BlockingSchedule,ProxyRefreshSchedule是获取代理后才立即执行的,代理只需要隔一段时间获取一次就好所以代理获取的功能不能和代理检查整合到一块,不然每个检查线程执行的时候都会执行一次代理获取,这显然不是我们需要的。这时我们就需要一个调度器隔一段时间进行一次代理获取然后创建多个线程对获取的代理进行检查。回到useful的检查,因为检查只取决于useful中存在的代理,所以没必要用BlockingSchedule。
然后是死循环问题,没错那就是一个死循环。你也许会想既然是死循环那么在useful的代理耗尽之前检查不是不会退出么?那其他工作就没法继续了啊?其实只要raw代理的刷新调度在useful的代理刷新调度之前启动就没问题,因为raw调度是定期执行的,useful调度执行过程中如果raw调度的时间到了会切换到raw调度执行,raw调度执行完才切换回useful调度。
为什么要设计成死循环?因为useful中的代理随时可能失效,所以检查应该时刻进行着,这样才能及时把失效代理剔除。
到这里项目已经可以运行了,只需要将Api,raw和useful调度的主文件分别执行即可。
上一节的内容完成后其实项目已经可以运行,但是文件分散运行起来不方便,所以将启动环节整合一下。
运行ProxyPool/Run/main.py即可启动项目。但是启动后好像有哪里不对???
单例模式是设计模式的一种,简单将就是类在整个运行过程中只有一个实例,这对某些资源的统一管理以及节省内存有很大作用
考虑一下需要单例的类,配置管理类Util/ConfigGetter,数据库工厂类Db/DbClient,代理管理类Proxy/ProxyManager好像也行?
这里其实可以排除ProxyManager,因为Schedule/ProxyRefreshSchedule是继承自它的,如果ProxyManager是单例的,那么ProxyRefreshSchedule就无法多线程进行刷新。
项目中延迟绑定主要用于配置管理类Util/ConfigGetter,将ConfigGetter中的@property都换成@LazyProperty即可
回想之前的数据库操作,尤其是插入操作我们需要依赖插入函数的默认函数或者直接传入参数来将代理插入到指定的表中,这样其实不太规范也不方便使用,所以给数据库相关类增加个改变表的函数
还有就是连接数据库的时候也没有指定数据库的功能,这也要加上。当然具体是否指定,怎样指定这些都要根据不同的数据库的实际来做,项目中的数据库是Redis,也许会和其他数据库操作不同
特定数据库类,项目中使用Redis且指定数据库为
0,所以Config.ini中的Name实际不起作用,但也可先占个位
特定数据库类中增加change_table后改动较多,主要就是原来依赖于默认参数或者手动指定参数确定表的地方全部改为使用self.table,而self.table的改变由change_table完成
日志一般来说都会保存在.log文件中,但是如果所有日志都保存在一个文件那么时间长了文件就会很大,难以阅读,所以日志文件应该每隔一段时间就新建一个,然后到了一定的时间还要删除太旧的日志
为了满足以上对日志类的要求,我们需要继承内置的logging.Logger并定制自己的日志类
在“项目初运行”那一章的末尾提到“好像有哪里不对”,其实项目确实有个bug,那就是useful检查的时候ProxyCheck不能按照调度定时执行检查。
注意到run函数只在进入while循环之前做了一个改变表的动作,这时表切换到useful中,代码正确执行。但是当ProxyRefreshSchedule开始执行的时候表会切换到raw并且raw中的代理将消耗完,当ProxyRefreshSchedule执行完后表还是raw并且raw中无数据,这时切换到ProxyCheck,它检查的就是raw中的代理,由于raw中无代理所以pop不出数据,然后ProxyCheck就会睡眠。然后又到ProxyRefreshSchedule执行,执行完又到ProxyCheck,过程中表一直是raw,所以ProxyCheck将陷入无尽的睡眠