python併發執行request請求

TechSynapse發表於2024-06-26

在Python中,我們可以使用requests庫來傳送HTTP請求,並使用threadingmultiprocessingasyncio(配合aiohttp)或concurrent.futures等庫來併發執行這些請求。這裡,我將為我們展示使用concurrent.futures.ThreadPoolExecutorrequests庫併發執行HTTP請求的示例。

1.使用concurrent.futures.ThreadPoolExecutor併發傳送請求示例

首先,我們需要安裝requests庫(如果還沒有安裝的話):

bash複製程式碼

pip install requests

然後,我們可以使用以下程式碼來併發地傳送HTTP GET請求:

import concurrent.futures  
import requests  
  
# 假設我們有一個URL列表  
urls = [  
    'http://example.com/api/data1',  
    'http://example.com/api/data2',  
    'http://example.com/api/data3',  
    # ... 新增更多URL  
]  
  
# 定義一個函式,該函式接收一個URL,傳送GET請求,並列印響應內容  
def fetch_data(url):  
    try:  
        response = requests.get(url)  
        response.raise_for_status()  # 如果請求失敗(例如,4xx、5xx),則丟擲HTTPError異常  
        print(f"URL: {url}, Status Code: {response.status_code}, Content: {response.text[:100]}...")  
    except requests.RequestException as e:  
        print(f"Error fetching {url}: {e}")  
  
# 使用ThreadPoolExecutor併發地執行fetch_data函式  
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:  # 你可以根據需要調整max_workers的值  
    future_to_url = {executor.submit(fetch_data, url): url for url in urls}  
    for future in concurrent.futures.as_completed(future_to_url):  
        url = future_to_url[future]  
        try:  
            # 透過呼叫future.result()來獲取函式的返回值,這會阻塞,直到結果可用  
            # 但是請注意,這裡我們只是列印結果,沒有返回值,所以呼叫future.result()只是為了等待函式完成  
            future.result()  
        except Exception as exc:  
            print(f'Generated an exception for {url}: {exc}')

在這裡簡單解釋一下這個程式碼示例。

(1)我們首先定義了一個URL列表,這些是我們想要併發訪問的URL。

(2)然後,我們定義了一個函式fetch_data,它接收一個URL作為引數,傳送GET請求,並列印響應的狀態碼和內容(只列印前100個字元以節省空間)。如果發生任何請求異常(例如,網路錯誤、無效的URL、伺服器錯誤等),它會捕獲這些異常並列印錯誤訊息。

(3)使用concurrent.futures.ThreadPoolExecutor,我們可以輕鬆地併發執行fetch_data函式。我們建立了一個ThreadPoolExecutor例項,並指定了最大工作執行緒數(在這個例子中是5,但我們可以根據需要調整這個值)。然後,我們使用列表推導式將每個URL與一個Future物件關聯起來,該物件表示非同步執行的函式。

(4)最後,我們使用as_completed函式迭代所有完成的Future物件。對於每個完成的Future物件,我們呼叫result方法來獲取函式的返回值(儘管在這個例子中我們沒有使用返回值)。如果函式執行期間發生任何異常,result方法會重新引發該異常,我們可以捕獲並處理它。

這個示例展示瞭如何使用Python的concurrent.futures模組來併發地傳送HTTP請求。這種方法在IO密集型任務(如網路請求)上特別有效,因為它允許在等待IO操作完成時釋放CPU資源供其他執行緒使用。

2.requests庫併發傳送HTTP GET請求的完整Python程式碼示例

以下是一個使用concurrent.futures.ThreadPoolExecutorrequests庫併發傳送HTTP GET請求的完整Python程式碼示例:

import concurrent.futures  
import requests  
  
# 假設我們有一個URL列表  
urls = [  
    'https://www.example.com',  
    'https://httpbin.org/get',  
    'https://api.example.com/some/endpoint',  
    # ... 新增更多URL  
]  
  
# 定義一個函式來傳送GET請求並處理響應  
def fetch_url(url):  
    try:  
        response = requests.get(url, timeout=5)  # 設定超時為5秒  
        response.raise_for_status()  # 如果請求失敗,丟擲HTTPError異常  
        return response.text  # 返回響應內容,這裡只是作為示例,實際使用中可能不需要返回  
    except requests.RequestException as e:  
        print(f"Error fetching {url}: {e}")  
        return None  
  
# 使用ThreadPoolExecutor併發地傳送請求  
def fetch_all_urls(urls):  
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:  
        # 使用executor.map來自動處理迭代和Future的獲取  
        results = executor.map(fetch_url, urls)  
  
    # 處理結果(這裡只是簡單地列印出來)  
    for result in results:  
        if result is not None:  
            print(f"Fetched content from a URL (truncated): {result[:100]}...")  
  
# 呼叫函式  
fetch_all_urls(urls)

在這個示例中,我們定義了一個fetch_url函式,它接收一個URL,傳送GET請求,並返回響應內容(或在出錯時返回None)。然後,我們定義了一個fetch_all_urls函式,它使用ThreadPoolExecutor併發地呼叫fetch_url函式,並將結果收集在一個迭代器中。最後,我們遍歷這個迭代器,並列印出每個成功獲取到的響應內容(這裡只列印了前100個字元作為示例)。

請注意,我們在requests.get中設定了一個超時引數(timeout=5),這是為了防止某個請求因為網路問題或其他原因而無限期地等待。在實際應用中,根據我們的需求調整這個值是很重要的。

此外,我們還使用了executor.map來自動處理迭代和Future的獲取。executor.map函式會返回一個迭代器,它會產生fetch_url函式的返回值,這些值在函式完成後會自動從相應的Future物件中提取出來。這使得程式碼更加簡潔,並且減少了顯式處理Future物件的需要。

3.如何在Python中實現併發程式設計

在Python中實現併發程式設計,主要有以下幾種方式:

(1)使用threading模組
threading模組提供了多執行緒程式設計的API。Python的執行緒是全域性直譯器鎖(GIL)下的執行緒,這意味著在任意時刻只有一個執行緒能夠執行Python位元組碼。然而,對於I/O密集型任務(如網路請求),多執行緒仍然可以透過併發地等待I/O操作來提高效能。

示例:

import threading  
import requests  
 
def fetch_url(url):  
    try:  
        response = requests.get(url)  
        response.raise_for_status()  
        print(f"URL: {url}, Status Code: {response.status_code}")  
    except requests.RequestException as e:  
        print(f"Error fetching {url}: {e}")  
 
threads = []  
for url in urls:  
    t = threading.Thread(target=fetch_url, args=(url,))  
    threads.append(t)  
    t.start()  
 
# 等待所有執行緒完成  
for t in threads:  
    t.join()

(2)使用multiprocessing模組
multiprocessing模組提供了跨多個Python直譯器的程序間並行處理。這對於CPU密集型任務特別有用,因為每個程序都有自己的Python直譯器和GIL,可以充分利用多核CPU的並行處理能力。

示例:

from multiprocessing import Pool  
import requests  
 
def fetch_url(url):  
    try:  
        response = requests.get(url)  
        response.raise_for_status()  
        return f"URL: {url}, Status Code: {response.status_code}"  
    except requests.RequestException as e:  
        return f"Error fetching {url}: {e}"  
 
with Pool(processes=4) as pool:  # 設定程序池的大小  
    results = pool.map(fetch_url, urls)  
 
for result in results:  
    print(result)

(3)使用asyncio模組(針對非同步I/O)
asyncio是Python 3.4+中引入的用於編寫單執行緒併發程式碼的庫,特別適合編寫網路客戶端和伺服器。它使用協程(coroutine)和事件迴圈(event loop)來管理併發。

示例(使用aiohttp庫進行非同步HTTP請求):

import asyncio  
import aiohttp  
 
async def fetch_url(url, session):  
    async with session.get(url) as response:  
        return await response.text()  
 
async def main():  
    async with aiohttp.ClientSession() as session:  
        tasks = []  
        for url in urls:  
            task = asyncio.create_task(fetch_url(url, session))  
            tasks.append(task)  
 
        results = await asyncio.gather(*tasks)  
        for result, url in zip(results, urls):  
            print(f"URL: {url}, Content: {result[:100]}...")  
 
# Python 3.7+ 可以使用下面的方式執行主協程  
asyncio.run(main())

注意:asyncio.run()是在Python 3.7中引入的,用於執行頂層入口點函式。在Python 3.6及以下版本中,我們需要自己設定和執行事件迴圈。

(4)使用concurrent.futures模組
concurrent.futures模組提供了高層次的介面,可以輕鬆地編寫併發程式碼。它提供了ThreadPoolExecutor(用於執行緒池)和ProcessPoolExecutor(用於程序池)。

前面已經給出了ThreadPoolExecutor的示例,這裡不再重複。ProcessPoolExecutor的用法與ThreadPoolExecutor類似,只是它是基於程序的。

選擇哪種併發方式取決於我們的具體需求。對於I/O密集型任務,多執行緒或非同步I/O通常是更好的選擇;對於CPU密集型任務,多程序可能是更好的選擇。此外,非同步I/O通常比多執行緒具有更好的效能,特別是在高併發的網路應用中。

相關文章