多執行緒爬蟲實現(上)

dwzb發表於2018-05-26

本文首發於知乎

爬蟲主要執行時間消耗是請求網頁時的io阻塞,所以開啟多執行緒,讓不同請求的等待同時進行,可以大大提高爬蟲執行效率。

本文基於多執行緒(這裡開啟了10個執行緒),使用github的api,抓取fork cpython專案的所有5千多個專案資訊,將資料儲存到json檔案中。

抓取github的這個內容,在上一篇文章中展示了不使用多執行緒的版本,這裡就直接在那個的基礎上進行改進。

爬蟲所需技術

  • requests庫請求網頁,獲取json格式資料,解析字典提取我們要的資訊,儲存json檔案
  • 使用threading為網頁請求部分設計多執行緒
  • 使用兩個佇列,分別儲存待抓取url和解析後的結果資料
  • 擁有github賬戶,需要在程式碼中填入賬號和密碼
  • 瞭解裝飾器(這裡只是計算執行時間,不瞭解也沒關係)

爬蟲程式碼如下

import requests
import time
from threading import Thread
from queue import Queue
import json

def run_time(func):
    def wrapper(*args, **kw):
        start = time.time()
        func(*args, **kw)
        end = time.time()
        print('running', end-start, 's')
    return wrapper


class Spider():

    def __init__(self):
        self.qurl = Queue()
        self.data = list()
        self.email = '' # 登入github用的郵箱
        self.password = '' # 登入github用的密碼
        self.page_num = 171
        self.thread_num = 10

    def produce_url(self):
        baseurl = 'https://api.github.com/repos/python/cpython/forks?page={}'
        for i in range(1, self.page_num + 1):
            url = baseurl.format(i)
            self.qurl.put(url) # 生成URL存入佇列,等待其他執行緒提取

    def get_info(self):
        while not self.qurl.empty(): # 保證url遍歷結束後能退出執行緒
            url = self.qurl.get() # 從佇列中獲取URL
            print('crawling', url)
            req = requests.get(url, auth = (self.email, self.password))
            data = req.json()
            for datai in data:
                result = {
                    'project_name': datai['full_name'],
                    'project_url': datai['html_url'],
                    'project_api_url': datai['url'],
                    'star_count': datai['stargazers_count']
                }
                self.data.append(result)

    @run_time
    def run(self):
        self.produce_url()

        ths = []
        for _ in range(self.thread_num):
            th = Thread(target=self.get_info)
            th.start()
            ths.append(th)
        for th in ths:
            th.join()

        s = json.dumps(self.data, ensure_ascii=False, indent=4)
        with open('github_thread.json', 'w', encoding='utf-8') as f:
            f.write(s)

        print('Data crawling is finished.')

if __name__ == '__main__':
    Spider().run()
複製程式碼

讀者只需要在Spider__init__中,指定自己的github郵箱和密碼,即可執行爬蟲。

爬蟲說明如下

1.run_time函式是一個計算程式執行時間的裝飾器,作用於Spider物件的run函式

2.Spider

  • __init__初始化一些常量
  • produce_url用於生產所有URL,儲存到Queue佇列qurl中。5千多個元素分佈在171個頁面之中,將這171個URL存入佇列中等待請求解析。其實這裡不需要多執行緒之間通訊,所以使用list代替Queue佇列也是可以的。
  • get_info網頁的請求與解析,之後開啟多執行緒就是多個這個函式同時執行。函式邏輯:只要qurl中還有元素,就每次從qurl中提取一個url進行請求解析,將結果存入data列表中。當佇列中沒有元素了即退出迴圈(爬蟲結束)。
  • run呼叫函式,執行爬蟲。首先呼叫produce_url產生待爬URL佇列。然後開啟指定數量的執行緒,每個執行緒都從qurl不斷提取URL進行解析,將資料存入data列表中。等到URL佇列被解析結束,將data中的資料儲存入json檔案中

爬蟲結果

抓取結果展示如下

多執行緒爬蟲實現(上)

這個程式開啟10個執行緒抓取171個頁面用了33秒。在這篇文章中不使用多執行緒則使用了333秒。為了能更清晰地體會多執行緒執行效率的改進,讀者可以自行嘗試修改上面程式碼中的self.page_numself.thread_num

我這裡做了一個實驗,self.page_num值設為20,即總共抓取20頁

  • 開2個執行緒執行 18.51 秒
  • 開5個執行緒執行 7.49 秒
  • 開10個執行緒執行 3.97 秒
  • 開20個執行緒執行 2.11 秒

一個問題

最後留一個問題給讀者思考:在前面的這篇文章中,我們也實現了一個多執行緒爬蟲,為什麼當時的程式碼那麼簡單,而現在卻複雜了這麼多呢?

後續

多執行緒爬蟲的下一篇文章會實現在翻頁、抓取二級頁面時使用多執行緒。

歡迎關注我的知乎專欄

專欄主頁:python程式設計

專欄目錄:目錄

版本說明:軟體及包版本說明

相關文章