1. 程式人生 > >抓取新聞網站:異步爬蟲實現的流程和細節

抓取新聞網站:異步爬蟲實現的流程和細節

頁面 class .com 數據 size 兩個 問題 流程 開發

利用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(\tloop_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學習教程猿人學python:https://www.yuanrenxue.com/

抓取新聞網站:異步爬蟲實現的流程和細節