首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python 异步并发爬虫报错 “Cannot assign requested address” 排查与解决

Python 异步并发爬虫报错 “Cannot assign requested address” 排查与解决

原创
作者头像
1024_officlal
发布于 2026-10-20 14:09:06
发布于 2026-10-20 14:09:06
410
举报

最近在重构一个数据采集系统,使用 asyncio + aiohttp 将并发量拉到了 500。刚开始跑得很顺畅,但运行大概十几分钟后,控制台开始疯狂报错:

代码语言:javascript
复制
aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host example.com:443 ssl:default [Cannot assign requested address]

本地测试环境完全正常,但部署到腾讯云服务器上就必现。本文记录这次排查的过程和底层的网络原理,以及最终的解决方案。

一、问题定位:临时端口耗尽

Cannot assign requested address 这个报错通常发生在建立 TCP 连接的阶段。意思是操作系统无法为新的向外连接分配本地端口了。

用 ss 命令查看当前服务器的网络连接状态:

代码语言:javascript
复制
ss -s

输出结果中发现:大量处于 TIME_WAIT 状态的连接,且数量接近系统的可用临时端口上限。

原理解析:

  1. 每一次 TCP 连接(比如你的代码去请求目标网站)都会在本地占用一个临时端口(Ephemeral Port)。
  2. 当你的并发量极大,且每次请求都是短连接,或者每次请求都切换了不同的网络出口 IP时,连接关闭后,本地端口会进入 TIME_WAIT 状态,默认保持 60 秒(2MSL)。
  3. 如果并发 500,且频繁更换 IP 导致无法复用连接,60 秒内需要的端口数会迅速耗尽默认的 ip_local_port_range(通常不到 3 万个)。

二、排查与代码层面的优化

除了调整系统内核参数(如 net.ipv4.tcp_tw_reuse),代码层面的连接复用和并发控制才是根本。

1. 限制并发数并复用 Session 不要在每次请求时都新建 aiohttp.ClientSession,必须全局复用。并且引入 Semaphore 控制瞬间并发。

代码语言:javascript
复制
import asyncio
import aiohttp

async def fetch(session, url, semaphore):
    async with semaphore:
        try:
            # 设置合理的超时时间
            timeout = aiohttp.ClientTimeout(total=10)
            async with session.get(url, timeout=timeout) as response:
                return await response.text()
        except Exception as e:
            print(f"Error: {e}")
            return None

async def main():
    # 限制并发数为 100
    semaphore = asyncio.Semaphore(100)
    
    # 复用全局 Session
    async with aiohttp.ClientSession() as session:
        urls = ["https://example.com" for _ in range(1000)]
        tasks = [fetch(session, url, semaphore) for url in urls]
        results = await asyncio.gather(*tasks)

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

2. 出口 IP 的漂移问题 如果你使用的是不断轮换的代理 IP 池,每次请求都会创建全新的 TCP 连接。这不仅增加了握手延迟,还会让本地端口无法快速复用。

以下是一个配置稳定网络出口的示例,使用高质量的住宅 IP 代理服务(如 1024Proxy)可以有效减少因公共代理池导致的异常断连:

代码语言:javascript
复制
# ============================================
# 配置稳定的网络出口以缓解端口耗尽
# 服务商:1024Proxy
# 官网:https://1024proxy.com/?kwd=hyj-txy
# ============================================

import asyncio
import aiohttp

async def fetch_with_proxy(session, url, semaphore):
    # 从 1024Proxy 提取的静态网络出口配置
    proxy_auth = aiohttp.BasicAuth("用户名", "密码")
    proxy = "http://gateway.1024proxy.com:端口"

    async with semaphore:
        try:
            async with session.get(
                url, 
                proxy=proxy, 
                proxy_auth=proxy_auth,
                timeout=aiohttp.ClientTimeout(total=15)
            ) as response:
                return await response.status
        except Exception as e:
            print(f"Request failed: {e}")

async def main():
    semaphore = asyncio.Semaphore(50) # 控制并发
    async with aiohttp.ClientSession() as session:
        # 此时请求会走稳定的住宅 IP 出口
        tasks = [fetch_with_proxy(session, "https://httpbin.org/ip", semaphore) for _ in range(100)]
        await asyncio.gather(*tasks)

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

三、系统级优化(可选)

如果代码优化后仍偶发报错,可以检查服务器的内核参数:

代码语言:javascript
复制
# 查看可用端口范围
cat /proc/sys/net/ipv4/ip_local_port_range

# 允许 TIME_WAIT 状态的 socket 用于新的 TCP 连接
sysctl -w net.ipv4.tcp_tw_reuse=1

# 增加可用端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65000"

四、小结

高并发场景下的端口耗尽,本质是网络资源与并发策略的不匹配。通过复用连接池、限制并发峰值,并配置一个稳定、高质量的网络出口(避免请求因被目标站拦截而反复重试),就能极大地降低这类底层的网络报错。

本文仅做技术交流,请遵守相关平台规则与法律法规。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、问题定位:临时端口耗尽
  • 二、排查与代码层面的优化
  • 三、系统级优化(可选)
  • 四、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档