先從乙個爬蟲開始,請看下面的**
import time
def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
time.sleep(sleep_time)
print('ok {}'.format(url))
def main(urls):
for url in urls:
crawl_page(url)
%time main(['url_1', 'url_2', 'url_3', 'url_4'])
########## 輸出 ##########
crawling url_1
ok url_1
crawling url_2
ok url_2
crawling url_3
ok url_3
crawling url_4
ok url_4
wall time: 10 s
這是乙個很簡單的爬蟲,main() 函式執行時,調取 crawl_page() 函式進行網路通訊,經過若干秒等待後收到結果,然後執行下乙個。
看起來很簡單,但你仔細一算,它也占用了不少時間,五個頁面分別用了 1 秒到 4 秒的時間,加起來一共用了 10 秒。這顯然效率低下,該怎麼優化呢?
於是,乙個很簡單的思路出現了——我們這種爬取操作,完全可以併發化。我們就來看看使用協程怎麼寫。
import asyncio
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('ok {}'.format(url))
async def main(urls):
for url in urls:
await crawl_page(url)
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
########## 輸出 ##########
crawling url_1
ok url_1
crawling url_2
ok url_2
crawling url_3
ok url_3
crawling url_4
ok url_4
wall time: 10 s
首先來看 import asyncio,這個庫包含了大部分我們實現協程所需的魔法工具。
async 修飾詞宣告非同步函式,於是,這裡的 crawl_page 和 main 都變成了非同步函式。而呼叫非同步函式,我們便可得到乙個協程物件(coroutine object)。
舉個例子,如果你 print(crawl_page('')),便會輸出,提示你這是乙個 python 的協程物件,而並不會真正執行這個函式。
再來說說協程的執行。執行協程有多種方法,這裡我介紹一下常用的三種。
首先,我們可以通過 await 來呼叫。await 執行的效果,和 python 正常執行是一樣的,也就是說程式會阻塞在這裡,進入被呼叫的協程函式,執行完畢返回後再繼續,而這也是 await 的字面意思。**中 await asyncio.sleep(sleep_time) 會在這裡休息若干秒,await crawl_page(url) 則會執行 crawl_page() 函式
import asyncio
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('ok {}'.format(url))
async def main(urls):
tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
for task in tasks:
await task
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
########## 輸出 ##########
crawling url_1
crawling url_2
crawling url_3
crawling url_4
ok url_1
ok url_2
ok url_3
ok url_4
wall time: 3.99 s
我們有了協程物件後,便可以通過 asyncio.create_task 來建立任務。
任務建立後很快就會被排程執行,這樣,我們的**也不會阻塞在任務這裡。
所以,我們要等所有任務都結束才行,用for task in tasks: await task 即可。
這次,你就看到效果了吧,結果顯示,執行總時長等於執行時間最長的爬蟲。
當然,你也可以想一想,這裡用多執行緒應該怎麼寫?而如果需要爬取的頁面有上萬個又該怎麼辦呢?再對比下協程的寫法,誰更清晰自是一目了然。其實,對於執行 tasks,還有另一種做法:
import asyncio
async def crawl_page(url):
print('crawling {}'.format(url))
sleep_time = int(url.split('_')[-1])
await asyncio.sleep(sleep_time)
print('ok {}'.format(url))
async def main(urls):
tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
await asyncio.gather(*tasks)
%time asyncio.run(main(['url_1', 'url_2', 'url_3', 'url_4']))
########## 輸出 ##########
crawling url_1
crawling url_2
crawling url_3
crawling url_4
ok url_1
ok url_2
ok url_3
ok url_4
wall time: 4.01 s
```1
Python 程序 執行緒 協程
程序和執行緒之間的關係 執行緒是屬於程序的,執行緒執行在程序空間內,同一程序所產生的執行緒共享同一記憶體空間,當程序退出時該程序所產生的執行緒都會被強制退出並清除。執行緒可與屬於同一程序的其它執行緒共享程序所擁有的全部資源,但是其本身基本上不擁有系統資源,只擁有一點在執行中必不可少的資訊 如程式計數...
Python 程序,執行緒, 協程
程序是系統進行資源分配和排程的乙個獨立單位 最小單位 程序的幾個狀態 空 新建 建立執行乙個程式的新程序,可能的事件有 新的批處理作業 互動登入 終端使用者登入到系統 作業系統因為提供一項服務而建立 由現有的程序派生等。新建 就緒 作業系統準備好再接納乙個程序時,把乙個程序從新建態轉換為就緒態。就緒...
python 執行緒程序協程(二)
1.程序的基本使用 from multiprocessing import process class myprocess process def run self print name pid format self.name,self.pid if name main p myprocess p...