【第四章-2】Python爬虫教程(协程,多任务异步协程,aiohttp模块,异步爬虫实战:爬取《西游记》全部章节内容)
本課程共五個章節,課程地址:
【Python爬蟲教程】花9888買的Python爬蟲全套教程2021完整版現分享給大家!(已更新項目)——附贈課程與資料_嗶哩嗶哩_bilibili
第四章
目錄
第四章
(六)協程?
(七)多任務異步協程
python編寫協程的程序
官方推薦寫法
在爬蟲領域的應用
(八)aiohttp模塊
安裝
代碼框架
例子
(九)異步爬蟲實戰:扒光一本電子書
思路
第一步:同步操作
第二步:異步操作
(六)協程?
協程能夠更加高效的利用CPU
其實,我們能夠高效的利用多線程來完成爬蟲已經很厲害了。但是,從某種角度講,線程的執行效率真的就無敵了嗎?我們真的充分利用CPU資源了嗎?非也
比如,我們來看下面這個例子。我們單獨的用一個線程來完成某一個操作,看看它的效率是否真的能把CPU完全利用起來?
import timedef func():print("我愛黎明")time.sleep(3) # 讓當前的線程處于阻塞狀態,CPU是不為我工作的print("我真的愛黎明")if __name__ == '__main__':func()# 其他能讓線程處于阻塞狀態的語句(一般情況下,當程序處于IO操作的時候,線程都會處于阻塞狀態) # input() 輸入 # requests.get() 發送請求(在網絡請求返回數據之前,程序也是處于阻塞狀態的)在該程序中,我們的func()實際在執行的時候至少需要3s的時間來完成操作,中間的3s需要讓我當前的線程處于阻塞狀態,阻塞狀態的線程CPU是不會來執行你的,那么此時CPU很可能會切換到其他程序上去執行。此時,對于你來說,在這3s內CPU并沒有為你工作。那么我們能不能通過某種手段,讓CPU一直為我工作,盡量不要去管其他人?
我們知道CPU一般拋開執行周期不談,如果一個線程遇到了IO操作,CPU就會自動地切換到其他線程進行執行。那么,如果我想辦法讓我的線程遇到了IO操作就掛起,留下的都是運算操作,那CPU是不是就會長時間的來照顧我
以此為目的,程序員就發明了一個新的執行過程:當線程中遇到了IO操作的時候,將線程中的任務進行切換,切換成非IO操作,等原來的IO執行完了,再恢復到原來的任務中?
協程:當程序遇見了IO操作(費時不費力的操作)的時候,可以選擇性的切換到其他任務上(程序完成的,不是操作系統完成的)
- 在微觀上是一個任務一個任務的進行切換,切換條件一般就是IO操作
- 在宏觀上,我們能看到的其實是多個任務一起在執行(多任務異步操作)
上方所講的一切,都是在單線程的條件下
(七)多任務異步協程
python編寫協程的程序
import asyncioasync def func():print("你好啊,我叫賽利亞")if __name__ == '__main__':# print(func()) 此時拿到的是一個協程對象,和生成器差不多,該函數默認是不會這樣執行的g = func() # 此時的函數是異步協程函數,函數執行得到的是一個協程對象asyncio.run(g) # 協程程序運行需要asyncio模塊的支持但上面這個代碼是單任務的,下面寫一個多任務的:
import asyncio import timeasync def func1():print("你好啊,我叫潘金蓮")time.sleep(3)print("你好啊,我叫潘金蓮")async def func2():print("你好啊,我叫王建國")time.sleep(2)print("你好啊,我叫王建國")async def func3():print("你好啊,我叫李雪琴")time.sleep(4)print("你好啊,我叫李雪琴")if __name__ == '__main__':f1 = func1() # 此時的函數是異步協程函數,函數執行得到的是一個協程對象f2 = func2()f3 = func3()# 把三個任務統一放到列表里去tasks = [f1,f2,f3]t1 = time.time() # 執行之前記錄一下時間# 一次性啟動多個任務(協程)asyncio.run(asyncio.wait(tasks)) # 協程程序運行需要asyncio模塊的支持t2 = time.time() # 執行之后記錄一下時間print(t2-t1) 異步操作,但跑出來的是同步效果異步效果的:
await:當該任務被掛起后,CPU會自動切換到其他任務中?
import asyncio import timeasync def func1():print("你好啊,我叫潘金蓮")# time.sleep(3) # 當程序出現了同步操作的時候,異步就中斷了 還有如requests.get()也會造成阻塞,碰到的時候也要換成對應的異步操作的代碼await asyncio.sleep(3) # 異步操作的代碼print("你好啊,我叫潘金蓮")async def func2():print("你好啊,我叫王建國")# time.sleep(2)await asyncio.sleep(2)print("你好啊,我叫王建國")async def func3():print("你好啊,我叫李雪琴")# time.sleep(4)await asyncio.sleep(4)print("你好啊,我叫李雪琴")if __name__ == '__main__':f1 = func1() # 此時的函數是異步協程函數,函數執行得到的是一個協程對象f2 = func2()f3 = func3()# 把三個任務統一放到列表里去tasks = [ # 協程任務列表f1,f2,f3 # 創建協程任務]t1 = time.time() # 執行之前記錄一下時間# 一次性啟動多個任務(協程)asyncio.run(asyncio.wait(tasks)) # 協程程序運行需要asyncio模塊的支持t2 = time.time() # 執行之后記錄一下時間print(t2-t1)官方推薦寫法:
import asyncio import timeasync def func1():print("你好啊,我叫潘金蓮")await asyncio.sleep(3) # 異步操作的代碼print("你好啊,我叫潘金蓮")async def func2():print("你好啊,我叫王建國")await asyncio.sleep(2)print("你好啊,我叫王建國")async def func3():print("你好啊,我叫李雪琴")await asyncio.sleep(4)print("你好啊,我叫李雪琴")async def main(): # 協程函數# 第一種寫法# f1 = func1()# await f1 # 一般await掛起操作放在協程對象前面# 第二種寫法(推薦)tasks = [ # 組成列表func1(),func2(),func3()]await asyncio.wait(tasks) # 一次性把所有任務都執行if __name__ == '__main__':t1 = time.time()asyncio.run(main()) # run協程對象t2 = time.time()print(t2-t1)python3.8之后的版本運行上述代碼時可能會有警告,將代碼稍作修改:
# py3.8之后需要我們手動將協程對象包裝成task對象tasks = [asyncio.create_task(func1()), # py3.8以后加上asyncio.create_task()asyncio.create_task(func2()),asyncio.create_task(func3())]await asyncio.wait(tasks)在爬蟲領域的應用:
import asyncio# 在爬蟲領域的應用 async def download(url):print("準備開始下載")await asyncio.sleep(2) # 模擬網絡請求 不能寫requests.get()print("下載完成")async def main():urls = ["http://www.baidu.com","http://www.bilibili.com","http://www.163.com"]tasks = []for url in urls:d = download(url) # 協程對象tasks.append(d)await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())python3.8之后的版本運行上述代碼時可能會有警告,將代碼稍作修改:
# 準備異步協程對象列表tasks = []for url in urls:d = asycio.create_task(download(url))tasks.append(d)tasks = [asyncio.create_task(download(url)) for url in urls] # 這么干也行哦~多線程和異步一個是程序執行模式,一個是IO模式。協程是單線程,事實上只是節省了CPU切棧的時間
(八)aiohttp模塊
requests.get() 是同步的代碼,如何換成異步的操作?? ? 借助?模塊aiohttp(第三方庫,需安裝)
aiohttp是python的一個非常優秀的第三方異步http請求庫,我們可以用aiohttp來編寫異步爬蟲(協程)?
安裝:
pip install aiohttp --trusted-host pypi.tuna.tsinghua.edu.cn代碼框架:
import asyncio import aiohttpurls = ["","" ]# 異步下載 async def aiodownload(url):passasync def main():tasks = [] # 添加下載任務for url in urls:tasks.append(aiodownload(url))await asyncio.wait(tasks) # 等待所有任務下載完成if __name__ == '__main__':asyncio.run(main())例子:?
【唯美壁紙】桌面壁紙唯美小清新_唯美手機壁紙_電腦桌面壁紙高清唯美大全 - 優美圖庫
import asyncio # 異步協程 import aiohttp # 異步協程的http請求urls = [ # 復制圖片地址"http://kr.shanghai-jiuxin.com/file/2020/1031/191468637cab2f0206f7d1d9b175ac81.jpg","http://kr.shanghai-jiuxin.com/file/2020/1031/563337d07af599a9ea64e620729f367e.jpg","http://kr.shanghai-jiuxin.com/file/2020/1031/774218be86d832f359637ab120eba52d.jpg" ]async def aiodownload(url):# 發送請求 ——> 得到圖片內容 ——> 保存到文件'''s = aiohttp.ClientSession() 等價于 requestsrequests.get() .post()s.get() .post()'''name = url.rsplit("/", 1)[1] # 從右邊切, 切一次. 得到[1]位置的內容# 有了with會自動closeasync with aiohttp.ClientSession() as session: # session對象相當于requests對象async with session.get(url) as resp: # 發送請求,相當于resp = requests.get()# 請求回來了. 寫入文件# 寫入文件可以自己去學習一個模塊aiofileswith open(name, mode="wb") as f: # 創建文件# resp.content.read() 等價于 resp.content 讀取圖片# resp.text() 等價于 resp.text 讀取文本,如頁面源代碼# resp.json() 等價于 resp.json() 讀取jsonf.write(await resp.content.read()) # 讀取內容是異步的. 需要await掛起print(name, "搞定")async def main():tasks = []for url in urls:tasks.append(aiodownload(url))await asyncio.wait(tasks)if __name__ == '__main__':asyncio.run(main())運行后能爬下圖片,但是代碼會報錯:?
將? asyncio.run() 改為 asyncio.get_event_loop().run_until_complete(main()) 即可。具體可參考下面這個帖子:
【Python自學筆記】asyncio.run()報錯RuntimeError:Event loop is closed的原因以及解決辦法_xiaoqiangclub的博客-CSDN博客_asyncio.run
if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())從最終運行的結果中能非常直觀地看到用異步IO完成爬蟲的效率明顯高了很多?
(九)異步爬蟲實戰:扒光一本電子書
百度小說
右鍵 ——> 查看網頁源代碼,如圖:
說明頁面的數據是通過 ajax 異步操作返回的數據(第二次加載的),故 f12 ,點擊頁面上 “全部章節(共100章)” 后的 “查看全部”
# 所有章節的內容(名稱、cid) # 同步的方式即可 https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"}隨便點進某一回的鏈接,如圖:
# 章節內部的內容 # 需要異步(100個章節即100個任務) https://dushu.baidu.com/api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|1569782244","need_bookinfo":1}思路:
第一步:同步操作
怎么拿cid?
import requestsdef getCatalog(url):resp = requests.get(url)dic = resp.json()for item in dic['data']['novel']['items']: # item就是對應每一個章節的名稱和cidtitle = item['title']cid = item['cid']print(title,cid)if __name__ == '__main__':b_id = "4306063500"url = 'http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"' + b_id + '"}'getCatalog(url)第二步:異步操作
每一個cid就是一個異步任務?
如何獲取章節內容?
代碼運行前,先創建一個文件夾,并把文件夾標記成 Excluded
# http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"4306063500"} => 所有章節的內容(名稱, cid) # http://dushu.baidu.com/api/pc/getChapterContent?data={"book_id":"4306063500","cid":"4306063500|11348571","need_bookinfo":1} => 章節內部的內容import requests import asyncio import aiohttp import aiofiles # 異步的文件讀寫 import json""" 1. 同步操作: 訪問getCatalog 拿到所有章節的cid和名稱 2. 異步操作: 訪問getChapterContent 下載所有的文章內容 """async def aiodownload(cid, b_id, title):data = {"book_id":b_id,"cid":f"{b_id}|{cid}","need_bookinfo":1}data = json.dumps(data) # 將對象變為json字符串url = f"http://dushu.baidu.com/api/pc/getChapterContent?data={data}"async with aiohttp.ClientSession() as session: # 準備好sessionasync with session.get(url) as resp: # session發送請求dic = await resp.json() # 從發送的請求里讀取jsonasync with aiofiles.open(title, mode="w", encoding="utf-8") as f:await f.write(dic['data']['novel']['content']) # 把小說內容寫出async def getCatalog(url):# 同步(此時還沒有其他任務會和該任務一起并行執行,所以完全沒必要用異步)resp = requests.get(url)dic = resp.json()# 得到要的內容之后,再異步tasks = []for item in dic['data']['novel']['items']: # item就是對應每一個章節的名稱和cidtitle = item['title']cid = item['cid']# 準備異步任務tasks.append(aiodownload(cid, b_id, title))await asyncio.wait(tasks)if __name__ == '__main__':b_id = "4306063500" # 百度小說的書籍idurl = 'http://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"' + b_id + '"}' # 首頁urlasyncio.get_event_loop().run_until_complete(getCatalog(url))運行后,將產生的文件全部移入之前建立好的文件夾里
怎么一次性選中多個文件?
左鍵選中一個文件,按住Ctrl+Shift,選中想要范圍內的文件
??
總結
以上是生活随笔為你收集整理的【第四章-2】Python爬虫教程(协程,多任务异步协程,aiohttp模块,异步爬虫实战:爬取《西游记》全部章节内容)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VS2013使用技巧汇总
- 下一篇: Python求1+2+…+n