直接用gevent.pool.Pool会卡死或不生效,因requests等库默认同步阻塞,gevent无法拦截其系统调用;必须在所有import(尤其requests)前执行gevent.monkey.patch_all(),否则协程不切换、请求仍串行等待。

为什么直接用 gevent.pool.Pool 会卡死或不生效?
旧爬虫项目通常基于 requests 或 urllib,而这些库默认是同步阻塞的——gevent 无法自动拦截它们的系统调用。如果你只是把原有循环改成 pool.spawn(),请求仍会串行等待 DNS 解析、TCP 握手、TLS 握手、响应读取,协程根本不会切换。
必须先打补丁,且顺序不能错:
-
gevent.monkey.patch_all()必须在所有其他 import(尤其是requests)之前执行 - 若用了
urllib31.26+,需额外加patch_socket=True, patch_ssl=True参数,否则 HTTPS 请求仍阻塞 - 如果项目里提前 import 了
threading或socket,补丁可能失效,得检查 import 链
如何安全替换原有 for 循环为 gevent.pool.Pool?
不要直接把整个爬虫函数丢进 spawn(),尤其当它内部含状态(如共享 session、计数器、文件句柄)时——协程间共享对象不是线程安全的,更不是协程安全的。
推荐做法是:每个任务函数保持无状态、输入输出明确,共享资源通过参数传入或由池外管理:
立即学习“Python免费学习笔记(深入)”;
- 用
requests.Session()实例要每个协程独享,或改用geventhttpclient这类原生协程客户端 - 避免在任务中修改全局 list/dict;需要收集结果时,统一用
pool.imap()+list()拉取返回值 - 设置合理
size:通常 20–100,远高于线程池(因协程开销极小),但受 DNS、远端限流、本地 fd 数限制
示例片段:
from gevent import monkey; monkey.patch_all() import gevent from gevent.pool import Pool import requests <p>def fetch_url(url):</p><h1>每次都新建 session,或从池外传入已 patch 的 session</h1><pre class='brush:python;toolbar:false;'>return requests.get(url, timeout=10).status_code
pool = Pool(50) urls = ['https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c'] * 100 results = list(pool.imap(fetch_url, urls)) # 自动并发、保序
遇到 BlockingIOError: [Errno 11] Resource temporarily unavailable 怎么办?
这是典型的系统级资源耗尽信号,不是代码逻辑错。常见原因有:
- Linux 默认单进程最多打开 1024 个文件描述符(fd),而每个 HTTP 连接至少占 1 个 socket fd;50 协程 × 多个连接 ≈ 快速触顶
-
gevent的 DNS 解析默认用同步getaddrinfo,高并发下会堆积并触发阻塞报错 - 目标站点主动限流(如返回 429),但你的代码没处理,重试风暴加剧连接堆积
解决方式:
- 启动前调用
resource.setrlimit(resource.RLIMIT_NOFILE, (65536, 65536))(需 root 权限或 ulimit 配置) - 用
gevent.resolver_thread.Resolver替换默认 resolver,或直接设环境变量GEVENT_RESOLVER=thread - 给
requests.get()加timeout=(3, 7),并捕获requests.exceptions.Timeout和ConnectionError
要不要把 requests 换成 geventhttpclient?
要看你的旧项目复杂度。如果只做简单 GET、无 cookie 管理、无重定向跟随、无复杂 header 构造,geventhttpclient 能省掉 monkey patch 的不确定性,性能也略高。但它不兼容 requests 接口,比如没有 .json() 方法,错误类型不同,重试逻辑要重写。
更现实的选择是保留 requests,但严格控制使用方式:
- 禁用连接池:
requests.adapters.HTTPAdapter(pool_connections=0, pool_maxsize=0) - 手动管理
Session生命周期,不用全局单例 - 加一层轻量封装,统一处理超时、重试、User-Agent 轮换
真正难调的从来不是并发数字,而是 DNS 缓存、TCP TIME_WAIT 回收、远端服务的连接复用策略——这些细节比换哪个库影响更大。



















