標准爬蟲初探,來自Python之父的大餐!


  首先不得不承認自己做了標題黨,本文實質是分析500lines or less的crawl工程,這個工程的地址是https://github.com/aosabook/500lines,有興趣的同學可以看看,是一個非常高質量的開源工程集合,據說要寫一本書,不過看着代碼提交記錄,這本書面世時間應該不會很快。這篇文章寫得很渣,錯誤一定要提啊。。。

 

  網絡爬蟲從一個或若干初始網頁的URL開始,獲得初始網頁上的URL,在抓取網頁的過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。簡單的可以將網絡爬蟲理解為一個帶有終止條件的while循環,在條件不觸發的情況下,爬蟲就不斷的從每個以及獲取的url發送請求獲取頁面數據,然后解析當前頁面的url,不斷迭代下去。在crawl工程當中,完成這一過程的是crawler類,他並未采用廣度優先或是深度優先的爬蟲,在當前請求失敗的時候就通過python掛起當前任務,然后在之后再進行調度,這可以勉強理解為基於網絡連通性的A*搜索,其運行方式如下所示:

  

  對一個初始化后的crawler對象,其中存在一個url,一個todo集合,存儲尚未繼續呢爬蟲操作的url;一個busy集合,保存等待其他爬蟲數據的url集合;一個done集合,保存完成頁面爬取的url集合。爬蟲的核心就是這個死循環,首先爬蟲從todo集合當中獲取一個url,然后初始化fetch對象用於獲取頁面上的url,最后進行任務調度執行一個url請求任務。這段流程的代碼如下所示。

 1 @asyncio.coroutine  2 def crawl(self):  3         """Run the crawler until all finished."""
 4         with (yield from self.termination):  5             while self.todo or self.busy:  6                 if self.todo:  7                     url, max_redirect = self.todo.popitem()  8                     fetcher = Fetcher(url,  9                                       crawler=self, 10                                       max_redirect=max_redirect, 11                                       max_tries=self.max_tries, 12  ) 13                     self.busy[url] = fetcher 14                     fetcher.task = asyncio.Task(self.fetch(fetcher)) 15                 else: 16                     yield from self.termination.wait() 17         self.t1 = time.time()

   

  一個爬蟲很明顯不會僅僅由一個死循環構成,在crawl外層需要其他模塊支持其操作,包括網絡連接,url獲取,任務調度等任務,整個crawl工程的調度框架如下所示:

 

  在crawl創建初始化時候首先創建一個ConnectionPool:

 

  self.pool = ConnectionPool(max_pool, max_tasks)

  其中保留屬性connections和queue,分別保存連接的集合和隊列,用於后續調度;而connection中存儲host和端口號並支持ssl,通過asyncio.open_connection()獲取連接。

  self.connections = {} # {(host, port, ssl): [Connection, ...], ...}
  self.queue = [] # [Connection, ...]

  任務執行時crawl方法首先通過loop.run_until_complete(crawler.crawl())加載到event loop當中,然后用上述語句構建的鏈接池ConnectionPool中保存connection對象,獲取連接對象然后通過fetcher對象的fetch方法進行數據爬取。對於一個url請求任務,使用fetcher進行處理,調度則是用asyncio.Task方法進行的調度。其中fetch方法獲取被掛起的generator,交給asyncio.Task執行。

  通過yield from和asynico.coroutine語句,將這個方法變為執行過程中的generator,在執行fetcher.fetch()方法時候如果被掛起,則通過調度程序進行處理。

  fetcher.fetch()方法是網絡爬蟲的核心方法,負責從網絡上獲取頁面數據並將其中的url加載到todo集合當中,該方法嘗試獲取頁面數據當嘗試次數達到上限時停止操作,獲取成功的html數據和外部鏈接以及重定向鏈接都將被存儲。在url鏈接次數到達上限的情況下,將停止這個url的鏈接操作,輸出出錯日志。之后針對頁面的不同狀態,采取不同的處理方式。

  下面的代碼是crawling.py文件從333行開始(crawling.py)到對應方法結束的區域,通過對頁面status的判斷選擇不同的處理方式。其中通過正則表達式,獲取頁面上的url信息,這里選擇為href開頭的字符串,核心url提取的代碼在下面: 

 1 # Replace href with (?:href|src) to follow image links.
 2 self.urls = set(re.findall(r'(?i)href=["\']?([^\s"\'<>]+)',body))
 3 if self.urls:
 4     logger.warn('got %r distinct urls from %r',len(self.urls), self.url)
 5     self.new_urls = set()
 6     for url in self.urls:
 7         url = unescape(url)
 8         url = urllib.parse.urljoin(self.url, url)
 9         url, frag = urllib.parse.urldefrag(url)
10         if self.crawler.add_url(url):
11             self.new_urls.add(url)            

  通過代碼,很明顯就可以看出正則匹配結果存儲在urls集合當中並通過for循環依次進行處理,加入到當前fetcher的crawler對象的todo集合當中。

 

  在之前分析的基礎上對主文件crawl.py進行進一步分析,可以得到整體爬蟲的架構:

  在主文件當中首先通過argparse.ArgumentParser進行解析,設置控制台的數據讀取和控制,其中選擇了IOCP作為windows環境下的event loop對象。主方法,首先通過parse_args返回存儲命令行數據的字典,如果沒有root屬性,則給出提示。然后配置日志級別,指示日志的輸出級別,低於最低級別的不輸出。

  通過入口函數main方法進入程序的時候,首先根據來自命令行參數對Crawler進行初始化,同時獲取使用asyncioloop event對象,執行run_until_complete方法,會一直執行到這個程序結束運行。

  除此之外reporting.py用於打印當前任務執行情況。其中fetcher_report(fetcher, stats, file=None)打印這個url的工作狀態,url就是fetcher的url屬性;report(crawler, file=None)打印整個工程所有完成的url工作狀態。

  至此,crawl的基本框架就展現在眼前了。至於在這個程序中出現的一些不容易理解的python語言特性,某些應用到的核心模塊,將在下一篇博客《標准爬蟲分析,精簡不簡單!》中進行闡述。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM