天天看點

回調函數Callback —從同步思維切換到異步思維

我們平時使用 Requests 的時候,一般是這樣寫代碼的:

import requests

def parse(html):
    print('對 html 進行處理')

html = requests.get('url')
parse(html)           

複制

這是一種非常常見的直線性思維,我先請求網站拿到 html,然後我再把 html 傳給負責處理的函數。在整個過程中,“我“擔任着排程的角色。

在這種思維方式的影響下,有些同學即使在使用 aiohttp 寫異步爬蟲,也是這樣寫的:

import aiohttp
import asyncio


async def request(url):
    async with aiohttp.ClientSession() as session:
        resp = await session.get(url)
        html = await resp.text(encoding='utf-8')

def parse(html):
    print('處理 html')


async def main():
    url_list = [url1, url2, url3, url4]
    tasks = []
    for url in url_list:
        tasks.append(request(url))
    html_list = await asyncio.gather(*tasks)
    for html in html_list:
        parse(html)


if __name__ == '__main__':
    asyncio.run(main())           

複制

确實,這些 URL 的網絡請求是異步了,但是卻必須等到所有 URL 全部請求完成以後,才能開始處理這些 HTML。假如其中一個 URL 通路隻需要1秒鐘,其他的 URL 請求需要3秒鐘。那麼這個1秒鐘的請求結束以後,還需要等待2秒,才能開始進行處理。

于是,有些同學會修改代碼,多包裝一層函數:

import aiohttp
import asyncio


async def request(url):
    async with aiohttp.ClientSession() as session:
        resp = await session.get(url)
        html = await resp.text(encoding='utf-8')

def parse(html):
    print('處理 html')

async def get(url):
    html = await request(url)
    parse(html)

async def main():
    url_list = [url1, url2, url3, url4]
    tasks = []
    for url in url_list:
        tasks.append(get(url))
    await asyncio.gather(*tasks)


if __name__ == '__main__':
    asyncio.run(main())           

複制

get()

函數整體負責擷取一個 URL 的源代碼并對它進行解析。然後讓

get()

函數異步。

這樣做确實能夠解決問題,但是大家如果仔細體會就會發現,在

get()

函數裡面的代碼寫法,還是用的同步處理的思想。

既然要寫異步代碼,那麼我們腦子裡就要一直記住——很多個請求會同時發出,但是我們并不知道他們什麼時候完成。與其讓我們去等待它完成,然後再把完成結果傳給另外一個函數。不如讓這些請求在結束的時候,自行主動把結果傳給處理函數。

有了這種思想以後,我們再來修改一下上面的代碼:

import aiohttp
import asyncio


async def request(url, callback):
    async with aiohttp.ClientSession() as session:
        resp = await session.get(url)
        html = await resp.text(encoding='utf-8')
    callback(html)


def parse(html):
    print('處理 html: ', html)


async def main():
    url_list = [
 'http://exercise.kingname.info/exercise_middleware_ip/1',
 'http://exercise.kingname.info/exercise_middleware_ip/2',
 'http://exercise.kingname.info/exercise_middleware_ip/3',
 'http://exercise.kingname.info/exercise_middleware_ip/4',
 'http://exercise.kingname.info/exercise_middleware_ip/5',
 'http://exercise.kingname.info/exercise_middleware_ip/6',
 ]
    tasks = []
    for url in url_list:
        tasks.append(request(url, parse))
    await asyncio.gather(*tasks)


if __name__ == '__main__':
    asyncio.run(main())           

複制

運作效果如下圖所示:

這種寫法,初看起來與用get()函數包裝沒什麼差別,但是他們在思維方式上卻完全不一樣。

via:https://mp.weixin.qq.com/s/2EhxWqZRBibr3Mb1mRZR8Q