python爬蟲之線程池和進(jìn)程池功能與用法詳解
本文實(shí)例講述了python爬蟲之線程池和進(jìn)程池功能與用法。分享給大家供大家參考,具體如下:
一、需求
最近準(zhǔn)備爬取某電商網(wǎng)站的數(shù)據(jù),先不考慮代理、分布式,先說(shuō)效率問(wèn)題(當(dāng)然你要是請(qǐng)求的太快就會(huì)被封掉,親測(cè),400個(gè)請(qǐng)求過(guò)去,服務(wù)器直接拒絕連接,心碎),步入正題。一般情況下小白的我們第一個(gè)想到的是for循環(huán),這個(gè)可是單線程啊。那我們考慮for循環(huán)直接開他個(gè)5個(gè)線程,問(wèn)題來(lái)了,如果有一個(gè)url請(qǐng)求還沒(méi)有回來(lái),后面的就干等,這么用多線程等于沒(méi)用,到處貼創(chuàng)可貼。
二、性能考慮
確定要用多線程或者多進(jìn)程了,那我們到底是用多線程還是多進(jìn)程,有些人對(duì)多進(jìn)程和多線程有一定的偏見(jiàn),就因?yàn)閜ython的GIL鎖,下面我們說(shuō)一下這兩個(gè)東西的差別。
三、多線程:
一般情況下我們啟動(dòng)一個(gè).py文件,就等于啟動(dòng)了一個(gè)進(jìn)程,一個(gè)進(jìn)程里面默認(rèn)有一個(gè)線程工作,我們使用的多線程的意思就是在一個(gè)進(jìn)程里面啟用多個(gè)線程。但問(wèn)題來(lái)了,為什么要使用多線程呢?我知道啟動(dòng)一個(gè)進(jìn)程的時(shí)候需要?jiǎng)?chuàng)建一些內(nèi)存空間,就相當(dāng)于一間房子,我們要在這個(gè)房子里面干活,你可以想一個(gè)人就等于一個(gè)線程,你房子里面有10個(gè)人的空間跟有20個(gè)人的空間,正常情況下是不一樣的,因?yàn)槲覀冎谰€程和線程之間默認(rèn)是可以通信的(進(jìn)程之間默認(rèn)是不可以通信的,不過(guò)可以用技術(shù)實(shí)現(xiàn),比如說(shuō)管道)。可以多線程為了保證計(jì)算數(shù)據(jù)的正確性,所以出現(xiàn)了GIL鎖,保證同一時(shí)間只能有一個(gè)線程在計(jì)算。GIL鎖你可以基本理解為,比如在這個(gè)房間里要算一筆賬,在同一時(shí)間內(nèi)只能有一個(gè)人在算這筆賬,想一個(gè)問(wèn)題,如果這筆賬5個(gè)人就能算清楚,我需要10平米的房間就行,那為什么要請(qǐng)10個(gè)人,花20平米呢?所以并不是開的線程越多越好。但是,但是,但是,注意大家不用動(dòng)腦筋(CPU計(jì)算)算這筆賬的時(shí)候可以去干別的事(比如說(shuō)5個(gè)人分工,各算一部分),比如說(shuō)各自把自己算完后的結(jié)果記錄在賬本上以便后面對(duì)賬,這個(gè)的話每個(gè)人都有自己的賬本,所以多線程適合IO操作,記住了就算是適合IO操作,也不代表說(shuō)人越多越好,所以這個(gè)量還是得根據(jù)實(shí)際情況而定。
線程池示例:
import requests from concurrent.futures import ThreadPoolExecutor urls_list = [ 'https://www.baidu.com', 'http://www.gaosiedu.com', 'https://www.jd.com', 'https://www.taobao.com', 'https://news.baidu.com', ] pool = ThreadPoolExecutor(3) def request(url): response = requests.get(url) return response def read_data(future,*args,**kwargs): response = future.result() response.encoding = 'utf-8' print(response.status_code,response.url) def main(): for url in urls_list: done = pool.submit(request,url) done.add_done_callback(read_data) if __name__ == '__main__': main() pool.shutdown(wait=True)
四、多進(jìn)程:
上面我們介紹了多線程(線程池),現(xiàn)在我們聊聊進(jìn)程池,我們知道一個(gè)進(jìn)程占用一個(gè)CPU,現(xiàn)在的配置CPU一般都是4核,我們啟動(dòng)兩個(gè)進(jìn)程就是分別在兩個(gè)CPU里面(兩個(gè)內(nèi)核)各運(yùn)行一個(gè)進(jìn)程,我知道進(jìn)程里面才有線程,默認(rèn)是一個(gè)。但是有個(gè)缺點(diǎn),按照上面的說(shuō)法,開兩個(gè)進(jìn)程占用的內(nèi)存空間是開一個(gè)進(jìn)程占用內(nèi)存空間的2倍。CPU就占用了2個(gè)核,電腦還得干別的事兒對(duì)吧,不能冒冒失失瞎用。開的太多是不是其他程序就得等著,我們思考一下,占用這么多的內(nèi)存空間,利用了多個(gè)CPU的優(yōu)點(diǎn)為了什么?CPU是用來(lái)做什么的?沒(méi)錯(cuò)就是用來(lái)計(jì)算的,所以在CPU密集運(yùn)算的情況下建議用多進(jìn)程。注意,具體要開幾個(gè)進(jìn)程,根據(jù)機(jī)器的實(shí)際配置和實(shí)際生產(chǎn)情況而定。
進(jìn)程池
import requests from concurrent.futures import ProcessPoolExecutor urls_list = [ 'https://www.baidu.com', 'http://www.gaosiedu.com', 'https://www.jd.com', 'https://www.taobao.com', 'https://news.baidu.com', ] pool = ProcessPoolExecutor(3) def request(url): response = requests.get(url) return response def read_data(future,*args,**kwargs): response = future.result() response.encoding = 'utf-8' print(response.status_code,response.url) def main(): for url in urls_list: done = pool.submit(request,url) done.add_done_callback(read_data) if __name__ == '__main__': main() pool.shutdown(wait=True)
總結(jié):
1、多線程適合IO密集型程序
2、多進(jìn)程適合CPU密集運(yùn)算型程序
五、協(xié)程:
協(xié)程:又稱微線程纖程。英文名Coroutine。那協(xié)程到底是個(gè)什么東西,通俗的講就是比線程還要小的線程,所以才叫微線程。
主要作用:有人要問(wèn)了,在python中線程是原子操作(意思就是說(shuō)一句話或者一個(gè)動(dòng)作就能搞定的操作或者計(jì)算),怎么還有個(gè)叫協(xié)程的呢?
優(yōu)點(diǎn):
1、使用高并發(fā)、高擴(kuò)展、低性能的;一個(gè)CPU支持上萬(wàn)的協(xié)程都不是問(wèn)題。所以很適合用于高并發(fā)處理。
2、無(wú)需線程的上下文切換開銷(乍一看,什么意思呢?我們都知道python實(shí)際上是就是單線程,那都是怎么實(shí)現(xiàn)高并發(fā)操作呢,就是CPU高速的切換,每個(gè)任務(wù)都干一點(diǎn),最后看上去是一起完事兒的,肉眼感覺(jué)就是多線程、多進(jìn)程)
缺點(diǎn):
1、無(wú)法利用CPU的多核優(yōu)點(diǎn),這個(gè)好理解,進(jìn)程里面包含線程,而協(xié)程就是細(xì)分后的線程,也就是說(shuō)一個(gè)進(jìn)程里面首先是線程其后才是協(xié)程,那肯定是用不了多核了,不過(guò)可以多進(jìn)程配合,使用CPU的密集運(yùn)算,平時(shí)我們用不到。
一般情況下用的比較多的是asyncio或者是gevent這兩個(gè)技術(shù)實(shí)現(xiàn)協(xié)程,asyncio是python自帶的技術(shù),gevent第三方庫(kù),個(gè)人比較喜歡gevent這個(gè)技術(shù)。
gevent:
安裝:gevent需要安裝greenlet,因?yàn)樗鞘褂玫搅薵reenlet這個(gè)庫(kù)。
pip3 install greenlet pip3 install gevent
1、gevent的基本實(shí)現(xiàn),按照下面的寫法,程序啟動(dòng)后將會(huì)開啟許許多多的協(xié)程,反而特別影響性能。
gevent+requests:
import requests import gevent from gevent import monkey #把當(dāng)前的IO操作,打上標(biāo)記,以便于gevent能檢測(cè)出來(lái)實(shí)現(xiàn)異步(否則還是串行) monkey.patch_all() def task(url): ''' 1、request發(fā)起請(qǐng)求 :param url: :return: ''' response = requests.get(url) print(response.status_code) gevent.joinall([ gevent.spawn(task,url='https://www.baidu.com'), gevent.spawn(task,url='http://www.sina.com.cn'), gevent.spawn(task,url='https://news.baidu.com'), ])
2、有一個(gè)改進(jìn)版本,就是可以設(shè)置到底讓它一次發(fā)起多少個(gè)請(qǐng)求(被忘了,協(xié)程=高并發(fā)現(xiàn)實(shí)之一)。其實(shí)里面就是利用gevnet下的pool模塊里面的Pool控制每次請(qǐng)求的數(shù)量。
gevent+reqeust+Pool(控制每次請(qǐng)求數(shù)量)
import requests import gevent from gevent import monkey from gevent.pool import Pool #把當(dāng)前的IO操作,打上標(biāo)記,以便于gevent能檢測(cè)出來(lái)實(shí)現(xiàn)異步(否則還是串行) monkey.patch_all() def task(url): ''' 1、request發(fā)起請(qǐng)求 :param url: :return: ''' response = requests.get(url) print(response.status_code) #控制最多一次向遠(yuǎn)程提交多少個(gè)請(qǐng)求,None代表不限制 pool = Pool(5) gevent.joinall([ pool.spawn(task,url='https://www.baidu.com'), pool.spawn(task,url='http://www.sina.com.cn'), pool.spawn(task,url='https://news.baidu.com'), ])
3、還有一版本,每次我們都要裝greenlet和gevent這肯定是沒(méi)法子,但是,我們上面寫的這個(gè)改進(jìn)版還是有點(diǎn)麻煩,所以就有人寫了100多行代碼把它們給搞到了一起,對(duì)就是搞到了一起,叫g(shù)requests,就是前者兩個(gè)技術(shù)的結(jié)合。
pip3 install grequests
這個(gè)版本是不是特別變態(tài),直接把requests、greenlet、gevent、Pool都省的導(dǎo)入了,但是裝還是要裝的,有人說(shuō)從下面代碼中我沒(méi)看到Pool的參數(shù)啊,grequests.map(request_list,size=5),size就是你要同時(shí)開幾個(gè)協(xié)程,還有參數(shù)你得點(diǎn)進(jìn)去看,是不是很牛,很輕松
grequests:
import grequests request_list = [ grequests.get('https://www.baidu.com'), grequests.get('http://www.sina.com.cn'), grequests.get('https://news.baidu.com'), ] # ##### 執(zhí)行并獲取響應(yīng)列表 ##### response_list = grequests.map(request_list,size=5) print(response_list)
結(jié)果返回一個(gè)列表,你可以再迭代一下就行了。
更多關(guān)于Python相關(guān)內(nèi)容可查看本站專題:《Python Socket編程技巧總結(jié)》、《Python正則表達(dá)式用法總結(jié)》、《Python數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Python函數(shù)使用技巧總結(jié)》、《Python字符串操作技巧匯總》、《Python入門與進(jìn)階經(jīng)典教程》及《Python文件與目錄操作技巧匯總》
希望本文所述對(duì)大家Python程序設(shè)計(jì)有所幫助。
相關(guān)文章
Python實(shí)現(xiàn)批量下載文件的示例代碼
下載文件是我們?cè)谌粘9ぷ髦谐3R龅囊患虑?當(dāng)我們需要從互聯(lián)網(wǎng)上批量下載大量文件時(shí),手動(dòng)一個(gè)一個(gè)去下載顯然不夠高效,所以本文為大家介紹一下如何利用python批量下載文件吧2023-11-11python實(shí)現(xiàn)循環(huán)語(yǔ)句1到100累和
這篇文章主要介紹了python循環(huán)語(yǔ)句1到100累和方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05python學(xué)生管理系統(tǒng)代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了python學(xué)生管理系統(tǒng)代碼實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03tensorflow使用freeze_graph.py將ckpt轉(zhuǎn)為pb文件的方法
這篇文章主要介紹了tensorflow使用freeze_graph.py將ckpt轉(zhuǎn)為pb文件的方法,需要的朋友可以參考下2020-04-0410種檢測(cè)Python程序運(yùn)行時(shí)間、CPU和內(nèi)存占用的方法
這篇文章主要介紹了10種檢測(cè)Python程序運(yùn)行時(shí)間、CPU和內(nèi)存占用的方法,包括利用Python裝飾器或是外部的Unix Shell命令等,需要的朋友可以參考下2015-04-04Python創(chuàng)建一個(gè)空的dataframe,并循環(huán)賦值的方法
今天小編就為大家分享一篇Python創(chuàng)建一個(gè)空的dataframe,并循環(huán)賦值的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-11-11零基礎(chǔ)寫python爬蟲之抓取百度貼吧并存儲(chǔ)到本地txt文件改進(jìn)版
前面已經(jīng)發(fā)了一篇關(guān)于百度貼吧抓取的代碼,今天我們來(lái)看下代碼的改進(jìn)版,參考了上篇抓取糗事百科的思路,給需要的小伙伴們參考下吧2014-11-11