欧美三级国产三级日韩三级_亚洲熟妇丰满大屁股熟妇_欧美亚洲成人一区二区三区_国产精品久久久久久模特

如何讓你寫的爬蟲速度像坐火箭一樣快【并發(fā)請求】 - 新聞資訊 - 云南小程序開發(fā)|云南軟件開發(fā)|云南網(wǎng)站建設(shè)-昆明葵宇信息科技有限公司

159-8711-8523

云南網(wǎng)建設(shè)/小程序開發(fā)/軟件開發(fā)

知識(shí)

不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們在追求其視覺表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營銷的便利,運(yùn)營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!

您當(dāng)前位置>首頁 » 新聞資訊 » 技術(shù)分享 >

如何讓你寫的爬蟲速度像坐火箭一樣快【并發(fā)請求】

發(fā)表時(shí)間:2019-7-22

發(fā)布人:葵宇科技

瀏覽次數(shù):41

話不多說,我們正式開始。在提升爬蟲的速度這方面,最基礎(chǔ)、最有效、最直接的操作是什么呢?沒錯(cuò),就是并發(fā)請求,如果你的爬蟲整個(gè)邏輯是順序執(zhí)行的,請求的時(shí)候永遠(yuǎn)不會(huì)并發(fā),那么你就會(huì)遇到像他這樣的情況:《小白寫了個(gè)壁紙的爬蟲,能跑起來,但是感覺很慢,不知道怎么回事,請大佬指點(diǎn)》。

上面這是我昨天刷V2的時(shí)候看到的一個(gè)帖子,樓主的代碼內(nèi)容簡單概括一下就完全是順序執(zhí)行的,每下載一個(gè)圖片都需要等待當(dāng)前這個(gè)圖片下載完了才能繼續(xù)下載下一個(gè),這樣子做當(dāng)然會(huì)非常慢了!這篇文章就拿他的代碼作為樣例,在原來的基礎(chǔ)上進(jìn)行一些調(diào)整,從而讓他寫的這個(gè)爬蟲的運(yùn)行速度能從龜爬變成像坐火箭一樣快!


首先,我們需要知道什么是并發(fā),這里的并發(fā)指的是“并行發(fā)送請求”,意思就是一次性發(fā)出多個(gè)請求,從而達(dá)到節(jié)省時(shí)間的效果!那么并發(fā)和不并發(fā)的區(qū)別在哪呢?簡單來說就是這樣子的:

把爬蟲比喻成工人,在不并發(fā)的情況下,一個(gè)工人一次只能做一件事情,所以必須要下載完一個(gè)圖片才能繼續(xù)下載下一個(gè)。

順序執(zhí)行的情況

而在并發(fā)的情況下,就有很多個(gè)工人一起在干活,每個(gè)工人都被分配了一件事情做,所以可以同時(shí)下載多個(gè)圖片,速度自然就快了很多。

并發(fā)的情況

當(dāng)然,上面說的這個(gè)例子只是從一個(gè)宏觀的角度上來看并發(fā),實(shí)際在做的時(shí)候要讓你的爬蟲能并發(fā)請求的方式是分為多線程、多進(jìn)程、協(xié)程三種的,并不是每一種方式在運(yùn)行時(shí)的效果都像上面說的這樣,這里先不做深入探討,因?yàn)檫@不是本文的重點(diǎn)。我們現(xiàn)在只需要知道,只要能讓爬蟲并發(fā)請求,就能同時(shí)下載多個(gè)圖片,讓速度快得飛起,這樣就夠了。


那么我們要用上面說的三種方式里的哪一種來實(shí)現(xiàn)并發(fā)請求呢?這還用問嗎?當(dāng)然是選擇代碼最簡單、改動(dòng)最小,并且最容易看懂的協(xié)程啊!在Python3.4之后Python就引入了一個(gè)叫做asyncio的庫,原生支持了異步IO,而在3.5之后Python又支持了asyncawait這兩個(gè)語法,使得寫異步代碼可以像寫同步代碼一樣簡單易讀。

剛剛又提到了兩個(gè)詞,同步和異步,這兩個(gè)詞的含義其實(shí)就跟上面的并發(fā)差不多,同步代碼就是順序執(zhí)行的,而異步則不是,這里同樣不做深入探討,先知道有這么個(gè)東西就行了。

看到這里肯定會(huì)有人開始有疑問了,雖然前面說我們要用協(xié)程來實(shí)現(xiàn)并發(fā)請求,但是后面說的卻是什么Python支持原生異步,那么這個(gè)異步跟協(xié)程的關(guān)系又是什么呢?

其實(shí)很簡單,協(xié)程可以讓你寫異步代碼的時(shí)候能像寫同步代碼一樣簡單,在Python3中寫協(xié)程代碼的核心語法就是asyncawait這兩個(gè),舉個(gè)簡單的例子吧:

1def func():
2    print(1)
3    time.sleep(10)
4    print(2)

這是一段普通的函數(shù),它屬于同步代碼,里面的time.sleep是普通函數(shù),也屬于同步代碼。

1async def func():  # 調(diào)用協(xié)程函數(shù)的那個(gè)函數(shù)也需要是一個(gè)協(xié)程函數(shù)
2    print(1)
3    await asyncio.sleep(10)  # 調(diào)用協(xié)程函數(shù)的時(shí)候要在前面加await
4    print(2)

而這是一個(gè)協(xié)程函數(shù),它屬于異步代碼,里面的asyncio.sleep是協(xié)程函數(shù),也屬于異步代碼。

它們的區(qū)別顯而易見,用協(xié)程來寫異步代碼,除了需要換成異步的庫以外,就只是多了個(gè)async、await而已,是不是非常簡單?


那么我們在了解了怎么寫協(xié)程代碼之后,就能開始優(yōu)化那段慢成龜速的代碼了嗎?答案是否定的,那段代碼中使用了requests庫進(jìn)行網(wǎng)絡(luò)請求,而requests是一個(gè)同步庫,不能在異步環(huán)境下使用;同樣,文件操作用的openfile.write也是同步的,也不能在異步環(huán)境下使用。

所以在開始之前我們還需要了解兩個(gè)庫,分別是aiohttp和aiofiles,aiohttp是一個(gè)異步網(wǎng)絡(luò)請求庫,而aiofiles是一個(gè)異步文件操作庫。(aiofiles是基于線程池實(shí)現(xiàn)的,并不是真正的原生異步,但問題不大,不影響使用)

切記,異步代碼不能與同步代碼混用,否則如果同步代碼耗時(shí)過長,異步代碼就會(huì)被阻塞,失去異步的效果。而網(wǎng)絡(luò)請求和文件操作是整個(gè)流程中最耗時(shí)的部分,所以我們必須使用異步的庫來進(jìn)行操作!否則就白搞了!

好了,先來看看aiohttp的用法吧,官方文檔上的示例大致如下:

1async with aiohttp.ClientSession() as session:
2    async with session.get(url) as resp:
3        result = await resp.text()

是不是覺得很麻煩,不像requests庫那么方便?還覺得兩層async with很丑?有沒有辦法讓它像requests庫一樣方便呢?

答案是有的,有一個(gè)叫作aiohttp-requests的庫,它能讓上面的這段代碼變成這樣:

1resp = await requests.get(url)
2result = await resp.text()

清爽多了對吧?我們等下就用它了!記得裝這個(gè)庫的前提是要先裝aiohttp哦!

然后我們來看看aiofiles的用法,官方文檔上的示例如下:

1async with aiofiles.open('filename', mode='r') as f:
2    contents = await f.read()
3print(contents)

嗯,這個(gè)用起來就和用同步代碼操作文件差不多了,沒啥可挑剔的,直接用就完事了。

提示:aiohttp-requests默認(rèn)是創(chuàng)建并使用了session的,對于一些需要不保留Cookie進(jìn)行請求的場景需要自己實(shí)例化一個(gè)Requests類,并指定cookie_jar為aiohttp.DummyCookieJar。


了解完了要用的庫之后我們就可以開始對貼子中的代碼進(jìn)行魔改了,如果你用的不是Python3.5以上版本的話需要先準(zhǔn)備一下環(huán)境。除了版本號(hào)大于等于3.5的Python以外,你還需要安裝以下幾個(gè)庫:

  • aiohttp(異步網(wǎng)絡(luò)請求庫)

  • aiohttp-requests(讓aiohttp用起來更方便的庫)

  • aiofiles(異步文件操作庫)

  • pillow(其實(shí)就是PIL庫,代碼中的圖片操作有用到)

執(zhí)行一下pip install aiohttp aiohttp-requests aiofiles pillow一次性裝完,如果存在多個(gè)不同版本的Python環(huán)境記得區(qū)分好。


然后我們打開編輯器,開始改代碼,首先調(diào)整一下導(dǎo)包的部分,將里面的requests替換成aiohttp-requests,像這樣:

然后搜索一下requests,看看哪些地方用到了它。

接著把所有搜到的部分都給改成異步請求的。

同時(shí)不要忘了將所有調(diào)用過requests.get的函數(shù)都變成協(xié)程函數(shù)。

然后我們把文件操作的部分也換成異步的,使用aiofiles.open代替open

最主要的部分都換好了,接著我們將原先在if __name__ == '__main__':下的代碼移到一個(gè)新寫的協(xié)程函數(shù)run中,并且將調(diào)用前面協(xié)程函數(shù)的部分都加上await。

再導(dǎo)入一下asyncio庫,然后在if __name__ == '__main__':下寫出這樣的代碼:

上面這個(gè)是Python3.7之后才能用的寫法,低于Python3.7要這樣寫:

現(xiàn)在我們就可以運(yùn)行一下看看修改后的代碼能不能跑通了。

這里報(bào)了個(gè)錯(cuò),從錯(cuò)誤堆棧中可以看出問題是出在response = await requests.get(url=url, headers=headers)這里的,原因是self.session._request方法沒有key為url的參數(shù)。這個(gè)問題很好解決,只需要將url=url變成url就好了(本來也就沒必要這么指定參數(shù)寫)。將代碼中所有用到requests.get并且存在url=url這種寫法的都做一下調(diào)整:

調(diào)整完之后再運(yùn)行一次就正常了,效果和原先的代碼相同。

注意!僅僅是這樣并不會(huì)讓速度發(fā)生很大的變化!我們最后還需要將這一堆代碼中最耗時(shí)且是順序執(zhí)行、沒有并發(fā)請求的部分單獨(dú)放到一個(gè)協(xié)程函數(shù)中,并且用asyncio.gather來并發(fā)調(diào)用(由于原本的邏輯較為混亂,這里除了并發(fā)請求以外還進(jìn)行了一些其他的微調(diào),主要是計(jì)數(shù)和文件路徑的部分,無關(guān)緊要)。

運(yùn)行一下看看效果,剛運(yùn)行起來一瞬間就刷了一排的下載完成,跟修改之前比起來簡直是天差地別。

這就是并發(fā)請求的威力!我們僅僅是對他原本的代碼進(jìn)行了一些微調(diào),把最耗時(shí)的下載圖片部分簡單粗暴地使用asyncio.gather并發(fā)執(zhí)行了一下,速度就從龜爬變成了像坐火箭一樣快!(其實(shí)代碼中還有很多可以優(yōu)化的點(diǎn),這里就不一一拿出來講了)


最后給大家提個(gè)醒:

雖然并發(fā)請求非常牛逼,可以讓你的爬蟲變得飛快,但它也不是不存在任何問題的!

如果你的并發(fā)請求數(shù)量過大(又稱并發(fā)數(shù)過高),你的爬蟲就相當(dāng)于是在對他人的服務(wù)器進(jìn)行Dos攻擊(拒絕服務(wù)攻擊)了!

舉個(gè)例子,你在爬一個(gè)小網(wǎng)站的時(shí)候?yàn)榱俗约号赖乃俣雀?#xff0c;對并發(fā)請求的數(shù)量毫無限制,使得你的爬蟲一次性發(fā)出了幾百、上千個(gè)請求,但一般的小網(wǎng)站根本扛不住這么高的并發(fā)!幾乎會(huì)在一瞬間就被你的爬蟲給打爆掉!試想一下,如果你是站長,看到這樣的情形你會(huì)怎么想?

如果你不能理解這個(gè)例子所產(chǎn)生的效果是什么樣的,可以自己搭建一個(gè)Web服務(wù),只放一個(gè)簡單的頁面,然后開個(gè)幾百并發(fā)去請求這個(gè)頁面,這樣你就能切身地體會(huì)到別人是什么感受了。

所以記住,一定要合理控制并發(fā)請求的數(shù)量,不要對對方網(wǎng)站造成過大的壓力!你給別人留活路,別人才會(huì)給你留活路!

最后再留個(gè)小作業(yè)吧,如何對這個(gè)修改后的代碼增加一道并發(fā)數(shù)的限制?在留言區(qū)給出你的答案。(提示:可通過搜索引擎查找【aiohttp并發(fā)連接數(shù)限制】和【python 列表切割】相關(guān)的內(nèi)容)

END

往期文章回顧

當(dāng)你寫爬蟲遇到APP的請求有加密參數(shù)時(shí)該怎么辦?【初級(jí)篇-秒殺模式】

一只爬蟲的旅途

有多少個(gè)人在看

相關(guān)案例查看更多