Blogger Information
Blog 13
fans 1
comment 1
visits 18090
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
Python3 基于asyncio的新闻爬虫思路
小猿猿er
Original
1131 people have browsed it

Python写爬虫是非常方便的,爬取的目标不同,实现的方式也有很大不同。新闻爬虫的方便之处是,新闻网站几乎没有反爬虫策略,不好的地方是你想要爬取的新闻网站非常非常多。这个时候,效率就是你首要考虑的问题。
同步循环的效率在这里相形见绌,你需要的是异步IO实现一个高效率的爬虫。

Python3.5开始,加入了新的语法,async和await这两个关键字,asyncio也成了标准库,这对于我们写异步IO的程序来说就是如虎添翼,让我们轻而易举的实现一个定向抓取新闻的异步爬虫。

1. 异步爬虫依赖的模块

asyncio: 标准异步模块,实现python的异步机制;

uvloop:一个用C开发的异步循环模块,大大提高异步机制的效率;

aiohttp: 一个异步http请求的模块,用于下载网页;

urllib.parse: 解析url网站的模块;

logging: 记录爬虫日志;

leveldb: Google的Key-Value数据库,用以记录url的状态;

farmhash: 对url进行hash计算作为url的唯一标识;

sanicdb: 对aiomysql的封装,更方便的进行数据库mysql操作;

2. 异步爬虫实现的流程

2.1 新闻源列表

本文要实现的异步爬虫是一个定向抓取新闻网站的爬虫,所以就需要管理一个定向源列表,这个源列表记录了很多我们想要抓取的新闻网站的url,这些url指向的网页叫做hub网页,它们有如下特点:

它们是网站首页、频道首页、最新列表等等;

它们包含非常多的新闻页面的链接;

它们经常被网站更新,以包含最新的新闻链接;

它们不是包含新闻内容的新闻页面;

Hub网页就是爬虫抓取的起点,爬虫从中提取新闻页面的链接再进行抓取。Hub网址可以保存在MySQL数据库中,运维可以随时添加、删除这个列表;爬虫定时读取这个列表来更新定向抓取的任务。这就需要爬虫中有一个循环来定时读取hub网址。

2.2 网址池

异步爬虫的所有流程不能单单用一个循环来完成,它是多个循环(至少两个)相互作用共同完成的。它们相互作用的桥梁就是“网址池”(用asyncio.Queue来实现)。

这个网址池就是我们比较熟悉的“***者-消费者”模式。

一方面,hub网址隔段时间就要进入网址池,爬虫从网页提取到的新闻链接也有进入到网址池,这是***网址的过程;

另一方面,爬虫要从网址池中取出网址进行下载,这个过程是消费过程;

两个过程相互配合,就有url不断的进进出出网址池。

2.3 数据库

这里面用到了两个数据库:MySQL和Leveldb。前者用于保存hub网址、下载的网页;后者用于存储所有url的状态(是否抓取成功)。

从网页提取到的很多链接可能已经被抓取过了,就不必再进行抓取,所以他们在进入网址池前就要被检查一下,通过leveldb可以快速查看其状态。

3. 异步爬虫的实现细节

前面的爬虫流程中提到两个循环:

循环一:定时更新hub网站列表

实例

async def loop_get_urls(self,):
   print('loop_get_urls() start')    while 1:        await self.get_urls() # 从MySQL读取hub列表并将hub url放入queue
       await asyncio.sleep(50)

运行实例 »

点击 "运行实例" 按钮查看在线实例

循环二: 抓取网页的循环

实例

async def loop_crawl(self,):
   print('loop_crawl() start')
   last_rating_time = time.time()
   asyncio.ensure_future(self.loop_get_urls())
   counter = 0
   while 1:
       item = await self.queue.get()
       url, ishub = item
       self._workers += 1
       counter += 1
       asyncio.ensure_future(self.process(url, ishub))
       span = time.time() - last_rating_time 
       if self._workers > self.workers_max:
           print('got workers_max, sleep 3 sec to next worker')            await asyncio.sleep(3)

运行实例 »

点击 "运行实例" 按钮查看在线实例

4. asyncio 要点:

读读asyncio的文档就可以知道它的运行流程,这里分享一下使用时注意到的地方。

(1)使用loop.run_until_complete(self.loop_crawl())来启动整个程序的主循环;

(2)使用asyncio.ensure_future() 来异步调用一个函数,它相当于多进程的fork,gevent的spawn(),具体可以参考上述代码。

文章首发于我的技术博客:猿人学python

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post