一、优化硬盘存储
所以千万级网页的抓取是需要先设计的,先来做一个计算题。共要抓取一亿张页面,一般一张网页的大小是400KB左右,一亿张网页就是1亿X200KB=36TB 。这么大的存储需求,一般的电脑和硬盘都是没法存储的。所以肯定要对网页做压缩后存储,可以用zlib压缩,也可以用压缩率更好的bz2或pylzma 。
二、优化内存,URL去重
再来说内存占用问题,做爬虫程序为了防止重复抓取URL,一般要把URL都加载进内存里,放在set()里面。拿天眼查的URL举例:
https://www.tianyancha.com/company/23402373
这个完整URL有44个字节,一亿个URL就是4G,一亿个URL就要占用4G内存,这还没有算存这一亿个URL需要的数据结构内存,还有待抓取URL,已抓取URL还保存在内存中的html等等消耗的内存。
所以这样直接用set()保存URL是不建议的,除非你的内存有十几个G。
一个取巧的办法是截断URL。只把URL:https://www.tianyancha.com/company/23402373
的后缀:23402373放进set()里,23402373只占8个字节,一亿个URL占700多M内存。
但是如果你是用的野云主机,用来不断拨号用的非正规云主机,这700多M内存也是吃不消的,机器会非常卡。
就还需要想办法压缩URL的内存占用,可以使用BloomFilter算法,是一个很经典的算法,非常适用海量数据的排重过滤,占用极少的内存,查询效率也非常的高。它的原理是把一个字符串映射到一个bit上,刚才23402373占8个字节,现在只占用1个bit(1字节=8bit),内存节省了近64倍,以前700M内存,现在只需要10多M了。
BloomFilter调用也非常简单,当然需要先install 安装bloom_filter:
from bloom_filter import BloomFilter # 生成一个装1亿大小的 bloombloom = BloomFilter(max_elements=100000000, error_rate=0.1) # 向bloom添加URL bloom.add('https://www.tianyancha.com/company/23402373') #判断URL是否在bloombloom.__contains__('https://www.tianyancha.com/company/23402373')
不过奇怪,bloom里没有公有方法来判断URL是否重复,我用的__contains__()方法,也可能是我没用对,不过判重效果是一样的。
三、反抓取访问频率限制
单台机器,单个IP大家都明白,短时间内访问一个网站几十次后肯定会被屏蔽的。每个网站对IP的解封策略也不一样,有的1小时候后又能重新访问,有的要一天,有的要几个月去了。突破抓取频率限制有两种方式,一种是研究网站的反爬策略。有的网站不对列表页做频率控制,只对详情页控制。有的针对特定UA,referer,或者微信的H5页面的频率控制要弱很多。
另一种方式就是多IP抓取,多IP抓取又分IP代理池和adsl拨号两种,这里说adsl拨号的方式。
adsl的特点是可以短时间内重新拨号切换IP,IP被禁止了重新拨号一下就可以了。这样你就可以开足马力疯狂抓取了,但是一天只有24小时合86400秒,要如何一天抓过百万网页,让网络性能最大化也是需要下一些功夫的,后面我再详说。
至于有哪些可以adsl拨号的野云主机,你在百度搜”vps adsl”,能选择的厂商很多的。大多宣称有百万级IP资源可拨号,我曾测试过一段时间,把每次拨号的IP记录下来,有真实二三十万IP的就算不错了。
选adsl的一个注意事项是,有的厂商拨号IP只能播出C段和D段IP,110(A段).132(B段).3(C段).2(D段),A和B段都不会变,靠C,D段IP高频次抓取对方网站,有可能对方网站把整个C/D段IP都封掉。
C/D段加一起255X255就是6万多个IP全都报废,所以要选拨号IP范围较宽的厂商。 你要问我哪家好,我也不知道,这些都是野云主机,质量和稳定性本就没那么好。只有多试一试,试的成本也不大,买一台玩玩一个月也就一百多元,还可以按天买。
四、网络性能,抓取技术细节调优
上面步骤做完了,每天能达到抓取五万网页的样子,要达到百万级规模,还需把网络性能和抓取技术细节调优。
1.调试开多少个线程,多长时间拨号切换IP一次最优。
每个网站对短时间内访问次数的屏蔽策略不一样,这需要实际测试,找出抓取效率最大化的时间点。先开一个线程,一直抓取到IP被屏蔽,记录下抓取耗时,总抓取次数,和成功抓取次数。 再开2个线程,重复上面步骤,记录抓取耗时,总的和成功的抓取次数。再开4个线程,重复上面步骤。
2.requests请求优化
要优化requests.get(timeout=1.5)的超时时间,不设置超时的话,有可能get()请求会一直挂起等待。而且野云主机本身性能就不稳定,长时间不回请求很正常。如果要追求抓取效率,超时时间设置短一点,设置10秒超时完全没有意义。对于超时请求失败的,大不了以后再二次请求,也比设置10秒的抓取效率高很多。
3.优化adsl拨号等待时间
上面步骤已算把单台机器的抓取技术问题优化到一个高度了,还剩一个优化野云主机的问题。就是每次断开拨号后,要等待几秒钟再拨号,太短时间内再拨号有可能又拨到上一个IP,还有可能拨号失败,所以要等待6秒钟(测试值)。所以要把拨号代码改一下:
import os # 断开拨号 os.popen('rasdial 网络名称 /disconnect') time.sleep(6) # 拨号 os.popen('rasdial 网络名称 adsl账号名 adsl密码')
而且 os.popen(‘rasdial 网络名称 adsl账号名 adsl密码’) 拨号完成后,你还不能马上使用,那时外网还是不可用的,你需要检测一下外网是否联通。
我使用 ping 功能来检测外网连通性:
import os code = os.system('ping www.baidu.com')
code为0时表示联通,不为0时还要重新拨号。而ping也很耗时间的,一个ping命令会ping 4次,就要耗时4秒。
知识Tips:
1.为什么不用异步抓取?
没必要,这里的整个抓取关键是网络性能,而不是程序性能。用异步把程序性能提高了,单位时间的抓取次数是提高了,但是这样反而会击中对方网站的访问频率控制策略
2.要计算对方的带宽压力
抓取归抓取,但不要影响对方网站,把对方网站带宽都打满了。
一个中小型网站的带宽在5M以内,大一点的网站带宽可能10-30M,超大型的另算。
一张网页300KB,对方一般会压缩后传输给浏览器,就按压缩后30KB算,你的爬虫一秒请求20次,带宽就是600KB。可能一个网站每天都有几十个爬虫都在爬,我们按有10个爬虫在同时抓取,就是这些爬虫一秒内就要消耗600KBX10=6M带宽。
再加上还有正规爬虫,人家网站上的正常用户访问这些,算下来可能一共要消耗10M带宽。一般的大中型网站都是吃不消的。
神龙|纯净稳定代理IP免费测试>>>>>>>>天启|企业级代理IP免费测试>>>>>>>>IPIPGO|全球住宅代理IP免费测试