大规模异步新闻爬虫的实现思路

Python爬虫 2019-05-20 14:23:16 阅读(6475) 评论(0)

我们知道,异步IO(asyncio)非常适合使用在网络请求的场景,也就是说它很适合在爬虫中应用。

但是,如果我们只是特定抓取某一个网站,而且该网站对IP访问频率做了限制,那么asyncio并没有什么优势,并且不如同步请求的爬虫的逻辑更清晰、实现更方便。

不过,我们要是抓几千家新闻网站的新闻呢?面对这么多的目标网站,我们的爬虫可以通过异步IO同时请求这些网站,并且新闻网站几乎都有这样一个特点:对爬虫敞开大门,毫不设防。

目标网站的大门敞开着,就看你如何把爬虫程序写得极度高效,榨干你服务器和网络资源,最大限度的提升爬虫的效率。这时候,异步IO可以毫不犹豫的登场了。

也就是说,我们可以构建一个“大规模异步新闻爬虫”。这样的一个新闻爬虫就是通异步IO实现大规模化,“大规模”指的是单台机器单日能下载几百万甚至上千万的新闻网页,也可以是多台机器以分布式方式下载更多的网页。

要实现这样的新闻爬虫,需要合理的模块化实现,不同的模块负责不同的功能,它们主要是:网址池,爬取器,数据存储,内容提取器这四大模块。接下来,我们就详细探究一下各个模块的功能和实现。

大规模异步新闻爬虫设计

 

一、网址池(UrlPool)

一个网页对应这一个网址(URL),我们要抓的几千家新闻网站包含的网页可以有几亿甚至上百亿(近十年内的新闻),每个新闻网页内可能链接了其它新闻网页,这种错综复杂的链接关系,导致爬虫在抓取过程中经常反复遇到同一个URL,那么判断这个URL是否已经被成功抓取至关重要,这决定着爬虫是否要重复劳动做无用功。

管理好这些URL,成为整个爬虫的关键。为此,我们设计一个网址池(UrlPool)来进行URL管理。

这个 UrlPool 是一个典型的“生产者-消费者”模型:

生产者消费者模型图示

模仿这个模型就得到UrlPool的模型:

网址池的设计

从网址池的使用目的出发来设计网址池的接口,它应该具有以下功能:

  • 往池子里面添加URL;
  • 从池子里面取URL以下载;
  • 池子内部要管理URL状态;

URL的状态有以下4种:

  • 已经下载成功
  • 下载多次失败无需再下载
  • 正在下载
  • 下载失败要再次尝试

前两个是永久状态,也就是已经下载成功的不再下载,多次尝试后仍失败的也就不再下载,它们需要永久存储起来,以便爬虫重启后,这种永久状态记录不会消失,已经成功下载的URL不再被重复下载。永久存储的方法有很多种:

比如,直接写入文本文件,但它不利于查找某个URL是否已经存在文本中;
比如,直接写入MySQL等关系型数据库,它利用查找,但是速度又比较慢;
比如,使用key-value数据库,查找和速度都符合要求,是不错的选择!

我们选用LevelDB来作为URL状态的永久存储。LevelDB是Google开源的一个key-value数据库,速度非常快,同时自动压缩数据。

而后面两个状态“正在下载”和“下载失败要再次尝试”的URL,状态变化较快,可以放在内存中,比如用Python的dict。

由此,我们的UrlPool的存储包括leveldb和一些在内存的dict。

 

二、抓取器(crawler)

有了UrlPool,抓取器的实现就方便多了,它的工作就是从UrlPool里面拿出URL,然后去下载网页,再从网页里面提取新的URL放到UrlPool里面。所以说,这个抓取器既是URL的“消费者”,又是URL的“生产者”。

抓取器的这个过程应该是在一个while循环,只要网址池里面有待抓取的URL,那么这循环就不该结束。通过使用asyncio可以大大提高这个循环的效率,每次循环都能同时下载多个网页。

asyncio对每个下载都会产生一个协程,为了让协程的数量匹配硬件资源(比如CPU比较差,网络带宽很小,那么协程数量就不能太多),需要记录当前开启的下载协程的数量,当这个数量超过预设的阈值时,while循环暂停一定时间。这个暂停时段内,会有很多下载协程完成,完成的协程会减小这个数量,从而在下次循环开始时,又可以产生新的下载协程。

另外,下载协程还要做的工作有:

  • 保存网页;
  • 设置它下载的URL在UrlPool中的状态;
  • 提取网页中的URL放入UrlPool;
  • 最后把当前协程数量减1。

写好这个抓取器,要注意的细节很多,比如从网页中提取的URL很多,这些URL都要抓取吗?当然不是,这URL有可能是链接到别的非新闻网站,比如微博。对于提取到的URL不管三七二十一就放入UrlPool进行抓取,会导致爬虫抓取的网页成几何级数增长,并且大都是非新闻网页,导致有效数据的抓取效率严重下降。

即使是新闻网站,也可能链接到它的非新闻频道。所以,界定URL是否是新闻网页是个重点,但也是个难点。

首先,我们抓取前,要先收集一批新闻网站的网址,它们域名之外的URL不抓;

其次,确认抓取这些网站的某些频道,其它频道不抓;

还有一个比较重要的点就是对URL的清洗,先看下面这两个URL有什么不同:

http://news.ifeng.com/a/20181106/60146589_0.shtml?_zbs_baidu_news

http://news.ifeng.com/a/20181106/60146589_0.shtml

第一个多了个参数,但是它们都是指向同一个网页。在新闻网站中有很多这样的URL,需要进行清洗,把第一种边成第二种。实际上,新闻网页的url几乎都是静态化的,即以 .html、.htm、.shtml 结尾,其后面的参数对网页内容没有影响,只是对后台日志分析时有用。我们可以通过这样的规则把问号后面的参数去掉。当然,也不绝对符合这样的规则,这也是难点所在。

对URL的判断还有很多,这些都是为了减少无效抓取的措施,提供有效抓取效率。在比如,一些通告型的新闻网页还会附上word文档、扫描件的图片、PDF之类的文档的下载URL,它们也不是新闻网页,也不需要下载。

讲到这里,我们从一个看似简单的新闻抓取任务中剖析出来了很多难点,如果把这些难点都有效的解决就可以得到一个高效的抓取器了,这对于硬件资源和网络带宽有限的环境尤为重要。如果你的硬件资源和网络相对富裕,对URL的判别可以放宽,多抓一点也无所谓。

 

三、数据存储

抓来的东西总得要存储下来才能用。抓来的就是网页,也就是网页的html代码,但这不是我们想要的最终数据,最终数据是从一个网页里面提取出来的新闻标题、发布时间、来源网站、正文内容等结构化的数据。这些最终数据肯定是要存储下来的。

那么,我们要不要把html存储起来呢?对于大规模的新闻爬虫来说,存html很有必要。

其一,网站数量很多导致提取程序会经常出错。爬虫抓到html就提取只存最终数据的策略,一旦遇到提取程序出错,爬虫就会退出,抓取效率大大降低。如果爬虫不退出,而是忽略提取错误,导致提取错误的网站内容不会进入最终数据库,网页做到了但没有数据,白白浪费了抓取。

其二,开始只想提取少量数据,后面使用时发现少提取了某些数据。如果不保存html,就要重新抓取,费时费力。

其三,抓取任务是IO密集型任务,而提取数据是CPU型任务。两者混在一起,效率堪忧。而分开来的话,抓取用异步IO提供效率。提取数据用多进程来提高效率。分而治之,都可以提高整个爬虫系统的效率。

当然,存储html的坏处也显而易见:对硬盘空间的需求变大。具体在另一篇微信文章中有详细阐述,见文末的延伸阅读。一句话就是要压缩存储。

用于存储的数据库的选择很多,比如MySQL、MongoDB等等,可以根据自己的喜好来选择。使用了异步IO抓取网页,存储也要用上异步IO才能配合下载提高爬虫的效率。

 

四、数据提取

爬虫爬来一堆的网页html代码,直接给应用开发者的话会把他们搞疯掉的。必须要从中提取出来有用的数据并结构化的存储下来才能方便的使用。

对应新闻网页的提取,可以使用比较通用的算法,也就是一个算法对应上千家网站的不同网页格式,这样的算法好处是实现起来不费力但费脑,几乎一劳永逸。(参见延伸阅读)

还有另外的思路是,对应每个网站写一个提取模板,同一个网站的不同频道可能需要不同的模板。这样几千家网站就要写成千上万个模板,费力不费脑。模板可以是正则表达式或其它自定义的规则,一旦网站改版,模板就要重新写,维护这么大数量的模板是一件比较费力的事情。

前面我们讲到,数据提取是一件CPU密集型的任务,因为它主要是通过以下方法实现的:

(1)正则表达式,Python的re模块来写正则表达式提取,CPU消耗在字符串查找匹配过程中;

(2)使用xpath,使用lxml库把网页生成DOM树,然后就是对节点的查找和匹配,这也是消耗CPU的过程。

CPU密集型的任务要提高效率主要就是多进程,而且提取网页数据是单任务型的,每个网页的提取与其它网页没有关系,可以通过一个进程池(数量与服务器可用CPU核数相等)来并行提取数据。

升级:分布式爬虫

以上新闻爬虫的四大模块都可以独立运行,构造成一个分布式爬虫系统,可以提高爬取效率,当然结构复杂了维护难度也会增加。

(1)网址池做成独立程序,称为爬虫Server,转发分发和回收URL;

(2)抓取器做成独立程序,称为爬虫Client,向Server请求URL,并发送新提取的URL;

(3)数据存储服务器独立于爬虫,提高爬虫的写入效率;

(4)数据提取程序分布运行在多台服务器,提高并行提取的能力。

最后,这个爬虫架构总结为一张图:

爬虫架构框架图

相关阅读:

(1)详细的异步爬虫设计教程,从Python爬虫开始看起。

(2)如何让爬虫一天抓取100万张网页

延伸阅读:

(1)爬虫技巧:逆向js代码加密,代码混淆不是难事

(2)让你的爬虫无障碍抓取上千万需登录的APP数据

(3)robots.txt快速抓取网站的小窍门

(4)爬虫小偏方:绕开登陆和访问频率控制

猿人学banner宣传图

我的公众号:猿人学 Python 上会分享更多心得体会,敬请关注。

***版权申明:若没有特殊说明,文章皆是猿人学 yuanrenxue.con 原创,没有猿人学授权,请勿以任何形式转载。***

说点什么吧...