背景说明:我初学python爬虫,要爬国内某网站上的数据做数据分析用,数据总共大约有 15e6 条,在这个网站上每个页面有 15 条数据。也就是说大约有 1e6 的页面要爬取。
目前爬取方案:获取html页面后用正则表达式匹配。在把匹配后的插入数据库中。我开了3个线程,分别从 0 , 1/3 ,2/3 出开始爬取。开多线程虽然有常数的优化,但是本身基数就很大,优化很不明显。
现在想学习解决的问题:
1,能否通过学习一些新的技术获得一个较大的优化?
2,在前期爬取的过程中速度明显比现在快,随着数据库里内容的增加,插入一条数据花费的时间边的更多了。(ps:数据库用的 sqlite3)(程序在新建数据库时运行速度会有较大提升,但数据库一旦到达 300 M 左右时耗时会明显增加)。数据库方面能有什么样的优化。
3,最近在学《计算机网络》,老师在课上讲到了分布式应用。想学着写个分布式的爬虫,就算是自己的练习。知友有哪些好的项目可以推荐一下学习?
回复内容:
针对题目,我有两个建议:
1. 使用xpath或者css selector替代正则。
2. 使用gevent来提升爬取速度。
感谢 @松鼠奥利奥 提到我,发现当初回答这道题是用手机随手一答,并没有说清楚具体原因,抱歉。
为什么建议使用xpath代替正则?
对于固定场景,合适的正则表达式解析的效率是会高于xpath的,之所以提倡使用xpath,是因为
程序可维护性,xpath的可读性远高于正则表达式,开发调试和维护效率会大大提高。
为什么建议使用gevent?
爬虫程序明显是IO密集型的程序,使用gevent可以
使得阻塞的IO操作变成非阻塞,减少无谓的等待时间,从而提高效率。
所有性能问题都能通过堆硬件的方式来解决,我们现在讨论的是如何最大效率地利用硬件。
利用消息队列来进行调度是个很聪明的方案,但是如果我们发现消息队列中的消息是有堆积的,那么说明这个系统真正的瓶颈并不在于消息队列,而在于爬取端。
我们可以通过简单地启动多个爬取实例消费同一个消息队列的任务来提升爬取速度,但是要注意,在一个系统中启动过多的爬取实例,进程切换的开销反而会使得效率下降,实例个数一般推荐为cpu的核数。
我的观点是,爬取实例尽可能高效,而爬取频率应该是调度方来控制,同时多机同时爬取也是很好的。
那些给你安利什么gevent/xpath什么的人,一定没有写过爬千万级或者亿级以上数据量的爬虫。
按照你的需求,你有1500万条数据需要抓取,这1500万条数据分布在100万页网页中。那么,从以下几个方面给题主一点建议。
首先,存储的问题,爬虫的数据,尤其到了一个比较大的两极,sqlite3这种文件型的数据库最好不要用了,建议使用MongoDB做存储,简单实用,方便拓展爬虫的数据。
其次,如果可以,将这100万页页码分解成100万个任务,放到一个MessageQueue中。
接着,在写程序的时候,不要考虑任何多线程多进程gevent或者
任何会让你的程序“提速”的东西,你的爬虫只要完成一个简单的功能:接受一个输入,这里就是某一页的页码,得到的输出是这一页的15条数据,最好直接存储到数据库中。
最后一步,将上两步具体实现,建议使用RabbitMQ或者ActiveMQ,然后在上一步的接受输入变成从Queue中取一条任务。
如果源网站不会因为访问频率过高而封你的IP,那么你可以在一台机器上多部几个爬虫实例,否则的话,你可以将你的代码部署到多台机器上。阿里云按时间付费的机器很便宜,你可以开多点机器以加快速度。
这样的好处是,你可以随时新增或减少一个爬虫实例,而不会影响之前已经部署的任何爬虫,也可以随时将抓取失败的页码数再次放入Queue中,而不影响正在抓取任务的爬虫。
最后,如果你按照这样的思路完成,你只需要写爬虫和搭建MessageQueue,它们之间的交互已经有了现成的库xtls可以帮你:
pypi: https://pypi.python.org/pypi/xtls/
文档:xtls: awesome tools by xlzd
github:xlzd/xtls · GitHub
任务入MessageQueue:
<code class="language-python"><span class="kn">from</span> <span class="nn">xtls.activemqwraps</span> <span class="kn">import</span> <span class="n">producer</span>
<span class="c"># mq_uri 是你的MQ的连接地址</span>
<span class="c"># queue_name 是queue的名字</span>
<span class="nd">@producer</span><span class="p">(</span><span class="n">mq_uri</span><span class="p">,</span> <span class="n">queue_name</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">insert_task</span><span class="p">():</span>
<span class="c"># 你有一百万个页码的任务</span>
<span class="c"># 如果你的任务是其它复杂的内容,也可以yield出去</span>
<span class="err">#</span> <span class="err">会自动加入到</span><span class="n">MessageQueue</span><span class="err">中</span>
<span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1000001</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">page</span>
</code>
Salin selepas log masuk
爬虫看看scrapy即可,都帮你想好了。性能问题不用想太多,主要是看你有多少台机器和你的带宽。
数据库有很多方法。例如可以先写文件或key-value型数据库,再弄个脚本存入做数据分析用的数据库。
安利scrapy
这个框架比你自己撸快太多了
300mb卡了…
sqlite开一次关一次延迟挺大的
你有啥变量没清空?
还是你往sqlite里面存了奇怪的东西…
我瞎猜的
哦小心被ban 假期爬tripadvisor 爬到第八个小时被block… 当时想掀桌…
你说的轮子scrapy都帮你做好了
scrapy/scrapy · GitHub
存储搞个NoSQL吧
xlzd说的是对的。
爬虫首先要做成分布式。用queque来做。
程序的大部分时间都花在下载上,你用同步还是异步写完全没区别。
别用多线程爬,分分钟被封掉。必要的时候还要加时延。
每个子任务爬之前,url过一遍bloom filter,数据库查一遍,确保不做重复下载。
数据库用mongo,index做好,不然到几百万后会很慢。
还有就是不要迷信scrapy,很多时候自己写的更好用。
如果用十几台爬虫机抓数据,应该两三天就可以爬完了,一台机器的话,时间会长一点。几点建议:
1,线程开的多一些,比如50个线程或者一百个线程,具体可以top一下看看负载怎么样来决定线程数。
2,如果是分布式,一台master多台slave,master用来调度爬虫任务,消息队列可以用redis,每台slave机器使用多线程。
3,建立一个ip池,每个ip隔一段时间爬取,这样防止被封,ip池可以上网去找,然后做一下验证,看是否可用。
4,如果需要,学一下模拟登陆。
5,不建议用sqlite,甚至可以不用数据库,可以直接写到txt里面存下来,等到计算的时候也很方便处理。
6,xpath解析用lxml,不规则的网页用正则匹配。
Scrapy,mongdb,如果要用搞个分布式的话再加个redis