Python網路爬蟲中的同步與非同步樣本詳解,python爬蟲

來源:互聯網
上載者:User

Python網路爬蟲中的同步與非同步樣本詳解,python爬蟲

一、同步與非同步

#同步編程(同一時間只能做一件事,做完了才能做下一件事情)<-a_url-><-b_url-><-c_url->#非同步編程 (可以近似的理解成同一時間有多個事情在做,但有先後)<-a_url-> <-b_url-> <-c_url-> <-d_url-> <-e_url->  <-f_url->  <-g_url->  <-h_url->  <--i_url-->   <--j_url-->

模板

import asyncio#函數名:做現在的任務時不等待,能繼續做別的任務。async def donow_meantime_dontwait(url): response = await requests.get(url)#函數名:快速高效的做任務async def fast_do_your_thing(): await asyncio.wait([donow_meantime_dontwait(url) for url in urls])#下面兩行都是套路,記住就好loop = asyncio.get_event_loop()loop.run_until_complete(fast_do_your_thing())

tips:

  • await運算式中的對象必須是awaitable
  • requests不支援非阻塞
  • aiohttp是用於非同步請求的庫

代碼

import asyncioimport requestsimport timeimport aiohttpurls = ['https://book.douban.com/tag/小說','https://book.douban.com/tag/科幻', 'https://book.douban.com/tag/漫畫','https://book.douban.com/tag/奇幻', 'https://book.douban.com/tag/曆史','https://book.douban.com/tag/經濟學']async def requests_meantime_dont_wait(url): print(url) async with aiohttp.ClientSession() as session: async with session.get(url) as resp:  print(resp.status)  print("{url} 得到響應".format(url=url))async def fast_requsts(urls): start = time.time() await asyncio.wait([requests_meantime_dont_wait(url) for url in urls]) end = time.time() print("Complete in {} seconds".format(end - start))loop = asyncio.get_event_loop()loop.run_until_complete(fast_requsts(urls))

gevent簡介

gevent是一個python的並發庫,它為各種並發和網路相關的任務提供了整潔的API。

gevent中用到的主要模式是greenlet,它是以C擴充模組形式接入Python的輕量級協程。 greenlet全部運行在主程式作業系統進程的內部,但它們被協作式地調度。

猴子補丁

requests庫是阻塞式的,為了將requests同步更改為非同步。只有將requests庫阻塞式更改為非阻塞,非同步作業才能實現。

而gevent庫中的猴子補丁(monkey patch),gevent能夠修改標準庫裡面大部分的阻塞式系統調用。這樣在不改變原有代碼的情況下,將應用的阻塞式方法,變成協程式的(非同步)。

代碼

from gevent import monkeyimport geventimport requestsimport timemonkey.patch_all()def req(url): print(url) resp = requests.get(url) print(resp.status_code,url)def synchronous_times(urls): """同步請求已耗用時間""" start = time.time() for url in urls: req(url) end = time.time() print('同步執行時間 {} s'.format(end-start))def asynchronous_times(urls): """非同步請求已耗用時間""" start = time.time() gevent.joinall([gevent.spawn(req,url) for url in urls]) end = time.time() print('非同步執行時間 {} s'.format(end - start))urls = ['https://book.douban.com/tag/小說','https://book.douban.com/tag/科幻', 'https://book.douban.com/tag/漫畫','https://book.douban.com/tag/奇幻', 'https://book.douban.com/tag/曆史','https://book.douban.com/tag/經濟學']synchronous_times(urls)asynchronous_times(urls)

gevent:非同步理論與實戰

gevent庫中使用的最核心的是Greenlet-一種用C寫的輕量級python模組。在任意時間,系統只能允許一個Greenlet處於運行狀態

一個greenlet遇到IO操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。由於IO操作非常耗時,經常使程式處於等待狀態,有了gevent為我們自動切換協程,就保證總有greenlet在運行,而不是等待IO。

串列和非同步

高並發的核心是讓一個大的任務分成一批子任務,並且子任務會被被系統高效率的調度,實現同步或者非同步。在兩個子任務之間切換,也就是經常說到的環境切換。

同步就是讓子任務串列,而非同步有點影分身之術,但在任意時間點,真身只有一個,子任務並不是真正的並行,而是充分利用了片段化的時間,讓程式不要浪費在等待上。這就是非同步,效率槓桿的。

gevent中的環境切換是通過yield實現。在這個例子中,我們會有兩個子任務,互相利用對方等待的時間做自己的事情。這裡我們使用gevent.sleep(0)代表程式會在這裡停0秒。

import geventdef foo(): print('Running in foo') gevent.sleep(0) print('Explicit context switch to foo again')def bar(): print('Explicit context to bar') gevent.sleep(0) print('Implicit context switch back to bar')gevent.joinall([ gevent.spawn(foo), gevent.spawn(bar) ])

啟動並執行順序:

Running in fooExplicit context to barExplicit context switch to foo againImplicit context switch back to bar

同步非同步順序問題

同步運行就是串列,123456...,但是非同步順序是隨機的任意的(根據子任務消耗的時間而定)

代碼

import geventimport randomdef task(pid): """ Some non-deterministic task """ gevent.sleep(random.randint(0,2)*0.001) print('Task %s done' % pid)#同步(結果更像串列)def synchronous(): for i in range(1,10): task(i)#非同步(結果更像亂步)def asynchronous(): threads = [gevent.spawn(task, i) for i in range(10)] gevent.joinall(threads)print('Synchronous同步:')synchronous()print('Asynchronous非同步:')asynchronous()

輸出

Synchronous同步:Task 1 doneTask 2 doneTask 3 doneTask 4 doneTask 5 doneTask 6 doneTask 7 doneTask 8 doneTask 9 doneAsynchronous非同步:Task 1 doneTask 5 doneTask 6 doneTask 2 doneTask 4 doneTask 7 doneTask 8 doneTask 9 doneTask 0 doneTask 3 done

同步案例中所有的任務都是按照順序執行,這導致主程式是阻塞式的(阻塞會暫停主程式的執行)。

gevent.spawn會對傳入的任務(子任務集合)進行進行調度,gevent.joinall方法會阻塞當前程式,除非所有的greenlet都執行完畢,程式才會結束。

實戰

實現gevent到底怎麼用,把非同步訪問得到的資料提取出來。

在有道詞典搜尋方塊輸入“hello”按斷行符號。觀察資料請求情況 觀察有道的url構建。

分析url規律

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

解析網頁資料

def fetch_word_info(word): url = "http://dict.youdao.com/w/eng/{}/".format(word) resp = requests.get(url,headers=headers) doc = pq(resp.text) pros = '' for pro in doc.items('.baav .pronounce'):  pros+=pro.text() description = '' for li in doc.items('#phrsListTab .trans-container ul li'):  description +=li.text() return {'word':word,'音標':pros,'注釋':description}

因為requests庫在任何時候只允許有一個訪問結束完全結束後,才能進行下一次訪問。無法通過正規途徑拓展成非同步,因此這裡使用了monkey補丁

同步代碼

import requestsfrom pyquery import PyQuery as pqimport geventimport timeimport gevent.monkeygevent.monkey.patch_all()words = ['good','bad','cool',   'hot','nice','better',   'head','up','down',   'right','left','east']def synchronous(): start = time.time() print('同步開始了') for word in words:  print(fetch_word_info(word)) end = time.time() print("同步已耗用時間: %s 秒" % str(end - start))#執行同步synchronous()

非同步代碼

import requestsfrom pyquery import PyQuery as pqimport geventimport timeimport gevent.monkeygevent.monkey.patch_all()words = ['good','bad','cool',   'hot','nice','better',   'head','up','down',   'right','left','east']def asynchronous(): start = time.time() print('非同步開始了') events = [gevent.spawn(fetch_word_info,word) for word in words] wordinfos = gevent.joinall(events) for wordinfo in wordinfos:  #擷取到資料get方法  print(wordinfo.get()) end = time.time() print("非同步已耗用時間: %s 秒"%str(end-start))#執行非同步asynchronous()

我們可以對待耙梳站即時非同步訪問,速度會大大提高。我們現在是爬取12個詞語的資訊,也就是說一瞬間我們對網站訪問了12次,這還沒啥問題,假如爬10000+個詞語,使用gevent的話,那幾秒鐘之內就給網站一股腦的發請求,說不定網站就把爬蟲封了。

解決辦法

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

方法1

seqence = list(range(20))size = 5 #子列表長度output = [seqence[i:i+size] for i in range(0, len(seqence), size)]print(output)

方法2

chunks = lambda seq, size: [seq[i: i+size] for i in range(0, len(seq), size)]print(chunks(seq, 5))

方法3

def chunks(seq,size): for i in range(0,len(seq), size):  yield seq[i:i+size]prinT(chunks(seq,5)) for x in chunks(req,5):   print(x) 

資料量不大的情況下,選哪一種方法都可以。如果特別大,建議使用方法3.

動手實現

import requestsfrom pyquery import PyQuery as pqimport geventimport timeimport gevent.monkeygevent.monkey.patch_all()words = ['good','bad','cool',   'hot','nice','better',   'head','up','down',   'right','left','east']def fetch_word_info(word): url = "http://dict.youdao.com/w/eng/{}/".format(word) resp = requests.get(url,headers=headers) doc = pq(resp.text) pros = '' for pro in doc.items('.baav .pronounce'):  pros+=pro.text() description = '' for li in doc.items('#phrsListTab .trans-container ul li'):  description +=li.text() return {'word':word,'音標':pros,'注釋':description}def asynchronous(words): start = time.time() print('非同步開始了') chunks = lambda seq, size: [seq[i: i + size] for i in range(0, len(seq), size)] for subwords in chunks(words,3):  events = [gevent.spawn(fetch_word_info, word) for word in subwords]  wordinfos = gevent.joinall(events)  for wordinfo in wordinfos:   # 擷取到資料get方法   print(wordinfo.get())  time.sleep(1)  end = time.time() print("非同步已耗用時間: %s 秒" % str(end - start))asynchronous(words)

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對幫客之家的支援。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.