自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python網(wǎng)絡(luò)爬蟲(chóng)的同步和異步

開(kāi)發(fā) 后端
同步就是讓子任務(wù)串行,而異步有點(diǎn)影分身之術(shù),但在任意時(shí)間點(diǎn),真身只有一個(gè),子任務(wù)并不是真正的并行,而是充分利用了碎片化的時(shí)間,讓程序不要浪費(fèi)在等待上。這就是異步,效率杠桿的。

一、同步與異步

 

  1. #同步編程(同一時(shí)間只能做一件事,做完了才能做下一件事情)  
  2. <-a_url-><-b_url-><-c_url->  
  3. #異步編程 (可以近似的理解成同一時(shí)間有多個(gè)事情在做,但有先后)  
  4. <-a_url->  
  5.   <-b_url->  
  6.     <-c_url->  
  7.       <-d_url->  
  8.         <-e_url->  
  9.           <-f_url->  
  10.             <-g_url->  
  11.               <-h_url->  
  12.                 <--i_url-->  
  13.                   <--j_url--> 

 

模板

 

  1. import asyncio 
  2.  
  3. #函數(shù)名:做現(xiàn)在的任務(wù)時(shí)不等待,能繼續(xù)做別的任務(wù)。 
  4.  
  5. async def donow_meantime_dontwait(url): 
  6.  
  7.     response = await requests.get(url) 
  8.  
  9. #函數(shù)名:快速高效的做任務(wù) 
  10.  
  11. async def fast_do_your_thing(): 
  12.  
  13.     await asyncio.wait([donow_meantime_dontwait(url) for url in urls]) 
  14.  
  15. #下面兩行都是套路,記住就好 
  16.  
  17. loop = asyncio.get_event_loop() 
  18.  
  19. loop.run_until_complete(fast_do_your_thing()) 

tips:

await表達(dá)式中的對(duì)象必須是awaitable

requests不支持非阻塞

aiohttp是用于異步請(qǐng)求的庫(kù)

代碼

 

  1. import asyncio 
  2. import requests 
  3. import time 
  4. import aiohttp 
  5. urls = ['https://book.douban.com/tag/小說(shuō)','https://book.douban.com/tag/科幻'
  6.         'https://book.douban.com/tag/漫畫(huà)','https://book.douban.com/tag/奇幻'
  7.         'https://book.douban.com/tag/歷史','https://book.douban.com/tag/經(jīng)濟(jì)學(xué)'
  8. async def requests_meantime_dont_wait(url): 
  9.     print(url) 
  10.     async with aiohttp.ClientSession() as session: 
  11.         async with session.get(url) as resp: 
  12.             print(resp.status) 
  13.             print("{url} 得到響應(yīng)".format(url=url)) 
  14. async def fast_requsts(urls): 
  15.     start = time.time() 
  16.     await asyncio.wait([requests_meantime_dont_wait(url) for url in urls]) 
  17.     end = time.time() 
  18.     print("Complete in {} seconds".format(end - start)) 
  19. loop = asyncio.get_event_loop() 
  20. loop.run_until_complete(fast_requsts(urls)) 

gevent簡(jiǎn)介

gevent是一個(gè)python的并發(fā)庫(kù),它為各種并發(fā)和網(wǎng)絡(luò)相關(guān)的任務(wù)提供了整潔的API。

gevent中用到的主要模式是greenlet,它是以C擴(kuò)展模塊形式接入Python的輕量級(jí)協(xié)程。 greenlet全部運(yùn)行在主程序操作系統(tǒng)進(jìn)程的內(nèi)部,但它們被協(xié)作式地調(diào)度。

猴子補(bǔ)丁

requests庫(kù)是阻塞式的,為了將requests同步更改為異步。只有將requests庫(kù)阻塞式更改為非阻塞,異步操作才能實(shí)現(xiàn)。

而gevent庫(kù)中的猴子補(bǔ)?。╩onkey patch),gevent能夠修改標(biāo)準(zhǔn)庫(kù)里面大部分的阻塞式系統(tǒng)調(diào)用。這樣在不改變?cè)写a的情況下,將應(yīng)用的阻塞式方法,變成協(xié)程式的(異步)。

代碼

 

  1. from gevent import monkey  
  2. import gevent  
  3. import requests  
  4. import time  
  5.  
  6. monkey.patch_all()  
  7. def req(url):  
  8.     print(url)  
  9.     resp = requests.get(url)  
  10.     print(resp.status_code,url)  
  11.  
  12. def synchronous_times(urls): 
  13.  
  14.     """同步請(qǐng)求運(yùn)行時(shí)間"""  
  15.     start = time.time() 
  16.      for url in urls:  
  17.         req(url)  
  18.     end = time.time()  
  19.     print('同步執(zhí)行時(shí)間 {} s'.format(end-start))  
  20.  
  21. def asynchronous_times(urls):  
  22.     """異步請(qǐng)求運(yùn)行時(shí)間"""  
  23.     start = time.time()  
  24.     gevent.joinall([gevent.spawn(req,url) for url in urls])  
  25.     end = time.time()  
  26.     print('異步執(zhí)行時(shí)間 {} s'.format(end - start))  
  27.  
  28. urls = ['https://book.douban.com/tag/小說(shuō)','https://book.douban.com/tag/科幻' 
  29.         'https://book.douban.com/tag/漫畫(huà)','https://book.douban.com/tag/奇幻' 
  30.         'https://book.douban.com/tag/歷史','https://book.douban.com/tag/經(jīng)濟(jì)學(xué)' 
  31.  
  32. synchronous_times(urls)  
  33. asynchronous_times(urls) 

 

gevent:異步理論與實(shí)戰(zhàn)

gevent庫(kù)中使用的最核心的是Greenlet-一種用C寫(xiě)的輕量級(jí)python模塊。在任意時(shí)間,系統(tǒng)只能允許一個(gè)Greenlet處于運(yùn)行狀態(tài)

一個(gè)greenlet遇到IO操作時(shí),比如訪問(wèn)網(wǎng)絡(luò),就自動(dòng)切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r(shí)候切換回來(lái)繼續(xù)執(zhí)行。由于IO操作非常耗時(shí),經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動(dòng)切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行,而不是等待IO。

串行和異步

高并發(fā)的核心是讓一個(gè)大的任務(wù)分成一批子任務(wù),并且子任務(wù)會(huì)被被系統(tǒng)高效率的調(diào)度,實(shí)現(xiàn)同步或者異步。在兩個(gè)子任務(wù)之間切換,也就是經(jīng)常說(shuō)到的上下文切換。

同步就是讓子任務(wù)串行,而異步有點(diǎn)影分身之術(shù),但在任意時(shí)間點(diǎn),真身只有一個(gè),子任務(wù)并不是真正的并行,而是充分利用了碎片化的時(shí)間,讓程序不要浪費(fèi)在等待上。這就是異步,效率杠桿的。

gevent中的上下文切換是通過(guò)yield實(shí)現(xiàn)。在這個(gè)例子中,我們會(huì)有兩個(gè)子任務(wù),互相利用對(duì)方等待的時(shí)間做自己的事情。這里我們使用gevent.sleep(0)代表程序會(huì)在這里停0秒。

 

  1. import gevent  
  2. def foo():  
  3.     print('Running in foo' 
  4.     gevent.sleep(0)  
  5.     print('Explicit context switch to foo again' 
  6.  
  7. def bar():  
  8.     print('Explicit context to bar' 
  9.     gevent.sleep(0)  
  10.     print('Implicit context switch back to bar' 
  11.  
  12. gevent.joinall([  
  13.     gevent.spawn(foo),  
  14.     gevent.spawn(bar) 
  15.  
  16.     ]) 

 

運(yùn)行的順序:

 

  1. Running in foo  
  2. Explicit context to bar  
  3. Explicit context switch to foo again  
  4. Implicit context switch back to bar 

 

同步異步的順序問(wèn)題

同步運(yùn)行就是串行,123456...,但是異步的順序是隨機(jī)的任意的(根據(jù)子任務(wù)消耗的時(shí)間而定)

代碼

 

  1. import gevent  
  2. import random  
  3. def task(pid):  
  4.     "" 
  5.     Some non-deterministic task  
  6.     "" 
  7.     gevent.sleep(random.randint(0,2)*0.001)  
  8.     print('Task %s done' % pid)  
  9.  
  10.  
  11. #同步(結(jié)果更像串行)  
  12. def synchronous():  
  13.     for i in range(1,10):  
  14.         task(i)  
  15.  
  16.  
  17. #異步(結(jié)果更像亂步)  
  18. def asynchronous():  
  19.     threads = [gevent.spawn(task, i) for i in range(10)]  
  20.     gevent.joinall(threads)  
  21.  
  22.  
  23. print('Synchronous同步:' 
  24. synchronous()  
  25.  
  26.  
  27. print('Asynchronous異步:'
  28.  
  29. asynchronous() 

 

輸出

Synchronous同步:

 

  1. Task 1 done  
  2. Task 2 done  
  3. Task 3 done 
  4. Task 4 done  
  5. Task 5 done  
  6. Task 6 done  
  7. Task 7 done  
  8. Task 8 done  
  9. Task 9 done 

 

Asynchronous異步:

 

  1. Task 1 done  
  2. Task 5 done  
  3. Task 6 done  
  4. Task 2 done  
  5. Task 4 done  
  6. Task 7 done  
  7. Task 8 done  
  8. Task 9 done  
  9. Task 0 done  
  10. Task 3 done 

 

同步案例中所有的任務(wù)都是按照順序執(zhí)行,這導(dǎo)致主程序是阻塞式的(阻塞會(huì)暫停主程序的執(zhí)行)。

gevent.spawn會(huì)對(duì)傳入的任務(wù)(子任務(wù)集合)進(jìn)行進(jìn)行調(diào)度,gevent.joinall方法會(huì)阻塞當(dāng)前程序,除非所有的greenlet都執(zhí)行完畢,程序才會(huì)結(jié)束。

實(shí)戰(zhàn)

實(shí)現(xiàn)gevent到底怎么用,把異步訪問(wèn)得到的數(shù)據(jù)提取出來(lái)。

在有道詞典搜索框輸入“hello”按回車。觀察數(shù)據(jù)請(qǐng)求情況 觀察有道的url構(gòu)建。

分析url規(guī)律

 

  1. #url構(gòu)建只需要傳入word即可 
  2.  
  3. url = "http://dict.youdao.com/w/eng/{}/".format(word) 

 

解析網(wǎng)頁(yè)數(shù)據(jù)

 

  1. def fetch_word_info(word):  
  2.     url = "http://dict.youdao.com/w/eng/{}/".format(word)  
  3.  
  4.     resp = requests.get(url,headers=headers)  
  5.     doc = pq(resp.text)  
  6.     pros = ''  
  7.     for pro in doc.items('.baav .pronounce'):  
  8.         pros+=pro.text()  
  9.  
  10.     description = ''  
  11.     for li in doc.items('#phrsListTab .trans-container ul li'):  
  12.         description +=li.text()  
  13.  
  14.     return {'word':word,'音標(biāo)':pros,'注釋':description} 

 

因?yàn)閞equests庫(kù)在任何時(shí)候只允許有一個(gè)訪問(wèn)結(jié)束完全結(jié)束后,才能進(jìn)行下一次訪問(wèn)。無(wú)法通過(guò)正規(guī)途徑拓展成異步,因此這里使用了monkey補(bǔ)丁

同步代碼

 

  1. import requests  
  2. from pyquery import PyQuery as pq  
  3. import gevent  
  4. import time  
  5. import gevent.monkey  
  6. gevent.monkey.patch_all() 
  7.  
  8. words = ['good','bad','cool' 
  9.          'hot','nice','better' 
  10.          'head','up','down' 
  11.          'right','left','east' 
  12.  
  13. def synchronous():  
  14.     start = time.time()  
  15.     print('同步開(kāi)始了' 
  16.     for word in words:  
  17.         print(fetch_word_info(word))  
  18.     end = time.time()  
  19.     print("同步運(yùn)行時(shí)間: %s 秒" % str(end - start)) 
  20.  
  21.  
  22. #執(zhí)行同步  
  23. synchronous() 

 

異步代碼

 

  1. import requests  
  2. from pyquery import PyQuery as pq  
  3. import gevent  
  4. import time  
  5. import gevent.monkey  
  6. gevent.monkey.patch_all()  
  7.  
  8. words = ['good','bad','cool' 
  9.          'hot','nice','better' 
  10.          'head','up','down' 
  11.          'right','left','east' 
  12.  
  13. def asynchronous():  
  14.     start = time.time()  
  15.     print('異步開(kāi)始了' 
  16.     events = [gevent.spawn(fetch_word_info,word) for word in words]  
  17.     wordinfos = gevent.joinall(events)  
  18.     for wordinfo in wordinfos:  
  19.         #獲取到數(shù)據(jù)get方法  
  20.         print(wordinfo.get())  
  21.     end = time.time()  
  22.     print("異步運(yùn)行時(shí)間: %s 秒"%str(end-start))  
  23.  
  24. #執(zhí)行異步  
  25. asynchronous() 

 

我們可以對(duì)待爬網(wǎng)站實(shí)時(shí)異步訪問(wèn),速度會(huì)大大提高。我們現(xiàn)在是爬取12個(gè)詞語(yǔ)的信息,也就是說(shuō)一瞬間我們對(duì)網(wǎng)站訪問(wèn)了12次,這還沒(méi)啥問(wèn)題,假如爬10000+個(gè)詞語(yǔ),使用gevent的話,那幾秒鐘之內(nèi)就給網(wǎng)站一股腦的發(fā)請(qǐng)求,說(shuō)不定網(wǎng)站就把爬蟲(chóng)封了。

解決辦法

將列表等分為若干個(gè)子列表,分批爬取。舉例我們有一個(gè)數(shù)字列表(0-19),要均勻的等分為4份,也就是子列表有5個(gè)數(shù)。下面是我在stackoverflow查找到的列表等分方案:

方法1

 

  1. seqence = list(range(20)) 
  2.  
  3. size = 5 #子列表長(zhǎng)度 
  4.  
  5. output = [seqence[i:i+sizefor i in range(0, len(seqence), size)] 
  6.  
  7. print(output

 

方法2

 

  1. chunks = lambda seq, size: [seq[i: i+sizefor i in range(0, len(seq), size)] 
  2.  
  3. print(chunks(seq, 5)) 

 

方法3

 

  1. def chunks(seq,size):  
  2.     for i in range(0,len(seq), size):  
  3.         yield seq[i:i+size 
  4. prinT(chunks(seq,5))  
  5.     for  x  in chunks(req,5):  
  6.          print(x)  

 

數(shù)據(jù)量不大的情況下,選哪一種方法都可以。如果特別大,建議使用方法3.

動(dòng)手實(shí)現(xiàn)

 

  1. import requests  
  2. from pyquery import PyQuery as pq  
  3. import gevent  
  4. import time  
  5. import gevent.monkey  
  6. gevent.monkey.patch_all()
  7.  
  8. words = ['good','bad','cool' 
  9.          'hot','nice','better' 
  10.          'head','up','down' 
  11.          'right','left','east' 
  12.  
  13. def fetch_word_info(word):   
  14.     url = "http://dict.youdao.com/w/eng/{}/".format(word)  
  15.  
  16.     resp = requests.get(url,headers=headers)  
  17.     doc = pq(resp.text) 
  18.  
  19.  
  20.     pros = ''  
  21.     for pro in doc.items('.baav .pronounce'):  
  22.         pros+=pro.text()  
  23.  
  24.     description = ''  
  25.     for li in doc.items('#phrsListTab .trans-container ul li'):  
  26.         description +=li.text()  
  27.  
  28.     return {'word':word,'音標(biāo)':pros,'注釋':description}  
  29.  
  30.  
  31. def asynchronous(words):  
  32.     start = time.time()  
  33.     print('異步開(kāi)始了'  
  34.  
  35.     chunks = lambda seq, size: [seq[i: i + sizefor i in range(0, len(seq), size)] 
  36.  
  37.  
  38.     for subwords in chunks(words,3):  
  39.         events = [gevent.spawn(fetch_word_info, word) for word in subwords]   
  40.         wordinfos = gevent.joinall(events)  
  41.         for wordinfo in wordinfos:  
  42.             # 獲取到數(shù)據(jù)get方法  
  43.             print(wordinfo.get()) 
  44.  
  45.         time.sleep(1)   
  46.         end = time.time()  
  47.     print("異步運(yùn)行時(shí)間: %s 秒" % str(end - start))  
  48.  
  49. asynchronous(words)  

 

責(zé)任編輯:龐桂玉 來(lái)源: Python愛(ài)好者社區(qū)
相關(guān)推薦

2024-07-26 21:55:39

RustRESTfulAPI

2023-09-07 08:15:58

場(chǎng)景同步異步

2012-03-01 20:32:29

iOS

2021-03-11 11:32:40

Python同步異步

2021-04-02 11:05:57

Python同步異步

2020-09-25 18:10:06

Python 開(kāi)發(fā)編程語(yǔ)言

2009-08-21 10:28:21

C#異步方法C#同步方法

2023-08-30 08:43:42

asyncioaiohttp

2018-09-27 12:38:46

Python同步異步

2023-12-29 22:41:12

同步架構(gòu)業(yè)務(wù)

2009-10-20 16:48:30

C#委托

2024-03-08 12:17:39

網(wǎng)絡(luò)爬蟲(chóng)Python開(kāi)發(fā)

2018-11-30 09:30:46

aiohttp爬蟲(chóng)Python

2022-06-13 06:20:42

setStatereact18

2023-03-13 17:18:09

OkHttp同步異步

2019-06-11 09:06:22

網(wǎng)絡(luò)爬蟲(chóng)工具

2019-07-23 11:01:57

Python同步異步

2024-11-27 06:31:02

2017-06-20 09:07:22

uvloopPython網(wǎng)絡(luò)框架

2021-03-23 07:56:54

JS基礎(chǔ)同步異步編程EventLoop底層
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)