1. 程式人生 > 其它 >Python爬蟲教程:執行緒池和程序池

Python爬蟲教程:執行緒池和程序池

技術標籤:程式語言python

####一、需求

最近準備爬取某電商網站的資料,先不考慮代理、分散式,先說效率問題(當然你要是請求的太快就會被封掉,親測,400個請求過去,伺服器直接拒絕連線,心碎),步入正題。一般情況下小白的我們第一個想到的是for迴圈,這個可是單執行緒啊。那我們考慮for迴圈直接開他個5個執行緒,問題來了,如果有一個url請求還沒有回來,後面的就乾等,這麼用多執行緒等於沒用,到處貼創可貼。

二、效能考慮

確定要用多執行緒或者多程序了,那我們到底是用多執行緒還是多程序,有些人對多程序和多執行緒有一定的偏見,就因為python的GIL鎖,下面我們說一下這兩個東西的差別。

三、多執行緒:

一般情況下我們啟動一個.py檔案,就等於啟動了一個程序,一個程序裡面預設有一個執行緒工作,我們使用的多執行緒的意思就是在一個程序裡面啟用多個執行緒。但問題來了,為什麼要使用多執行緒呢?我知道啟動一個程序的時候需要建立一些記憶體空間,就相當於一間房子,我們要在這個房子裡面幹活,你可以想一個人就等於一個執行緒,你房子裡面有10個人的空間跟有20個人的空間,正常情況下是不一樣的,因為我們知道執行緒和執行緒之間預設是可以通訊的(程序之間預設是不可以通訊的,不過可以用技術實現,比如說管道)。可以多執行緒為了保證計算資料的正確性,所以出現了GIL鎖,保證同一時間只能有一個執行緒在計算。

GIL鎖你可以基本理解為,比如在這個房間裡要算一筆賬,在同一時間內只能有一個人在算這筆賬,想一個問題,如果這筆賬5個人就能算清楚,我需要10平米的房間就行,那為什麼要請10個人,花20平米呢?所以並不是開的執行緒越多越好。

但是,但是,但是,注意大家不用動腦筋(CPU計算)算這筆賬的時候可以去幹別的事(比如說5個人分工,各算一部分),比如說各自把自己算完後的結果記錄在賬本上以便後面對賬,這個的話每個人都有自己的賬本,所以多執行緒適合IO操作,記住了就算是適合IO操作,也不代表說人越多越好,所以這個量還是得根據實際情況而定。

示例:

執行緒池

 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)

四、多程序:

上面我們介紹了多執行緒(執行緒池),現在我們聊聊程序池,我們知道一個程序佔用一個CPU,現在的配置CPU一般都是4核,我們啟動兩個程序就是分別在兩個CPU裡面(兩個核心)各執行一個程序,我知道程序裡面才有執行緒,預設是一個。

但是有個缺點,按照上面的說法,開兩個程序佔用的記憶體空間是開一個程序佔用記憶體空間的2倍。CPU就佔用了2個核,電腦還得幹別的事兒對吧,不能冒冒失失瞎用。開的太多是不是其他程式就得等著,我們思考一下,佔用這麼多的記憶體空間,利用了多個CPU的優點為了什麼?CPU是用來做什麼的?沒錯就是用來計算的,所以在CPU密集運算的情況下建議用多程序。注意,具體要開幾個程序,根據機器的實際配置和實際生產情況而定。

程序池

'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
 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)

總結:

1、多執行緒適合IO密集型程式

2、多程序適合CPU密集運算型程式

五、協程:

**協程:**又稱微執行緒纖程。英文名Coroutine。那協程到底是個什麼東西,通俗的講就是比執行緒還要小的執行緒,所以才叫微執行緒。

**主要作用:**有人要問了,在python中執行緒是原子操作(意思就是說一句話或者一個動作就能搞定的操作或者計算),怎麼還有個叫協程的呢?

優點:

1、使用高併發、高擴充套件、低效能的;一個CPU支援上萬的協程都不是問題。所以很適合用於高併發處理。

2、無需執行緒的上下文切換開銷(乍一看,什麼意思呢?我們都知道python實際上是就是單執行緒,那都是怎麼實現高併發操作呢,就是CPU高速的切換,每個任務都幹一點,最後看上去是一起完事兒的,肉眼感覺就是多執行緒、多程序)

缺點:

1、無法利用CPU的多核優點,這個好理解,程序裡面包含執行緒,而協程就是細分後的執行緒,也就是說一個程序裡面首先是執行緒其後才是協程,那肯定是用不了多核了,不過可以多程序配合,使用CPU的密集運算,平時我們用不到。

2、一般情況下用的比較多的是asyncio或者是gevent這兩個技術實現協程,asyncio是python自帶的技術,gevent第三方庫,個人比較喜歡gevent這個技術。

gevent:

安裝:gevent需要安裝greenlet,因為它是使用到了greenlet這個庫。

pip3 install greenlet
pip3 install gevent

1、gevent的基本實現,按照下面的寫法,程式啟動後將會開啟許許多多的協程,反而特別影響效能。

gevent+requests

'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
 import requests
 import gevent
 from gevent import monkey
 
 #把當前的IO操作,打上標記,以便於gevent能檢測出來實現非同步(否則還是序列)
 monkey.patch_all()
  
 def task(url):
     '''
     1、request發起請求
     :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、有一個改進版本,就是可以設定到底讓它一次發起多少個請求(被忘了,協程=高並發現實之一)。其實裡面就是利用gevnet下的pool模組裡面的Pool控制每次請求的數量。

gevent+reqeust+Pool(控制每次請求數量)

 import requests
 import gevent
 from gevent import monkey
 from gevent.pool import Pool
 
 #把當前的IO操作,打上標記,以便於gevent能檢測出來實現非同步(否則還是序列)
 monkey.patch_all() 
 
 def task(url):
     '''
     1、request發起請求
     :param url:
     :return:
     '''
     response = requests.get(url)
     print(response.status_code)
     
    
 #控制最多一次向遠端提交多少個請求,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這肯定是沒法子,但是,我們上面寫的這個改進版還是有點麻煩,所以就有人寫了100多行程式碼把它們給搞到了一起,對就是搞到了一起,叫grequests,就是前者兩個技術的結合。

pip3 install grequests

這個版本是不是特別變態,直接把requests、greenlet、gevent、Pool都省的匯入了,但是裝還是要裝的,有人說從下面程式碼中我沒看到Pool的引數啊,grequests.map(request_list,size=5),size就是你要同時開幾個協程,還有引數你得點進去看,是不是很牛,很輕鬆

grequests

'''
遇到問題沒人解答?小編建立了一個Python學習交流QQ群:778463939
尋找有志同道合的小夥伴,互幫互助,群裡還有不錯的視訊學習教程和PDF電子書!
'''
 import grequests
 
 request_list = [
     grequests.get('https://www.baidu.com'),
     grequests.get('http://www.sina.com.cn'),
     grequests.get('https://news.baidu.com'),
 ]
 # ##### 執行並獲取響應列表 #####
 response_list = grequests.map(request_list,size=5)
 print(response_list)

結果返回一個列表,你可以再迭代一下就行了。
在這裡插入圖片描述