zoukankan      html  css  js  c++  java
  • 抓取新闻网站:异步爬虫实现的流程和细节

    利用python写爬虫的人越来越多,这也表明了用python写爬虫相比其它语言用起来更方便一些。很多新闻网站都没有反爬虫的策略,所以爬取新闻网站的数据就更加方便。但是,新闻网站多如牛毛,我们该如何去爬呢?从哪里开爬呢?是我们需要首先考虑的问题。

    你需要的是异步IO实现一个高效率的爬虫。

    下面我们就来看看Python3 基于asyncio的新闻爬虫,我们如何去高效地实现它。

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

    1. 异步爬虫依赖的模块

    1. asyncio: 标准异步模块,实现python的异步机制;
    2. uvloop:一个用C开发的异步循环模块,大大提高异步机制的效率;
    3. aiohttp: 一个异步http请求的模块,用于下载网页;
    4. urllib.parse: 解析url网站的模块;
    5. logging: 记录爬虫日志;
    6. leveldb: Google的Key-Value数据库,用以记录url的状态;
    7. farmhash: 对url进行hash计算作为url的唯一标识;
    8. 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 span > 3:
                      rate = counter / span
                      print('	loop_crawl2() rate:%s, counter: %s, workers: %s' % (round(rate, 2), counter, self._workers))
                      last_rating_time = time.time()
                      counter = 0
                  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基础教程

  • 相关阅读:
    HDU 5795
    HDU5783
    HDU 5791
    VK Cup 2016
    Codeforces Round #357 (Div. 2)
    Educational Codeforces Round 15
    HDU5724
    博弈学习 3
    Spring的多配置文件加载
    spring 核心技术
  • 原文地址:https://www.cnblogs.com/amiza/p/10330060.html
Copyright © 2011-2022 走看看