<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
簡單的爬蟲只有一個程序、一個執行緒,因此稱為單執行緒爬蟲
。單執行緒爬蟲每次只存取一個頁面,不能充分利用計算機的網路頻寬。一個頁面最多也就幾百KB,所以爬蟲在爬取一個頁面的時候,多出來的網速和從發起請求到得到原始碼中間的時間都被浪費了。如果可以讓爬蟲同時存取10個頁面,就相當於爬取速度提高了10倍。為了達到這個目的,就需要使用多執行緒技術
了。
微觀上的單執行緒,在宏觀上就像同時在做幾件事。這種機制在 I/O(Input/Output,輸入/輸出)密集型的操作
上影響不大,但是在CPU計算密集型的操作
上面,由於只能使用CPU的一個核,就會對效能產生非常大的影響。所以涉及計算密集型的程式,就需要使用多程序。
爬蟲屬於I/O密集型的程式,所以使用多執行緒可以大大提高爬取效率。
multiprocessing
本身是Python的多程序庫
,用來處理與多程序相關的操作。但是由於程序與程序之間不能直接共用記憶體和堆疊資源,而且啟動新的程序開銷也比執行緒大得多,因此使用多執行緒來爬取比使用多程序有更多的優勢。
multiprocessing下面有一個dummy模組
,它可以讓Python的執行緒使用multiprocessing的各種方法。
dummy下面有一個Pool類
,它用來實現執行緒池。這個執行緒池有一個map()方法
,可以讓執行緒池裡面的所有執行緒都“同時”執行一個函數。
測試案例 計算0~9的每個數的平方
# 迴圈 for i in range(10): print(i ** i)
也許你的第一反應會是上面這串程式碼,迴圈不就行了嗎?反正就10個數!
這種寫法當然可以得到結果,但是程式碼是一個數一個數地計算,效率並不高。而如果使用多執行緒的技術,讓程式碼同時計算很多個數的平方,就需要使用 multiprocessing.dummy
來實現:
from multiprocessing.dummy import Pool # 平方函數 def calc_power2(num): return num * num # 定義三個執行緒池 pool = Pool(3) # 定義迴圈數 origin_num = [x for x in range(10)] # 利用map讓執行緒池中的所有執行緒‘同時'執行calc_power2函數 result = pool.map(calc_power2, origin_num) print(f'計算1-10的平方分別為:{result}')
在上面的程式碼中,先定義了一個函數用來計算平方,然後初始化了一個有3個執行緒的執行緒池。這3個執行緒負責計算10個數位的平方,誰先計算完手上的這個數,誰就先取下一個數繼續計算,直到把所有的數位都計算完成為止。
在這個例子中,執行緒池的 map()
方法接收兩個引數,第1個引數是函數名,第2個引數是一個列表。注意:第1個引數僅僅是函數的名字,是不能帶括號的。第2個引數是一個可迭代的物件,這個可迭代物件裡面的每一個元素都會被函數 clac_power2()
接收來作為引數。除了列表以外,元組、集合或者字典都可以作為 map()
的第2個引數。
由於爬蟲是 I/O密集型
的操作,特別是在請求網頁原始碼的時候,如果使用單執行緒來開發,會浪費大量的時間來等待網頁返回,所以把多執行緒技術應用到爬蟲中,可以大大提高爬蟲的執行效率。
下面通過兩段程式碼來對比單執行緒爬蟲和多執行緒爬蟲爬取CSDN首頁
的效能差異:
import time import requests from multiprocessing.dummy import Pool # 自定義函數 def query(url): requests.get(url) start = time.time() for i in range(100): query('https://www.csdn.net/') end = time.time() print(f'單執行緒迴圈存取100次CSDN,耗時:{end - start}') start = time.time() url_list = [] for i in range(100): url_list.append('https://www.csdn.net/') pool = Pool(5) pool.map(query, url_list) end = time.time() print(f'5執行緒存取100次CSDN,耗時:{end - start}')
從執行結果可以看到,一個執行緒用時約69.4s
,5個執行緒用時約14.3s
,時間是單執行緒的五分之一
左右。從時間上也可以看到5個執行緒“同時執行”的效果。
但並不是說執行緒池設定得越大越好。從上面的結果也可以看到,5個執行緒執行的時間其實比一個執行緒執行時間的五分之一(13.88s
)要多一點。這多出來的一點其實就是執行緒切換的時間。這也從側面反映了Python的多執行緒在微觀上還是序列的。
因此,如果執行緒池設定得過大,執行緒切換導致的開銷可能會抵消多執行緒帶來的效能提升。執行緒池的大小需要根據實際情況來確定,並沒有確切的資料。
從 https://www.kanunu8.com/book2/11138/ 爬取
《北歐眾神》
所有章節的網址,再通過一個多執行緒爬蟲將每一章的內容爬取下來。在本地建立一個“北歐眾神”資料夾,並將小說中的每一章分別儲存到這個資料夾中,且每一章儲存為一個檔案。
import re import os import requests from multiprocessing.dummy import Pool # 爬取的主網站地址 start_url = 'https://www.kanunu8.com/book2/11138/' """ 獲取網頁原始碼 :param url: 網址 :return: 網頁原始碼 """ def get_source(url): html = requests.get(url) return html.content.decode('gbk') # 這個網頁需要使用gbk方式解碼才能讓中文正常顯示 """ 獲取每一章連結,儲存到一個列表中並返回 :param html: 目錄頁原始碼 :return: 每章連結 """ def get_article_url(html): article_url_list = [] article_block = re.findall('正文(.*?)<div class="clear">', html, re.S)[0] article_url = re.findall('<a href="(d*.html)" rel="external nofollow" rel="external nofollow" >', article_block, re.S) for url in article_url: article_url_list.append(start_url + url) return article_url_list """ 獲取每一章的正文並返回章節名和正文 :param html: 正文原始碼 :return: 章節名,正文 """ def get_article(html): chapter_name = re.findall('<h1>(.*?)<br>', html, re.S)[0] text_block = re.search('<p>(.*?)</p>', html, re.S).group(1) text_block = text_block.replace(' ', '') # 替換 網頁空格符 text_block = text_block.replace('<p>', '') # 替換 <p></p> 中的嵌入的 <p></p> 中的 <p> return chapter_name, text_block """ 將每一章儲存到本地 :param chapter: 章節名, 第X章 :param article: 正文內容 :return: None """ def save(chapter, article): os.makedirs('北歐眾神', exist_ok=True) # 如果沒有"北歐眾神"資料夾,就建立一個,如果有,則什麼都不做" with open(os.path.join('北歐眾神', chapter + '.txt'), 'w', encoding='utf-8') as f: f.write(article) """ 根據正文網址獲取正文原始碼,並呼叫get_article函數獲得正文內容最後儲存到本地 :param url: 正文網址 :return: None """ def query_article(url): article_html = get_source(url) chapter_name, article_text = get_article(article_html) # print(chapter_name) # print(article_text) save(chapter_name, article_text) if __name__ == '__main__': toc_html = get_source(start_url) toc_list = get_article_url(toc_html) pool = Pool(4) pool.map(query_article, toc_list)
# 爬取的主網站地址 start_url = 'https://www.kanunu8.com/book2/11138/' def get_source(url): html = requests.get(url) return html.content.decode('gbk') # 這個網頁需要使用gbk方式解碼才能讓中文正常顯示
這一部分並不難,主要就是指明需要爬取的網站,並通過 request.get()
的請求方式獲取網站,在通過 content.decode()
獲取網頁的解碼內容,其實就是獲取網頁的原始碼。
def get_article_url(html): article_url_list = [] # 根據正文鎖定每一章節的連結區域 article_block = re.findall('正文(.*?)<div class="clear">', html, re.S)[0] # 獲取到每一章的連結 article_url = re.findall('<a href="(d*.html)" rel="external nofollow" rel="external nofollow" >', article_block, re.S) for url in article_url: article_url_list.append(start_url + url) return
這裡需要獲取到每一章的連結,首先我們根據正文鎖定每一章節的連結區域,然後在連結區域中獲取到每一章的連結,形成列表返回。
在獲取每章連結的時候,通過頁面原始碼可以發現均為數位開頭
,.html結尾
,於是利用正則 (d*.html)
匹配即可:
def get_article(html): chapter_name = re.findall('<h1>(.*?)<br>', html, re.S)[0] text_block = re.search('<p>(.*?)</p>', html, re.S).group(1) text_block = text_block.replace(' ', '') # 替換 網頁空格符 text_block = text_block.replace('<p>', '') # 替換 <p></p> 中的嵌入的 <p></p> 中的 <p> return chapter_name,
這裡利用正則分別匹配出每章的標題和正文內容:
格式化後:
""" 將每一章儲存到本地 :param chapter: 章節名, 第X章 :param article: 正文內容 :return: None """ def save(chapter, article): os.makedirs('北歐眾神', exist_ok=True) # 如果沒有"北歐眾神"資料夾,就建立一個,如果有,則什麼都不做" with open(os.path.join('北歐眾神', chapter + '.txt'), 'w', encoding='utf-8') as f: f.write(article)
這裡獲取到我們處理好的文章標題及內容,並將其寫入本地磁碟。首先建立資料夾,然後開啟資料夾以 章節名
+.txt
結尾儲存每章內容。
""" 根據正文網址獲取正文原始碼,並呼叫get_article函數獲得正文內容最後儲存到本地 :param url: 正文網址 :return: None """ def query_article(url): article_html = get_source(url) chapter_name, article_text = get_article(article_html) # print(chapter_name) # print(article_text) save(chapter_name, article_text) if __name__ == '__main__': toc_html = get_source(start_url) toc_list = get_article_url(toc_html) pool = Pool(4) pool.map(query_article, toc_list)
這裡 query_article
呼叫 get_source
、get_article
函數獲取以上分析的內容,再呼叫 save
函數進行本地儲存,主入口main中建立執行緒池,包含4個執行緒。
map()方法
,可以讓執行緒池裡面的所有執行緒都“同時”執行一個函數。 同時map()
方法接收兩個引數,第1個引數是函數名,第2個引數是一個列表。這裡我們需要對每一個章節進行爬取,所以應該是遍歷章節連結的列表
(呼叫 get_article_url
獲取),執行 query_article
方法進行爬取儲存。
最後執行程式即可!
到此這篇關於Python 多執行緒爬取案例的文章就介紹到這了,更多相關Python 多執行緒爬取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45