跳转到内容
Guides 3 分钟阅读 803 次浏览

Python 异步代理

了解如何用 Python 构建高性能异步代理。本指南介绍 aiohttp 和 httpx,助你实现高效的网络操作。

Python
Python 异步代理

Python 中的异步代理使用 aiohttphttpx 等库高效管理多个并发网络请求,避免 I/O 操作阻塞主执行线程。

代理服务本质上是 I/O 密集型的,大部分运行时间都在等待上游服务器的网络响应或客户端请求。传统的同步(阻塞)I/O 模型每个线程一次只处理一个请求,导致资源利用率低下、可扩展性受限。基于 Python asyncio 框架的异步 I/O 则允许单个线程在等待 I/O 操作完成时切换上下文,从而管理大量并发连接。这种架构显著提升了代理的吞吐量和响应能力。

异步核心概念

Python 的 asyncio 库为异步编程提供了基础。关键要素包括:

  • 事件循环(Event Loop): 核心组件,负责调度和执行协程,处理 I/O 事件和回调。
  • 协程(async def): 可以暂停和恢复的函数。使用 async def 定义,通过 await 执行。
  • await 关键字: 用于暂停协程的执行,直到某个可等待对象(另一个协程、Future 或 Task)完成。这会把控制权交还给事件循环。

aiohttp 构建异步代理服务

aiohttp 是面向 asyncio 的异步 HTTP 客户端/服务器框架。它非常适合构建代理的入站(服务器)和出站(客户端)两个组件。

aiohttp 用作代理服务器

aiohttp.web 提供了构建 Web 服务器所需的工具,用于监听传入的客户端请求。

import aiohttp.web

async def handle_request(request):
    """
    处理传入请求的示例 handler。
    在真实的代理中,这里会转发请求。
    """
    return aiohttp.web.Response(text=f"Received: {request.method} {request.url}")

async def main():
    app = aiohttp.web.Application()
    app.router.add_route('*', '/{path:.*}', handle_request) # 捕获所有路由
    runner = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = aiohttp.web.TCPSite(runner, '0.0.0.0', 8080)
    await site.start()
    print("aiohttp proxy server started on port 8080")
    while True:
        await asyncio.sleep(3600) # 保持服务器运行

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

aiohttp 用作异步 HTTP 客户端

aiohttp.ClientSession 用于发起出站 HTTP 请求,这是把客户端请求转发到上游服务器的关键。它负责管理连接池和 cookie。

import aiohttp
import asyncio

async def fetch_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            response.raise_for_status() # HTTP 错误时抛出异常
            return await response.text()

async def example_client_usage():
    content = await fetch_url('http://httpbin.org/get')
    print(f"Fetched content: {content[:100]}...")

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

httpx 构建异步代理服务

httpx 是一个现代、功能完备的 Python HTTP 客户端,同时提供同步和异步 API。其异步能力构建在 asyncio 之上。

httpx 用作异步 HTTP 客户端

httpx.AsyncClient 是发起异步请求的主要接口。它提供类似 requests 的 API,对熟悉 requests 库的开发者非常直观。

import httpx
import asyncio

async def fetch_url_httpx(url):
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        response.raise_for_status() # HTTP 错误时抛出异常
        return response.text

async def example_httpx_client_usage():
    content = await fetch_url_httpx('http://httpbin.org/get')
    print(f"Fetched content (httpx): {content[:100]}...")

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

httpx 不提供服务器功能,它纯粹是一个客户端库。

aiohttphttpx 客户端对比

特性 aiohttp.ClientSession httpx.AsyncClient
用途 异步 HTTP 客户端与服务器框架。 异步(及同步)HTTP 客户端。
API 风格 更底层的 asyncio 集成,代码更冗长。 requests 的 API,通常更简洁。
HTTP/2 支持 客户端无原生 HTTP/2 支持。 客户端原生支持 HTTP/2。
HTTP/3(QUIC)支持 无。 通过 quic-go(Rust)提供实验性支持。
WebSocket 客户端 支持。 不支持。
依赖 multidictyarlasync_timeoutattrs httpcoreidnacertifi`sniffio(依赖极少)。
连接池 ClientSession 管理。 AsyncClient 管理。
重定向处理 自动,可配置。 自动,可配置。
流式响应 支持,使用 response.content.read() 支持,使用 response.aiter_bytes()
代理配置 ClientSession 方法中直接使用 proxy 参数。 AsyncClient 和请求中直接使用 proxies 参数。

构建代理服务器时,需要 aiohttp 的服务器能力。至于出站客户端组件,两者都可行。httpx 通常提供更简单的 API 和内置的 HTTP/2 支持,这可能更有优势。

aiohttp(服务器)和 httpx(客户端)构建异步代理

该方案用 aiohttp 处理传入的代理请求,用 httpx 将其转发到目标服务器。这种组合通常能在服务器控制力与客户端简洁性/功能之间取得良好平衡。

import aiohttp.web
import httpx
import asyncio
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 只初始化一次 httpx.AsyncClient 以复用连接池
# 该客户端将用于所有出站请求
# 设置默认超时以避免连接挂起
OUTGOING_CLIENT = httpx.AsyncClient(timeout=30.0) 

async def proxy_handler(request):
    """
    处理传入的客户端请求,使用 httpx 转发,
    并将响应返回给客户端。
    """
    target_url = str(request.url).lstrip('/') # 去掉路径开头的斜杠

    # 重建目标 URL,保留协议、主机和查询参数
    # 在典型的正向代理中,客户端发送完整 URL(例如 GET http://example.com/path)
    # 在反向代理中,服务器可能只拿到路径,因此需要一个基础 URL。
    # 本示例假设为正向代理,完整 URL 位于路径中。
    # 若为反向代理,则应在前面拼接固定的基础 URL:
    # target_url = f"http://upstream.example.com{request.url.path_qs}"

    # 提取请求头,排除 hop-by-hop 头和代理专用头
    headers = {
        k: v for k, v in request.headers.items() 
        if k.lower() not in ['host', 'connection', 'keep-alive', 'proxy-authenticate', 
                             'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 
                             'upgrade', 'via', 'x-forwarded-for', 'x-real-ip']
    }

    # 如果尚未存在,则添加 X-Forwarded-For
    client_ip = request.remote
    if client_ip:
        headers['X-Forwarded-For'] = headers.get('X-Forwarded-For', '') + (', ' if headers.get('X-Forwarded-For') else '') + client_ip

    request_method = request.method
    request_body = await request.read() if request_method in ('POST', 'PUT', 'PATCH') else None

    logger.info(f"Proxying {request_method} {target_url} from {request.remote}")

    try:
        # 使用 httpx 转发请求
        proxy_response = await OUTGOING_CLIENT.request(
            method=request_method,
            url=target_url,
            headers=headers,
            content=request_body,
            params=request.query # 单独传递查询参数
        )
        proxy_response.raise_for_status() # 4xx/5xx 响应时抛出异常

        # 为客户端准备响应
        response_headers = {
            k: v for k, v in proxy_response.headers.items()
            if k.lower() not in ['content-encoding', 'transfer-encoding', 'connection'] # hop-by-hop 头
        }

        # 以流式方式回传响应体,避免把大响应全部载入内存
        response = aiohttp.web.StreamResponse(status=proxy_response.status, headers=response_headers)
        await response.prepare(request)
        async for chunk in proxy_response.aiter_bytes():
            await response.write(chunk)
        await response.write_eof()

        logger.info(f"Forwarded {request_method} {target_url} with status {proxy_response.status}")
        return response

    except httpx.HTTPStatusError as e:
        logger.error(f"HTTP error proxying {target_url}: {e.response.status_code} - {e.response.text}")
        return aiohttp.web.Response(
            status=e.response.status_code,
            text=f"Upstream HTTP Error: {e.response.status_code}\n{e.response.text}",
            content_type="text/plain"
        )
    except httpx.RequestError as e:
        logger.error(f"Network error proxying {target_url}: {e}")
        return aiohttp.web.Response(
            status=502, # Bad Gateway
            text=f"Proxy Network Error: {e}",
            content_type="text/plain"
        )
    except Exception as e:
        logger.exception(f"Unexpected error in proxy_handler for {target_url}")
        return aiohttp.web.Response(
            status=500,
            text=f"Proxy Internal Error: {e}",
            content_type="text/plain"
        )

async def start_proxy_server():
    app = aiohttp.web.Application()
    # 该路由处理所有方法和路径
    # 在正向代理中,客户端请求形如:GET http://example.com/path
    # aiohttp 会把路径解析为 '/http://example.com/path'
    # 我们在 proxy_handler 中去掉开头的 '/' 以得到完整 URL。
    app.router.add_route('*', '/{path:.*}', proxy_handler) 

    runner = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = aiohttp.web.TCPSite(runner, '0.0.0.0', 8080)
    await site.start()
    logger.info("Asynchronous proxy server started on http://0.0.0.0:8080")

    # 让服务器持续运行
    try:
        while True:
            await asyncio.sleep(3600) 
    finally:
        await OUTGOING_CLIENT.aclose() # 确保关闭 httpx 客户端
        await runner.cleanup()

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

代理服务的实践要点

请求头管理

代理必须谨慎处理 HTTP 头。
* hop-by-hop 头ConnectionKeep-AliveProxy-AuthenticateProxy-AuthorizationTETrailerTransfer-EncodingUpgrade)仅适用于两个节点之间的连接,不应转发。
* X-Forwarded-For / X-Real-IP:把客户端的 IP 地址添加或追加到这些头中,以便上游服务器了解原始请求方。
* Via:可选择添加 Via 头,表明代理参与了转发。

请求体流式传输

对于较大的请求体或响应体,务必以流式方式传输数据,而不是全部载入内存。aiohttp(入站用 request.read()、出站用 response.write())和 httpx(通过 content 参数和 response.aiter_bytes())都支持流式传输,可避免内存耗尽并降低延迟。

连接池

aiohttp.ClientSessionhttpx.AsyncClient 都实现了连接池。只实例化一次这些客户端并在多次请求间复用(如 OUTGOING_CLIENT 所示)对性能至关重要,这可以减少每次请求都建立新 TCP 连接的开销。

超时

代理服务容易受到上游服务器延迟或故障的影响。为出站请求设置严格的超时是避免资源枯竭、保持服务响应能力的关键。httpx.AsyncClientaiohttp.ClientSession 都支持配置连接超时、读取超时和总超时。

错误处理与重试

必须对网络问题(例如连接被拒绝、DNS 错误)和 HTTP 错误(例如上游返回 5xx)做健壮的错误处理。针对瞬时错误实现带指数退避的重试机制,以提升可靠性。

WebSocket 代理

aiohttp 在服务器端原生支持 WebSocket(aiohttp.web.WebSocketResponse)。代理 WebSocket 需要处理 Upgrade 头,并在客户端、代理和目标 WebSocket 服务器之间建立双向数据流。httpx 不支持 WebSocket 客户端连接。

性能调优

  • uvloop:对于 aiohttp 应用,安装 uvloop(用 Cython 编写、可直接替换 asyncio 事件循环)可显著提升性能。
  • 操作系统限制:高并发代理服务通常需要调整操作系统的打开文件描述符上限(ulimit -n)。
  • 资源监控:监控 CPU、内存和网络 I/O,以定位瓶颈并优化资源分配。
已更新: 03.03.2026
返回分类

试用我们的代理

遍布 100+ 国家的 20,000+ 代理

support_agent
GProxy Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.