天天看點

python爬蟲----DAY8----異步爬蟲之協程異步爬蟲之協程

異步爬蟲之協程

建議從代碼部分開始閱讀!!!不清楚的地方向上檢視基本概念

異步程式設計大緻流程:

1.事件循環:了解為一個死循環(去檢測并執行某些代碼)
           
#僞代碼
        任務清單={任務1,任務2,任務3.。。。}
        while True:
            可執行的任務清單,已完成的任務清單=去任務清單中檢查所有的任務,将’可執行‘和’已完					成‘的任務傳回
            for 就緒任務   in  可執行的任務清單:
                執行已就緒的任務
            for 已完成的任務 in 已完成的任務清單;
                在任務清單中移除,已完成的任務
            如果 任務清單 中的任務都已完成,則終止循環
           

文章目錄

  • 異步爬蟲之協程
    • 基本概念介紹
      • 1. 特殊的函數
      • 2. 協程對象
      • 3. 任務對象
      • 4. 事件循環對象
    • 5. 注意事項!!!!!
    • 代碼部分
      • 1. 基本協程的使用
      • 2. 基于loop的任務對象的使用
      • 3. 任務對象future的使用
      • 4. 回調函數的綁定和使用
      • 5. 多任務協程
      • 6. aiohttp的使用---實戰

基本概念介紹

1. 特殊的函數

  • 如果一個函數的定義被async關鍵字修飾後,則該函數就變成了一個特殊的函數
  • 特殊之處:

    1. 該特殊的函數被調用後,内部的語句不會被立即執行

    2. 該函數被調用後傳回一個協程對象

2. 協程對象

  • 一個對象,通過特殊的函數調用放回的一個協程對象。

    是以,協程 = 特殊的函數 =一組指定的操作 => 協程= 一組特殊的函數

3. 任務對象

  • 任務對象就是一個進階的協程對象(即對協程對象的進一步封裝)
  • 有task和future兩種。本質上無差別。但是基于loop建立的任務對象的前提是得有loop。

如何建立一個任務對象:

· asyncio.ensure_future(協程對象)—(future)

第二種方法為基于事件循環對象的建立task,在後面介紹

  • 任務對象的進階之處:
  1. 可以給任務對象綁定回調

    task.add_done_callback(task_callback)

  2. 回調函數的調用時機:

    任務被執行結束後,才可以調用回調函數

-回調函數的參數隻可以有一個;表示的就是該回調函數的調用者(任務對象)
    -使用回調函數的參數調用result()傳回的就是任務對象表示的特殊函數的傳回值
           

4. 事件循環對象

作用:

-可以将多個任務對象注冊/裝載到任務循環對象中

-如果開啟了事件循環,則其内部注冊/裝載的任務對象表示的就是指定操作就是任務對象

-建立方式:
        -loop=asyncio.get_event_loop()
    - 建立任務對象
    	- task=loop.creat_task(協程對象)
    -注冊且啟動方式:
        -loop.run_until_complete(task)
           

啟動時不不能直接将task清單放入,要加asyncio.wait(tasks)修飾

wait()作用:可以将任務清單中的任務對象賦予可挂起權限。(每一個任務對象都可以被挂起)

任務對象的挂起:将目前挂起的任務對象交出CPU的使用權。隻有當任務對象的CPU的使用權,

loop才可以使用CPU去執行下一個任務對象

-當loop在執行某一個任務對象時,遇到了阻塞操作,則loop會跳過阻塞操作執行下一個任務對象

-當loop在執行某一個任務對象時,前面一個任務對象的阻塞操作結束了,則loop會回頭将該阻塞

結束的任務對象阻塞之後的操作進行執行

5. 注意事項!!!!!

-在特殊函數内部不可以出現不支援異步子產品對應的代碼,否則會中斷整個異步效果,requests不支援異步

    -await關鍵字
            -在特殊函數内部,凡是阻塞操作前,都必須使用await進行修飾,await就可以保證阻塞
            操作在異步執行過程中不會被跳過!!!
   
    -aiohttp
            -是一個支援異步的網絡請求子產品。
            -使用代碼:
                    1.寫一個大緻的架構
                  async def get_request(url):
                         #執行個體化好了一個請求對象
                         with aiohttp.ClientSession() as sess:
                             #調用get發起請求,傳回一個響應對象
                             #get/post(url,headers,params/data,proxy=''http://ip:port)
                             with sess.get(url=url) as response:
                                 #擷取了字元串形式的響應資料
                                 page_text=response.text()
                                 return page_text
                   	2.補充細節:
                            -在阻塞操作前加await關鍵字,在每個with前加上async關鍵字(爬蟲中隻有發送請求和擷取響應資料是阻塞操作)
                            
                    3。完整代碼:
                            async def get_request(url):
                                #執行個體化好了一個請求對象
                                async with aiohttp.ClientSession() as sess:
                                    #調用get發起請求,傳回一個響應對象
                                    #get/post(url,headers,params/data,proxy=''http://ip:port)
                                    async with await sess.get(url=url) as response:
                                        #text()擷取了字元串形式的響應資料
                                        #read()擷取byte類型的響應資料
                                        page_text=await response.text()
                                        return page_text
           

代碼部分

安裝子產品 : pip install asyncio
           

1. 基本協程的使用

import asyncio

# 定義協程函數
async def request(url):
    print('正在請求:',url)
    print('請求結束',url)
#  async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')

# 建立一個事件循環對象
loop=asyncio.get_event_loop()

# 将協程對象注冊到loop中,然後啟動loop
loop.run_until_complete(c)
           

2. 基于loop的任務對象的使用

import asyncio

async def request(url):
    print('正在請求:',url)
    print('請求結束',url)
#  async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')

# task的使用

#建立事件循環對象
loop=asyncio.get_event_loop()

# 基于loop建立的task對象
# 通過creat_task建立任務對象,将協程對象封裝進去
task=loop.create_task(c)
print(task)
# 注冊并執行任務
loop.run_until_complete(task)
print(task)
           
python爬蟲----DAY8----異步爬蟲之協程異步爬蟲之協程

3. 任務對象future的使用

import asyncio

async def request(url):
    print('正在請求:',url)
    print('請求結束',url)
#  async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')

# future的使用

# 建立事件循環對象
loop=asyncio.get_event_loop()
# 建立future
future=asyncio.ensure_future(c)
print(future)
#注冊并啟動
loop.run_until_complete(future)
print(future)
           

狀态與task相同

4. 回調函數的綁定和使用

import asyncio

async def request(url):
    print('正在請求:',url)
    print('請求結束',url)
    # 傳回,回調函數可以通過.result擷取傳回值
    return url
#  async修飾的函數,調用後傳回一個協程對象
c= request('www.baidu.com')

#綁定回調

# 回調函數的建立無需關鍵字async,參數隻能有一個,即回調函數的調用者,task對象
def callback_func(task):
    print(task.result())
#建立事件循環對象
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
#将回調函數綁定到任務對象中
task.add_done_callback(callback_func)
#注冊并啟動任務
loop.run_until_complete(task)

           
python爬蟲----DAY8----異步爬蟲之協程異步爬蟲之協程

5. 多任務協程

import asyncio
import time

#模拟請求發送
async def request(url):
    print('正在請求',url)
    time.sleep(2)
    print("下載下傳完畢",url)
# 請求清單
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']

# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
    c=request(url)
    task=asyncio.ensure_future(c)
    # 将建立的任務對象加入到任務清單中
    tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)

           
python爬蟲----DAY8----異步爬蟲之協程異步爬蟲之協程

原因:time是同步子產品,在特殊函數内部不可以出現不支援異步子產品對應的代碼,否則會中斷整個異步效果。 詳情看注意事項

解決辦法: 改用異步子產品,await asyncio.sleep(2)

import asyncio
import time

#模拟請求發送
async def request(url):
    print('正在請求',url)
    # time.sleep(2)
    # 使用異步子產品,
    await asyncio.sleep(2)
    print("下載下傳完畢",url)
# 請求清單
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']

# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
    c=request(url)
    task=asyncio.ensure_future(c)
    # 将建立的任務對象加入到任務清單中
    tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
           
python爬蟲----DAY8----異步爬蟲之協程異步爬蟲之協程

6. aiohttp的使用—實戰

詳情可看注意事項

import asyncio
import time
import aiohttp #使用該子產品中的ClientSession進行網絡請求發送
import requests
from lxml import etree

# 請求清單
urls=['https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
,'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD',
      'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
      ]
headers={
        "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.67',
}
#請求發送
async def get_request(url):
    #執行個體化一個session對象發送請求
    async with aiohttp.ClientSession() as sess:
        # 請求發送,手動挂起
        async with await sess.get(url=url,headers=headers) as response:
            #text()方法可以傳回字元串形式的響應資料
            #read()方法可以傳回二進制形式的資料
            #json()方法傳回的就是json對象
            # 注意在擷取響應資料操作之前要使用await進行手動挂起
            page_text=await response.text()
            return page_text

# 解析響應資料
def parse(task):
    page_text=task.result()
    tree=etree.HTML(page_text)
    title=tree.xpath('/html/head/title/text()')[0]
    print(title)
# 任務清單:存放多個任務對象
tasks=[]
# 計時
start_time=time.time()
for url in urls:
    c=get_request(url)
    task=asyncio.ensure_future(c)
    #綁定回調
    task.add_done_callback(parse)
    # 将建立的任務對象加入到任務清單中
    tasks.append(task)
# 建立任務循環對象
loop=asyncio.get_event_loop()
#注冊和啟動時不能直接放入清單,具體詳情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)