自己曾经搭建过一个通用化的爬虫平台,这个通用化爬虫平台主要包含6大服务: 1、规则配置服务;2、爬虫采集服务;3、代理采集服务;4、动态页面渲染服务(渲染JS加载的页面);5、数据清洗服务;6、数据推送和输出服务;每个服务工作纯粹,各司其职,方便开发和维护。
这次主要说一下代理服务(也就是你说的代理IP池)是如何实现的:爬虫代理服务主要分为付费代理和免费代理:1、付费代理:现在有很多卖代理的网站,这些网站的代理IP来源主要是:a.网上采集免费代理b.自己买云服务器自己搭建对于第一种方式,我们自己也可以无成本做到,下面说如何完成。第二种方式,则需要花费一定成本,当然,这种类型的代理相对第一种比较稳定,如果是要购买的话,建议购买这种代理。
2、免费代理:这种方式就是自己写个程序,到网上采集免费的代理IP下来,供自己使用。采集的过程无非是:程序访问代理页面 --> 正则/XPATH匹配出IP --> 保存到本地
理清楚了思路之后,程序当然很好写出来。但是在这个过程中,会遇到一些问题。例如:在哪里寻找代理网站?采集到的代理质量如何?能用多久?如果代理都失效,如何持续不断采集代理?代理是否可做成服务化,让代理变得更通用?
下面一一解决这些问题:a.在哪里寻找代理网站?很简单,作为程序员,最基本的技能就是使用搜索引擎,输入关键字 免费代理IP,就会看到很多的代理IP网站打开看一下,每个网站代理IP少则几十个,多则几百个。OK,写个代理抓取程序,把这些网站的代理抓下来。
b.采集到的代理质量如何?能用多久?这么多IP,难道别人真的就免费送给你了么?当然不是,这些代理中,有很大一部分已经失效了。如何测试这些代理是否可用?写一个代理测试程序,挂上这些代理,访问某一个稳定的网站,看是否可以正常访问。可以正常访问则这个代理就是可用的,不能访问则认为是失效的。当然,这里用多线程或异步的方式更高效。通过这个代理测试程序,你就能获取到最近可用的代理列表了。
c.如果代理都失效,如何持续不断采集代理?通过上面的 代理采集程序 和 代理测试程序两个配合,定时启动采集程序,然后把这些代理写到数据库中,同时,定时启动代理测试程序,代理可用就打上可用标记,在这个测试程序中,你还可以记录下测试访问的响应时间,存入数据库。如果代理网站上有显示代理的协议、代理类型(透明、普通匿名、高匿名)、代理位置、运营商,那就一并存入库中,这样你后期筛选代理,不就多了更多维度吗?(按测试时间排序查询、按类型查询、按运营商查询等等)只要这2个程序定时启动间隔控制好,就能持续输出可用代理。
d.代理是否可做成服务化,让代理变得更通用?现在有了这么多代理,你应该知道如何使用吧?爬虫挂上代理,采集页面即可。但是,有一种更好的方式使用代理,就是把代理做成服务化。使用squid的cache_peer机制,把这些代理按照一定格式(具体格式参考文档)写入到配置文件中,配置好squid的端口,那么squid就可以帮你调度代理了,而且还可以摒弃失效的代理。这么做的好处是,你的爬虫程序只需要指定一个代理:squid的端口就可以了!
详细可参考我的专栏文章:如何搭建一个爬虫代理服务?
花了大概两个多月的时间集中实现了一个分布式的代理IP池,目标是为大型分布式爬虫提供高可用低延迟的代理,它是开源免费的。实现过程中有一些感想,虽然这个问题的日志是16年的,但是还是忍不住来班门弄斧和各个大佬交流交流。
Kaito的答案给了我很大的启发。在haipproxy实现的前期,很大部分功能就是根据他的回答一步步实现的。站在巨人的肩膀上,也有一些自己的想法。比如代理IP来源,除了百度和谷歌之外,另外两个很重要的来源是同类项目的参考和代理IP站点的友链,它们也是一个很重要的来源。haipproxy算上墙内墙外的代理IP源,累积数量有30+。
当IP源多了,如何落实到编码上来呢?如果设计得不好,那么光是代理IP抓取就要写很多代码,完全变成了体力活儿。haipproxy实现代理IP抓取的思路是:将页面结构类似的网站抽象出共性,然后编写规则以复用代码。因为很多网站还是用的 table 这种形式布局,所以代码复用会很容易。然后针对动态抓取的网站,采用 scrapy-splash进行渲染,再抽象出共性以复用代码。
在代理抓取代码完了之后,由于IP源网站会每隔一段时间更新代理,因此我们需要定时抓取。定时任务也有一定的讲究,一般不同网站的IP源更新间隔不同,因此最好的方式是让定时任务和对应网站的更新间隔保持一致,这样的好处有:(1)不会漏掉每次更新的代理源 (2)不会因为频繁访问网站对其正常运行造成太大的压力 (3)当服务器资源有限的时候,也不会出现任务大量堆积的情况。
通过上述过程,我们已经可以在30分钟以内采集到成千上上万的代理IP了。但是我们都知道,免费的代理IP可用性很低。如何在资源高度不可用的情况下获取到质量还算不错的代理IP本身就是一件很有挑战和意思的事情。haipproxy的做法和Kaito提到的做法类似,它会对Redis中的代理IP进行定时校验,校验的维度有三个:(1)是否匿名(2)稳定性(3)响应速度。是否匿名可以通过访问一个自己搭建的Web验证服务器来实现,为了减轻校验压力,haipproxy在解析代理网站页面的时候,就会丢弃掉大部分透明IP。为了确保真实IP的匿名性,做了是否匿名的再校验。稳定性可以通过一定的算法计算得到,由于篇幅原因,这里不写细节了,有兴趣的可以查看这篇文章。响应速度也是可以通过编写一个profilemiddleware中间件来获取到。
到上一步,我们完成了一个代理IP从被抓取到入库的首次校验。部分朋友应该也知道,同一个代理IP对应不同网站,它的代理效果可能完全不同。因此,有必要维护特定站点的校验器。那么校验器的细节应该怎么实现呢?大致思路有:(1)如果通过代理IP返回的是非Timeout的错误,那么十有八九该代理服务器的相关端口被关了,这时候应该直接舍弃掉该代理,而不能再去校验它。如果是Timeout错误,那么对其进行减分等下一轮定时任务再对其进行校验(2)校验的网站应该是特定站点的一个稳定的页面。通过返回的内容来判断该IP是否可用。比如知乎、微博这类 站点,如果该代理IP本来就被它屏蔽了,但是网站仍然会响应200状态码。同样地,我们需要每次校验都记录或者更新对应代理的响应速度和最近校验时间。
通过上一步,我们已经完成了特定的校验器了。这时候我们只需要编写特定开发语言的客户端代码来获取代理IP了。Kaito所提的squid来做二级代理这个方法是有启发性和可行性的,haipproxy对其进行了实现。但是深入使用下来,发现了几个问题:(1)squid可能成为请求瓶颈(2)squid对代理IP的具体使用情况并不会有感知和回馈。因此,haipproxy目前实现了基于Python的代理调用客户端py_cli。客户端采用了一些策略来保证代理IP的高可用和低延迟,具体做法如下:
根据配置从存储代理分数、代理速度和最近验证时间的队列中选出满足配置参数需求的代理再对其求交集,这样可以对上述的各个标准做合理的保证。在上述挑选方式选出来的代理数量不足的时候,会放宽挑选要求,对速度和最近验证时间求交集,然后和成功率做并集。如果代理数量还不足,它还会放低要求,对满足最近验证时间和成功率的集合做并集。
在爬虫客户端调用py_cli的时候,代理客户端会首先调用refresh()方法,如果代理客户端实例的可用代理量不够,那么就会通过上一步的算法对IP池进行扩充,如果数量足够,那么就会根据代理的调度策略选取合适的IP进行使用。对代理客户端实例中的代理池的代理IP调用同样也需要策略。haipproxy目前实现了两种代理调度策略。(1)轮询策略。代理池是一个队列结构,每次从队首拿一个IP进行使用,如果该IP请求成功,则放到队尾,如果不成功,则需要调用代理客户端的proxy_feedback()方法将结果进行反馈。这种策略的好处是IP负载比较均衡。但是缺点在于,IP质量参差不齐,有的响应时间很快,有的响应时间很慢,并且高质量的免费代理IP的生命周期可能很短,这样就无法充分利用。(2)贪婪策略。使用此种策略的时候,需要爬虫端对每次请求的响应时间进行记录,每次使用后调用`proxy_feedback()`方法以决定该代理IP是否继续下一次请求的时候被使用。如果使用某个代理IP的响应时间低于某个设置值,那么就会一直使用它,直到不能用就从代理池中删除。如果时间高于了该值,那么它会把该IP放入队尾。概括起来,该策略就是低质量IP轮询,高质量IP一直使用。
以上便是从高度不可用的资源中实现一个高可用的代理IP池的思路。
此外,为了爬虫的效率和定时器的高可用,haipproxy基于scrapy和redis实现了分布式爬虫和定时任务调度器。
说了这么多,那么该代理池的效果如何呢?下面是3月3日以知乎为目标站点,单机抓取的测试结果
可以看到单机条件下,采用并发抓取可以达到1w/hour的请求量。
项目和测试代码地址
SpiderClub/haipproxygithub.com/SpiderClub/haipproxy其中还有很多东西可以挖掘,比如流控、按城市筛选(在爬虫登录状态下有用)、高可用策略的继续优化等。所以希望有志同道合的朋友加入进来一起优化,一个人比较孤单。也希望看了这个回答或者使用了这个项目觉得有帮助的同学,能给个star鼓励一下。
知乎确实对单个ip的访问限制挺严格的,但是对于http访问来说,并不一定需要抓取别人做好的代理。国外的GAE,AWS,以及各种免费的虚拟主机,用python,php都有现成的代理服务,写个自动化脚本不停地去配置、删除代理服器就可以了。
要是仅仅短期使用的话其实用不着这么麻烦,在访问的http request里添加x-forward-for标签,client随机生成,宣称自己是一台透明代理服务器,代理其他人的访问就能绕过许多限制了,一般服务商不会限制透明代理。
在做的项目,免费爬虫代理IP池 知乎专栏
1、问题
代理IP从何而来?刚自学爬虫的时候没有代理IP就去西刺、快代理之类有免费代理的网站去爬,还是有个别代理能用。当然,如果你有更好的代理接口也可以自己接入。 免费代理的采集也很简单,无非就是:访问页面页面 —> 正则/xpath提取 —> 保存
如何保证代理质量?可以肯定免费的代理IP大部分都是不能用的,不然别人为什么还提供付费的(不过事实是很多代理商的付费IP也不稳定,也有很多是不能用)。所以采集回来的代理IP不能直接使用,可以写检测程序不断的去用这些代理访问一个稳定的网站,看是否可以正常使用。这个过程可以使用多线程或异步的方式,因为检测代理是个很慢的过程。
采集回来的代理如何存储?这里不得不推荐一个高性能支持多种数据结构的NoSQL数据库SSDB,用于代理Redis。支持队列、hash、set、k-v对,支持T级别数据。是做分布式爬虫很好中间存储工具。
如何让爬虫更简单的使用这些代理?答案肯定是做成服务咯,python有这么多的web框架,随便拿一个来写个api供爬虫调用。这样有很多好处,比如:当爬虫发现代理不能使用可以主动通过api去delete代理IP,当爬虫发现代理池IP不够用时可以主动去refresh代理池。这样比检测程序更加靠谱。
2、代理池设计
代理池由四部分组成:
ProxyGetter:代理获取接口,目前有5个免费代理源,每调用一次就会抓取这个5个网站的最新代理放入DB,可自行添加额外的代理获取接口;
DB:用于存放代理IP,现在暂时只支持SSDB。至于为什么选择SSDB,大家可以参考这篇文章,个人觉得SSDB是个不错的Redis替代方案,如果你没有用过SSDB,安装起来也很简单,可以参考这里;
Schedule:计划任务用户定时去检测DB中的代理可用性,删除不可用的代理。同时也会主动通过ProxyGetter去获取最新代理放入DB;
ProxyApi:代理池的外部接口,由于现在这么代理池功能比较简单,花两个小时看了下Flask,愉快的决定用Flask搞定。功能是给爬虫提供get/delete/refresh等接口,方便爬虫直接使用。
3、代码模块
Python中高层次的数据结构,动态类型和动态绑定,使得它非常适合于快速应用开发,也适合于作为胶水语言连接已有的软件部件。用Python来搞这个代理IP池也很简单,代码分为6个模块:
Api: api接口相关代码,目前api是由Flask实现,代码也非常简单。客户端请求传给Flask,Flask调用ProxyManager中的实现,包括get/delete/refresh/get_all;
DB: 数据库相关代码,目前数据库是采用SSDB。代码用工厂模式实现,方便日后扩展其他类型数据库;
Manager: get/delete/refresh/get_all等接口的具体实现类,目前代理池只负责管理proxy,日后可能会有更多功能,比如代理和爬虫的绑定,代理和账号的绑定等等;
ProxyGetter: 代理获取的相关代码,目前抓取了快代理、代理66、有代理、西刺代理、guobanjia这个五个网站的免费代理,经测试这个5个网站每天更新的可用代理只有六七十个,当然也支持自己扩展代理接口;
Schedule: 定时任务相关代码,现在只是实现定时去刷新代码,并验证可用代理,采用多进程方式;
Util: 存放一些公共的模块方法或函数,包含GetConfig:读取配置文件config.ini的类,ConfigParse: 集成重写ConfigParser的类,使其对大小写敏感, Singleton:实现单例,LazyProperty:实现类属性惰性计算。等等;
其他文件: 配置文件:Config.ini,数据库配置和代理获取接口配置,可以在GetFreeProxy中添加新的代理获取方法,并在Config.ini中注册即可使用;
4、安装
下载代码:
git clone [email protected]:jhao104/proxy_pool.git 或者直接到 下载zip文件安装依赖:
pip install -r requirements.txt启动:
需要分别启动定时任务和api 到Config.ini中配置你的SSDB 到Schedule目录下: >>>python ProxyRefreshSchedule.py 到Api目录下: >>>python ProxyApi.py5、使用
定时任务启动后,会通过代理获取方法fetch所有代理放入数据库并验证。此后默认每20分钟会重复执行一次。定时任务启动大概一两分钟后,便可在SSDB中看到刷新出来的可用的代理:
启动ProxyApi.py后即可在浏览器中使用接口获取代理,一下是浏览器中的截图:
index页面:
get:
get_all:
爬虫中使用,如果要在爬虫代码中使用的话, 可以将此api封装成函数直接使用,例如:
import requests def get_proxy(): return requests.get(":5000/get/").content def delete_proxy(proxy): requests.get(":5000/delete/?proxy={}".format(proxy)) # your spider code def spider(): # .... requests.get( proxies={"http": "http://{}".format(get_proxy)}) # ....6、最后
时间仓促,功能和代码都比较简陋,以后有时间再改进。喜欢的在github上给个star。感谢!
日常使用python爬虫多的,会自己有一个代理ip的数据库。
平时一直用自己写的python脚本来抓取更新代理地址。
代码就不贴了,有兴趣前行到
http://30daydo.com/article/94代码里头有注释帮助,易懂。
顺便统计了一下 http://www.xicidaili.com 上面显示出来的代理的可用率,最新最前面的可用率有76%,到时后面20个之后降到了30%。
########## 2016-08-13 更新 还是贴上代码吧(个人网站代码会定期更新,知乎的估计第一次怎样以后就怎样的了)################
# -*- coding=utf-8 -*- __author__ = Rocky import urllib2, time, datetime from lxml import etree import sqlite3,time class getProxy(): def __init__(self): self.user_agent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" self.header = {"User-Agent": self.user_agent} self.dbname="proxy.db" self.now = time.strftime("%Y-%m-%d") def getContent(self, num): nn_url = "/nn/" + str(num) #国内高匿 req = urllib2.Request(nn_url, headers=self.header) resp = urllib2.urlopen(req, timeout=10) content = resp.read() et = etree.HTML(content) result_even = et.xpath(//tr[@class=""]) result_odd = et.xpath(//tr[@class="odd"]) #因为网页源码中class 分开了奇偶两个class,所以使用lxml最方便的方式就是分开获取。 #刚开始我使用一个方式获取,因而出现很多不对称的情况,估计是网站会经常修改源码,怕被其他爬虫的抓到 #使用上面的方法可以不管网页怎么改,都可以抓到ip 和port for i in result_even: t1 = i.xpath("./td/text()")[:2] print "IP:%s\tPort:%s" % (t1[0], t1[1]) if self.isAlive(t1[0], t1[1]): self.insert_db(self.now,t1[0],t1[1]) for i in result_odd: t2 = i.xpath("./td/text()")[:2] print "IP:%s\tPort:%s" % (t2[0], t2[1]) if self.isAlive(t2[0], t2[1]): self.insert_db(self.now,t2[0],t2[1]) def insert_db(self,date,ip,port): dbname=self.dbname try: conn=sqlite3.connect(dbname) except: print "Error to open database%" %self.dbname create_tb= CREATE TABLE IF NOT EXISTS PROXY (DATE TEXT, IP TEXT, PORT TEXT ); conn.execute(create_tb) insert_db_cmd= INSERT INTO PROXY (DATE,IP,PORT) VALUES (%s,%s,%s); %(date,ip,port) conn.execute(insert_db_cmd) conn.commit() conn.close() def loop(self,page): for i in range(1,page): self.getContent(i) #查看爬到的代理IP是否还能用 def isAlive(self,ip,port): proxy={http:ip+:+port} print proxy #使用这个方式是全局方法。 proxy_support=urllib2.ProxyHandler(proxy) opener=urllib2.build_opener(proxy_support) urllib2.install_opener(opener) #使用代理访问腾讯官网,进行验证代理是否有效 test_url="" req=urllib2.Request(test_url,headers=self.header) try: #timeout 设置为10,如果你不能忍受你的代理延时超过10,就修改timeout的数字 resp=urllib2.urlopen(req,timeout=10) if resp.code==200: print "work" return True else: print "not work" return False except : print "Not work" return False if __name__ == "__main__": now = datetime.datetime.now() print "Start at %s" % now obj=getProxy() obj.loop(5)