2022 年Python3 爬蟲教程 - aiohttp 的基本使用

      網(wǎng)友投稿 876 2025-04-06

      在上一節(jié)中,我們介紹了異步爬蟲的基本原理和 asyncio 的基本用法,并且在最后簡(jiǎn)單提及了使用 aiohttp 來實(shí)現(xiàn)網(wǎng)頁(yè)爬取的過程。在本節(jié)中,我們來介紹一下 aiohttp 的常見用法。


      1. 基本介紹

      前面介紹的 asyncio 模塊內(nèi)部實(shí)現(xiàn)了對(duì) TCP、UDP、SSL 協(xié)議的異步操作,但是對(duì)于 HTTP 請(qǐng)求來說,我們就需要用到 aiohttp 來實(shí)現(xiàn)了。

      aiohttp 是一個(gè)基于 asyncio 的異步 HTTP 網(wǎng)絡(luò)模塊,它既提供了服務(wù)端,又提供了客戶端。其中我們用服務(wù)端可以搭建一個(gè)支持異步處理的服務(wù)器,就是用來處理請(qǐng)求并返回響應(yīng)的,類似于 Django、Flask、Tornado 等一些 Web 服務(wù)器。而客戶端可以用來發(fā)起請(qǐng)求,類似于使用 requests 發(fā)起一個(gè) HTTP 請(qǐng)求然后獲得響應(yīng),但 requests 發(fā)起的是同步的網(wǎng)絡(luò)請(qǐng)求,aiohttp 則是異步的。

      本節(jié)中,我們主要了解一下 aiohttp 客戶端部分的用法。

      2. 基本實(shí)例

      首先,我們來看一個(gè)基本的 aiohttp 請(qǐng)求案例,代碼如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      import aiohttp

      import asyncio

      async def fetch(session, url):

      async with session.get(url) as response:

      return await response.text(), response.status

      async def main():

      async with aiohttp.ClientSession() as session:

      html, status = await fetch(session, 'https://cuiqingcai.com')

      print(f'html: {html[:100]}...')

      print(f'status: {status}')

      if __name__ == '__main__':

      loop = asyncio.get_event_loop()

      loop.run_until_complete(main())

      這里我們使用 aiohttp 來爬取我的個(gè)人博客,獲得了源碼和響應(yīng)狀態(tài)碼并輸出出來,運(yùn)行結(jié)果如下:

      1

      2

      3

      4

      5

      6

      html:

      status: 200

      這里網(wǎng)頁(yè)源碼過長(zhǎng),只截取輸出了一部分。可以看到,這里我們成功獲取了網(wǎng)頁(yè)的源代碼及響應(yīng)狀態(tài)碼 200,也就完成了一次基本的 HTTP 請(qǐng)求,即我們成功使用 aiohttp 通過異步的方式來進(jìn)行了網(wǎng)頁(yè)爬取。當(dāng)然,這個(gè)操作用之前講的 requests 也可以做到。

      可以看到,其請(qǐng)求方法的定義和之前有了明顯的區(qū)別,主要有如下幾點(diǎn):

      首先在導(dǎo)入庫(kù)的時(shí)候,我們除了必須要引入 aiohttp 這個(gè)庫(kù)之外,還必須要引入 asyncio 這個(gè)庫(kù)。因?yàn)橐獙?shí)現(xiàn)異步爬取,需要啟動(dòng)協(xié)程,而協(xié)程則需要借助于 asyncio 里面的事件循環(huán)來執(zhí)行。除了事件循環(huán),asyncio 里面也提供了很多基礎(chǔ)的異步操作。

      異步爬取方法的定義和之前有所不同,在每個(gè)異步方法前面統(tǒng)一要加 async 來修飾。

      with as 語(yǔ)句前面同樣需要加 async 來修飾。在 Python 中,with as 語(yǔ)句用于聲明一個(gè)上下文管理器,能夠幫我們自動(dòng)分配和釋放資源。而在異步方法中,with as 前面加上 async 代表聲明一個(gè)支持異步的上下文管理器。

      對(duì)于一些返回 coroutine 的操作,前面需要加 await 來修飾。比如 response 調(diào)用 text 方法,查詢 API 可以發(fā)現(xiàn),其返回的是 coroutine 對(duì)象,那么前面就要加 await;而對(duì)于狀態(tài)碼來說,其返回值就是一個(gè)數(shù)值類型,那么前面就不需要加 await。所以,這里可以按照實(shí)際情況處理,參考官方文檔說明,看看其對(duì)應(yīng)的返回值是怎樣的類型,然后決定加不加 await 就可以了。

      最后,定義完爬取方法之后,實(shí)際上是 main 方法調(diào)用了 fetch 方法。要運(yùn)行的話,必須要啟用事件循環(huán),而事件循環(huán)就需要使用 asyncio 庫(kù),然后使用 run_until_complete 方法來運(yùn)行。

      注意:在 Python 3.7 及以后的版本中,我們可以使用 asyncio.run(main()) 來代替最后的啟動(dòng)操作,不需要顯示聲明事件循環(huán),run 方法內(nèi)部會(huì)自動(dòng)啟動(dòng)一個(gè)事件循環(huán)。但這里為了兼容更多的 Python 版本,依然還是顯式聲明了事件循環(huán)。

      3. URL 參數(shù)設(shè)置

      對(duì)于 URL 參數(shù)的設(shè)置,我們可以借助于 params 參數(shù),傳入一個(gè)字典即可,示例如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      import aiohttp

      import asyncio

      async def main():

      params = {'name': 'germey', 'age': 25}

      async with aiohttp.ClientSession() as session:

      async with session.get('https://httpbin.org/get', params=params) as response:

      print(await response.text())

      if __name__ == '__main__':

      asyncio.get_event_loop().run_until_complete(main())

      運(yùn)行結(jié)果如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      {

      "args": {

      "age": "25",

      "name": "germey"

      },

      "headers": {

      "Accept": "*/*",

      "Accept-Encoding": "gzip, deflate",

      "Host": "httpbin.org",

      "User-Agent": "Python/3.7 aiohttp/3.6.2",

      "X-Amzn-Trace-Id": "Root=1-5e85eed2-d240ac90f4dddf40b4723ef0"

      },

      "origin": "17.20.255.122",

      "url": "https://httpbin.org/get?name=germey&age=25"

      }

      這里可以看到,其實(shí)際請(qǐng)求的 URL 為 https://httpbin.org/get?name=germey&age=25,其 URL 請(qǐng)求參數(shù)就對(duì)應(yīng)了 params 的內(nèi)容。

      4. 其他請(qǐng)求類型

      另外,aiohttp 還支持其他請(qǐng)求類型,如 POST、PUT、DELETE 等,這和 requests 的使用方式有點(diǎn)類似,示例如下:

      1

      2

      3

      4

      5

      6

      session.post('http://httpbin.org/post', data=b'data')

      session.put('http://httpbin.org/put', data=b'data')

      session.delete('http://httpbin.org/delete')

      session.head('http://httpbin.org/get')

      session.options('http://httpbin.org/get')

      session.patch('http://httpbin.org/patch', data=b'data')

      要使用這些方法,只需要把對(duì)應(yīng)的方法和參數(shù)替換一下即可。

      【2022 年】Python3 爬蟲教程 - aiohttp 的基本使用

      5. POST 請(qǐng)求

      對(duì)于 POST 表單提交,其對(duì)應(yīng)的請(qǐng)求頭的 Content-Type 為 application/x-www-form-urlencoded,我們可以用如下方式來實(shí)現(xiàn),代碼示例如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      import aiohttp

      import asyncio

      async def main():

      data = {'name': 'germey', 'age': 25}

      async with aiohttp.ClientSession() as session:

      async with session.post('https://httpbin.org/post', data=data) as response:

      print(await response.text())

      if __name__ == '__main__':

      asyncio.get_event_loop().run_until_complete(main())

      運(yùn)行結(jié)果如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      {

      "args": {},

      "data": "",

      "files": {},

      "form": {

      "age": "25",

      "name": "germey"

      },

      "headers": {

      "Accept": "*/*",

      "Accept-Encoding": "gzip, deflate",

      "Content-Length": "18",

      "Content-Type": "application/x-www-form-urlencoded",

      "Host": "httpbin.org",

      "User-Agent": "Python/3.7 aiohttp/3.6.2",

      "X-Amzn-Trace-Id": "Root=1-5e85f0b2-9017ea603a68dc285e0552d0"

      },

      "json": null,

      "origin": "17.20.255.58",

      "url": "https://httpbin.org/post"

      }

      對(duì)于 POST JSON 數(shù)據(jù)提交,其對(duì)應(yīng)的請(qǐng)求頭的 Content-Type 為 application/json,我們只需要將 post 方法的 data 參數(shù)改成 json 即可,代碼示例如下:

      1

      2

      3

      4

      5

      async def main():

      data = {'name': 'germey', 'age': 25}

      async with aiohttp.ClientSession() as session:

      async with session.post('https://httpbin.org/post', json=data) as response:

      print(await response.text())

      運(yùn)行結(jié)果如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      {

      "args": {},

      "data": "{\"name\": \"germey\", \"age\": 25}",

      "files": {},

      "form": {},

      "headers": {

      "Accept": "*/*",

      "Accept-Encoding": "gzip, deflate",

      "Content-Length": "29",

      "Content-Type": "application/json",

      "Host": "httpbin.org",

      "User-Agent": "Python/3.7 aiohttp/3.6.2",

      "X-Amzn-Trace-Id": "Root=1-5e85f03e-c91c9a20c79b9780dbed7540"

      },

      "json": {

      "age": 25,

      "name": "germey"

      },

      "origin": "17.20.255.58",

      "url": "https://httpbin.org/post"

      }

      可以發(fā)現(xiàn),其實(shí)現(xiàn)也和 requests 非常像,不同的參數(shù)支持不同類型的請(qǐng)求內(nèi)容。

      6. 響應(yīng)

      對(duì)于響應(yīng)來說,我們可以用如下方法分別獲取響應(yīng)的狀態(tài)碼、響應(yīng)頭、響應(yīng)體、響應(yīng)體二進(jìn)制內(nèi)容、響應(yīng)體 JSON 結(jié)果,示例如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      import aiohttp

      import asyncio

      async def main():

      data = {'name': 'germey', 'age': 25}

      async with aiohttp.ClientSession() as session:

      async with session.post('https://httpbin.org/post', data=data) as response:

      print('status:', response.status)

      print('headers:', response.headers)

      print('body:', await response.text())

      print('bytes:', await response.read())

      print('json:', await response.json())

      if __name__ == '__main__':

      asyncio.get_event_loop().run_until_complete(main())

      運(yùn)行結(jié)果如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      status: 200

      headers:

      body: {

      "args": {},

      "data": "",

      "files": {},

      "form": {

      "age": "25",

      "name": "germey"

      },

      "headers": {

      "Accept": "*/*",

      "Accept-Encoding": "gzip, deflate",

      "Content-Length": "18",

      "Content-Type": "application/x-www-form-urlencoded",

      "Host": "httpbin.org",

      "User-Agent": "Python/3.7 aiohttp/3.6.2",

      "X-Amzn-Trace-Id": "Root=1-5e85f2f1-f55326ff5800b15886c8e029"

      },

      "json": null,

      "origin": "17.20.255.58",

      "url": "https://httpbin.org/post"

      }

      bytes: b'{\n "args": {}, \n "data": "", \n "files": {}, \n "form": {\n "age": "25", \n "name": "germey"\n }, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Content-Length": "18", \n "Content-Type": "application/x-www-form-urlencoded", \n "Host": "httpbin.org", \n "User-Agent": "Python/3.7 aiohttp/3.6.2", \n "X-Amzn-Trace-Id": "Root=1-5e85f2f1-f55326ff5800b15886c8e029"\n }, \n "json": null, \n "origin": "17.20.255.58", \n "url": "https://httpbin.org/post"\n}\n'

      json: {'args': {}, 'data': '', 'files': {}, 'form': {'age': '25', 'name': 'germey'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '18', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'Python/3.7 aiohttp/3.6.2', 'X-Amzn-Trace-Id': 'Root=1-5e85f2f1-f55326ff5800b15886c8e029'}, 'json': None, 'origin': '17.20.255.58', 'url': 'https://httpbin.org/post'}

      這里我們可以看到有些字段前面需要加 await,有的則不需要。其原則是,如果它返回的是一個(gè) coroutine 對(duì)象(如 async 修飾的方法),那么前面就要加 await,具體可以看 aiohttp 的 API,其鏈接為:https://docs.aiohttp.org/en/stable/client_reference.html。

      7. 超時(shí)設(shè)置

      對(duì)于超時(shí)設(shè)置,我們可以借助 ClientTimeout 對(duì)象,比如這里要設(shè)置 1 秒的超時(shí),可以這么實(shí)現(xiàn):

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      import aiohttp

      import asyncio

      async def main():

      timeout = aiohttp.ClientTimeout(total=1)

      async with aiohttp.ClientSession(timeout=timeout) as session:

      async with session.get('https://httpbin.org/get') as response:

      print('status:', response.status)

      if __name__ == '__main__':

      asyncio.get_event_loop().run_until_complete(main())

      如果在 1 秒之內(nèi)成功獲取響應(yīng)的話,運(yùn)行結(jié)果如下:

      1

      200

      如果超時(shí)的話,會(huì)拋出 TimeoutError 異常,其類型為 asyncio.TimeoutError,我們?cè)龠M(jìn)行異常捕獲即可。

      另外,聲明 ClientTimeout 對(duì)象時(shí)還有其他參數(shù),如 connect、socket_connect 等,詳細(xì)可以參考官方文檔:https://docs.aiohttp.org/en/stable/client_quickstart.html#timeouts。

      8. 并發(fā)限制

      由于 aiohttp 可以支持非常大的并發(fā),比如上萬(wàn)、十萬(wàn)、百萬(wàn)都是能做到的,但對(duì)于這么大的并發(fā)量,目標(biāo)網(wǎng)站很可能在短時(shí)間內(nèi)無法響應(yīng),而且很可能瞬時(shí)間將目標(biāo)網(wǎng)站爬掛掉,所以我們需要控制一下爬取的并發(fā)量。

      一般情況下,我們可以借助于 asyncio 的 Semaphore 來控制并發(fā)量,示例如下:

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      import asyncio

      import aiohttp

      CONCURRENCY = 5

      URL = 'https://www.baidu.com'

      semaphore = asyncio.Semaphore(CONCURRENCY)

      session = None

      async def scrape_api():

      async with semaphore:

      print('scraping', URL)

      async with session.get(URL) as response:

      await asyncio.sleep(1)

      return await response.text()

      async def main():

      global session

      session = aiohttp.ClientSession()

      scrape_index_tasks = [asyncio.ensure_future(scrape_api()) for _ in range(10000)]

      await asyncio.gather(*scrape_index_tasks)

      if __name__ == '__main__':

      asyncio.get_event_loop().run_until_complete(main())

      這里我們聲明了 CONCURRENCY(代表爬取的最大并發(fā)量)為 5,同時(shí)聲明爬取的目標(biāo) URL 為百度。接著,我們借助于 Semaphore 創(chuàng)建了一個(gè)信號(hào)量對(duì)象,將其賦值為 semaphore,這樣我們就可以用它來控制最大并發(fā)量了。怎么使用呢?這里我們把它直接放置在對(duì)應(yīng)的爬取方法里面,使用 async with 語(yǔ)句將 semaphore 作為上下文對(duì)象即可。這樣的話,信號(hào)量可以控制進(jìn)入爬取的最大協(xié)程數(shù)量,即我們聲明的 CONCURRENCY 的值。

      在 main 方法里面,我們聲明了 10000 個(gè) task,將其傳遞給 gather 方法運(yùn)行。倘若不加以限制,這 10000 個(gè) task 會(huì)被同時(shí)執(zhí)行,并發(fā)數(shù)量太大。但有了信號(hào)量的控制之后,同時(shí)運(yùn)行的 task 的數(shù)量最大會(huì)被控制在 5 個(gè),這樣就能給 aiohttp 限制速度了。

      9. 總結(jié)

      本節(jié)我們了解了 aiohttp 的基本使用方法,更詳細(xì)的內(nèi)容還是推薦大家到官方文檔查閱,詳見 https://docs.aiohttp.org/。

      本節(jié)代碼:https://github.com/Python3WebSpider/AsyncTest。

      JSON Python

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Word如何添加頁(yè)碼而首頁(yè)不添加(Word首頁(yè)不加頁(yè)碼)
      下一篇:敏捷潘多拉魔盒
      相關(guān)文章
      亚洲av无码一区二区三区在线播放| 久久精品国产亚洲AV久| 亚洲AV成人精品日韩一区| 亚洲国产日韩在线成人蜜芽| 久久精品国产亚洲av水果派| 亚洲AV福利天堂一区二区三| 亚洲激情视频在线观看| 国产亚洲综合网曝门系列| 亚洲人成人无码网www电影首页| 久久亚洲国产精品123区| 国产日韩成人亚洲丁香婷婷| 国产亚洲情侣一区二区无码AV| 亚洲综合色区在线观看| 国产aⅴ无码专区亚洲av麻豆 | 国产精品亚洲精品观看不卡| 亚洲中文字幕久在线| 色在线亚洲视频www| 最新亚洲卡一卡二卡三新区| 亚洲第一第二第三第四第五第六 | 青青草原亚洲视频| 亚洲日韩精品一区二区三区无码| 在线播放亚洲第一字幕| 亚洲国产第一站精品蜜芽| 亚洲AV无码一区二区三区DV| 亚洲AV人无码综合在线观看| 亚洲麻豆精品果冻传媒| 亚洲一区二区三区免费观看| 久久亚洲精品国产亚洲老地址| 亚洲欧美国产国产一区二区三区| 亚洲av永久中文无码精品综合| www.亚洲色图.com| 亚洲中文字幕久久精品无码APP| 亚洲国产精品嫩草影院在线观看| 亚洲国语精品自产拍在线观看| 亚洲第一页中文字幕| 亚洲欧洲日韩国产一区二区三区| 亚洲精品无码aⅴ中文字幕蜜桃| 国产亚洲日韩在线a不卡| 日本亚洲国产一区二区三区 | 亚洲国产精品一区二区第一页 | 免费在线观看亚洲|