上一次的抓取豆瓣高分計算機書籍的案例,採用的是徹底同步的方式。即單個線程依次執行完全部的邏輯,這樣存在的問題就是咱們的爬蟲程序會很是的慢。html
因此本文做爲上一次案例的升級版本,經過按部就班、動手實踐的方式來達到更好的學習效果。編程
import requests from bs4 import BeautifulSoup import re import numpy as np import csv import time import threading import queue
本次新增了兩個包,threading 和 queue。threading 是用來進行多線程編程的,queue 也就是用來建立隊列。至於更詳細的使用方法,能夠上網自行學習。這裏就很少作介紹了。多線程
以上前三個方法都沒有改動,主要是第四個和第五個。app
req_page(): 用來請求url。函數
def req_page(): while True: try: url = url_task.get(block=False) resp = requests.get(url) html = resp.text task_html.put(html) time.sleep(1) except: break
以上代碼會被若干個線程執行,每個線程的流程都是不段的從 url_task
也就是咱們建立的隊列1中取出一個URL,而後執行請求,並把下載到的 HTML 放入隊列2。這裏有兩點要注意的。第一個點就是經過 url_task.get()
方法從隊列裏拿出任務的時候,因爲咱們的隊列1是提早設定好的,也就是說當下載線程取任務的時候並不會發生 queue.Empty
的異常。只有當隊列中的數據被處理完的時候纔會執行 except
,那麼線程就能夠經過這個來退出。第二點是sleep
這塊 ,由於請求太頻繁會被豆瓣封掉IP。學習
get_content():url
def get_content(): if task_html.qsize() > 10: while True: try: html = task_html.get(block=False) bs4 = BeautifulSoup(html, "lxml") book_info_list = bs4.find_all('li', class_='subject-item') if book_info_list is not None: for book_info in book_info_list: list_ = [] try: star = book_info.find('span', class_='rating_nums').get_text() if float(star) < 9.0: continue title = book_info.find('h2').get_text().replace(' ', '').replace('\n', '') comment = book_info.find('span', class_='pl').get_text() comment = re.sub("\D", "", comment) list_.append(title) list_.append(comment) list_.append(star) task_res.append(list_) except: continue except: break
這個函數首先判斷一下 HTML 文檔隊列(隊列2)的大小是否是大於10,目的是防止解析線程比下載線程執行的快,若是解析線程快於下載線程,那麼再尚未下載完全部的URL時,就觸發隊列的 queue.Empty
異常,從而過早退出線程。中間的代碼也是上次案例中的代碼,不一樣之處也就是之前是從列表中讀取,如今是從隊列中讀取。同時這個函數也是由多個解析線程執行。spa
主函數:線程
# 生成分頁url url_list = make_url(50) # url 隊列 (隊列1) url_task = queue.Queue() for url in url_list: url_task.put(url) # 下載好的html隊列 (隊列2) task_html = queue.Queue() # 最終結果列表 task_res = [] threads = [] # 獲取html線程 for i in range(5): threads.append(threading.Thread(target=req_page)) # 解析html線程 threads.append(threading.Thread(target=get_content)) threads.append(threading.Thread(target=get_content)) for i in threads: i.start() i.join() # 主線程排序保存 save(_sort(task_res))
主函數的流程也就是最開始寫的五個流程。由於咱們建立的全部線程都調用了 join()
方法,那麼在最後執行排序和保存操做的時候,全部的子線程都已經執行完畢了。3d