You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

asyncio+httpx爬虫在DigitalOcean VPS处理多关键词时挂死

问题现象

基于asyncio、httpx开发的Google广告爬虫,本地环境运行完全正常;部署至配置为8GB内存、4核AMD vCPU、25GB磁盘、Ubuntu 20.04系统的DigitalOcean VPS后,处理50个以上关键词时会无响应挂死,无任何错误日志输出。用户通过POST接口上传关键词CSV即可触发爬虫任务。

现有技术栈与配置
  • 后端框架:FastAPI,采用Redis Queue(RQ)将爬虫任务放入后台执行以规避请求超时,已配置RQ队列default_timeout=-1
  • 爬虫逻辑:通过ScraperAPI代理请求Google Shopping搜索结果,解析广告落地页域名后导出CSV文件
  • 服务部署:Web服务使用Gunicorn+UvicornWorker部署,已配置服务超时时间为0
  • 请求配置:代码中已设置httpx请求timeout=None,内置3次请求重试逻辑
已完成排查
  • 排除API层问题:在VPS上单独直接运行爬虫脚本,可复现相同挂死问题
  • 现象确认:爬虫固定在处理最后几个关键词时挂起数小时无进展,无报错输出
排查思路与解决方案

快速定位手段

先不要盲目改配置,第一时间定位卡点:

  1. 执行pip install py-spy安装采样工具
  2. 等爬虫挂死时,执行ps aux | grep 爬虫脚本名拿到进程PID
  3. 执行py-spy dump --pid <PID>直接打印所有协程、线程的调用栈,可直接看到代码具体卡在哪一行,是等待网络响应、锁竞争还是阻塞IO。

高频根因与修复

1. 无限超时配置导致半开连接永久卡死

timeout=None是当前配置里最高危的问题:

  • VPS到代理服务器的公网链路存在丢包概率,代理侧触发风控、长连接静默断开时,TCP连接不会主动发送断开标记,客户端会永久等待对端返回数据,既不报错也不触发重试,刚好对应无日志挂死的现象
  • 修复方案:
    • 移除timeout=None配置,给所有请求设置合理超时阈值,参考配置:timeout=httpx.Timeout(20.0, connect=10.0)
    • 为TCP连接开启keepalive探测,及时断开死连接,参考配置:
      import socket
      import httpx
      
      transport = httpx.AsyncHTTPTransport(
          socket_options=[
              (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
              (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60),
              (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 10),
              (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3),
          ]
      )
      # 全局复用这一个client实例,不要每个请求新建client
      client = httpx.AsyncClient(transport=transport, timeout=httpx.Timeout(20.0))
      

2. 无限制并发触发连接池耗尽/句柄泄漏

  • 本地网络到代理延迟低,连接周转快,50个并发可以正常跑;VPS公网延迟高,连接占用时间长,httpx默认连接池大小、系统默认文件描述符上限很容易被打满,后续请求会永久等待可用连接,表现为挂死
  • 如果每个请求都新建一个AsyncClient实例、请求结束后没有显式关闭响应,会进一步加剧连接泄漏
  • 修复方案:
    • 全局复用同一个AsyncClient实例,不要为单个请求新建客户端
    • 用asyncio.Semaphore限制爬虫最大并发数,根据代理QPS限制设置为10-20即可,不要用asyncio.gather一次性扔出所有请求
    • 调整httpx连接池配置适配并发量:初始化AsyncClient时传入limits=httpx.Limits(max_connections=50, max_keepalive_connections=20)
    • 临时执行ulimit -n 65535调高系统文件描述符上限,同时将配置写入/etc/security/limits.conf持久化,避免高并发下句柄耗尽

3. 同步代码阻塞asyncio事件循环

  • 如果在协程里直接跑同步IO操作(比如同步写CSV、用同步版BeautifulSoup做解析、文件读写),会阻塞整个事件循环,所有协程都无法调度
  • 本地测试数据量小、执行快,阻塞感知不明显;VPS上资源调度延迟高,最后几个任务刚好卡到阻塞点就会完全停住
  • 修复方案:所有同步阻塞逻辑用await asyncio.to_thread(同步函数, 参数)扔到线程池执行,不要直接在协程上下文里调用同步方法。

4. 系统层异常无感知

  • VPS内存不足时OOM Killer会静默杀掉进程,RQ fork模式运行任务时也可能出现子进程僵死、事件循环锁死的问题
  • 排查方式:执行dmesg | grep -i oom查看系统日志,确认有没有进程被OOM强杀的记录
  • 修复方案:RQ worker启动时加上--worker-class rq.worker.SimpleWorker参数,禁用fork模式跑任务,规避fork导致的asyncio事件循环异常。

内容的提问来源于stack exchange,提问作者dougj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 09:18:21