ThreadPoolExecutor比手动管理threading.Thread更稳妥,因其内置任务队列、异常传播、自动回收等机制;max_workers建议从5起调,结合响应延迟与DNS能力设定;必须加非周期性随机延时并置于请求前;工作函数需自行捕获异常并重试。

多线程能显著提升单机爬虫的并发采集性能,但前提是它被用在对的地方、以正确的方式控制——不是开越多线程越快,而是让IO等待时间被填满,同时不触发反爬或资源耗尽。
为什么 ThreadPoolExecutor 比裸写 threading.Thread 更稳妥
手动管理 threading.Thread 容易漏掉 join()、线程异常未捕获、任务分发不均等问题,而 ThreadPoolExecutor 内置了任务队列、结果收集、异常传播和自动资源回收机制。
- 它默认使用
queue.Queue(线程安全),无需自己加锁处理共享 URL 列表 -
submit()返回Future对象,可统一用as_completed()或result()获取结果,避免阻塞主线程 - 超出
max_workers的任务会排队等待,不会瞬间创建数百线程把系统拖垮
max_workers 设多少才不翻车
10–20 是多数中小型爬虫的合理起点,但不能硬套。实际值取决于目标站点响应延迟、本地网络带宽、DNS解析能力,而非 CPU 核心数。
- 若平均响应耗时 800ms,设 10 个线程 ≈ 每秒 12–15 个请求;设 50 个线程反而可能因 DNS 超时或连接池打满导致大量
ConnectionError - 用
requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=10)显式限制连接池大小,防止底层 socket 耗尽 - 首次上线建议从 5 开始,观察日志中
429、ConnectionTimeout、Max retries exceeded出现频率再逐步上调
不做随机延时等于主动送封
固定间隔(如 time.sleep(1))极易被识别为机器行为;无延时则大概率触发 429 Too Many Requests 或 IP 封禁。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
- 必须用
random.uniform(0.8, 2.5)这类非整数、非周期性延迟,且应在每个线程内独立执行,而非主线程统一 sleep - 延时应放在请求发起前,而不是响应后——否则空闲线程仍会堆积任务,失去并发意义
- 遇到
429响应码时,可临时将该线程的后续延时倍增(例如 ×2),实现轻量级自适应限速
异常不隔离,整个线程池就变“脆皮”
一个线程里未捕获的异常(如 JSONDecodeError、SSLError)不会终止其他线程,但会导致该线程静默退出,任务丢失且无提示。
- 每个工作函数内部必须包裹
try/except Exception,至少记录url和repr(e) - 不要依赖
Future.exception()做兜底——有些异常(如 SIGINT 中断)不会被包装进 Future - 重试逻辑应封装在工作函数内,用
retrying或手动计数(最多 3 次),避免失败任务无限占着线程
真正卡住性能的往往不是并发数,而是 DNS 解析慢、SSL 握手长、响应体过大却没流式读取、或者解析环节用 BeautifulSoup 处理 5MB HTML ——这些地方的优化收益,经常比把线程从 10 拉到 30 更实在。


















