摘要:本文把“速度”和“稳定”放在同等重要的位置,系统呈现 Python 爬虫从单机并发、网络优化、到分布式扩展的全流程工程实践。涵盖并发模型选择、HTTP 链路优化、代理池与会话复用、Scrapy 与 aiohttp 的最佳实践、分布式任务队列、可观测性、容量规划与合规注意事项。文中附带可直接落地的代码片段、配置示例与工程化 checklist,适合作为生产环境性能化改造的操作手册。


先说结论(你应该记住的 5 件事)

  1. IO 为王:HTTP 抓取是 I/O 密集型任务,优先使用异步 IO(aiohttp / httpx)或高效框架(Scrapy + Twisted)。
  2. 会话与连接复用:长连接(Keep-Alive)、连接池能显著降低延迟与 CPU 消耗。
  3. 限速 & 回退:高并发要配配额(Token bucket)和指数退避,避免被封或引发级联故障。
  4. 水平扩展:用任务队列(Redis/Kafka)+ 无状态 Worker 实现弹性扩展和故障隔离。
  5. 可观测性:性能优化必须度量 —— 请求吞吐、延迟 P50/P95/P99、错误率、队列长度应纳入监控与告警。

一、并发模型对比:线程 / 进程 / 异步 / 协程

  • 线程 (threading)
    适合少量并发(几十级),对 CPU 轻量但 Python GIL 限制线程内 CPU 并行。使用 requests + ThreadPoolExecutor 易实现,但在大并发(几百/几千)效率差,因每线程开销大。

  • 进程 (multiprocessing)
    适合 CPU 密集或需利用多核场景。爬虫通常 IO 密集,进程模式用于解析/模型推理等 CPU 步骤再好不过。

  • 异步(asyncio / aiohttp / httpx)
    最推荐的单机高并发方式:低内存开销、成百上千协程并发,充分利用单机网络带宽。适合大多数页面抓取场景。

  • 框架(Scrapy / Twisted)
    Scrapy 基于 Twisted 的异步模型,生态成熟:中间件、储存 pipeline、去重、调度器等内建。用于复杂抓取与分布式场景非常合适。


二、单机性能优化(秒开经验)

下面给出生产级单机优化清单与核心代码示例(aiohttp)。

2.1 核心原则

  • 使用连接池长连接(Keep-Alive),减少 TCP/TLS 握手。
  • 合理设置 超时重试策略(避免长期阻塞)。
  • 控制并发(Semaphore / Connector.limit)并对目标域实施 per-host 限速
  • 使用 HTTP/2(若目标支持)可进一步提升多请求复用效率。
  • 尽量直接请求 JSON 接口而非渲染页面;遇渲染仅用 Playwright/Headless 做必要抓取。

2.2 aiohttp 高并发模板(推荐)

# aiohttp_high_concurrency.py
import asyncio, aiohttp, async_timeout
from asyncio import Semaphore
from tenacity import retry, wait_exponential, stop_after_attempt

CONCURRENCY = 500  # 协程并发上限(根据机器/带宽调)
PER_HOST_LIMIT = 50

sem = Semaphore(CONCURRENCY)

async def fetch(session, url, proxy=None):
    async with sem:
        try:
            async with async_timeout.timeout(15):
                async with session.get(url, proxy=proxy) as resp:
                    if resp.status == 200:
                        return await resp.text()
                    else:
                        return None
        except Exception as e:
            return None

async def main(urls, proxies=None):
    conn = aiohttp.TCPConnector(limit=CONCURRENCY, force_close=False, enable_cleanup_closed=True)
    headers = {"User-Agent":"Mozilla/5.0 (compatible; MyCrawler/1.0)"}
    async with aiohttp.ClientSession(connector=conn, headers=headers) as session:
        tasks = [asyncio.create_task(fetch(session, u, proxy=(None if not proxies else proxies[i%len(proxies)]))) for i,u in enumerate(urls)]
        return await asyncio.gather(*tasks)

# 运行示例
# asyncio.run(main(list_of_urls))

说明TCPConnector.limit 控制连接池大小;Semaphore 做全局并发保护。tenacity 可用于更复杂重试策略。

2.3 requests + HTTPAdapter(保守方案)

如果必须用 requests,务必启用 SessionHTTPAdapter

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

s = requests.Session()
retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[429,500,502,503,504])
s.mount('https://', HTTPAdapter(pool_connections=100, pool_maxsize=100, max_retries=retries))
s.mount('http://', HTTPAdapter(pool_connections=100, pool_maxsize=100, max_retries=retries))

r = s.get('https://example.com', timeout=10)

三、HTTP 层优化(网络链路与协议)

3.1 Keep-Alive 与连接池

  • 每次建立 TCP/TLS 握手成本高,保持连接复用(HTTP keep-alive)能大量减少延迟和 CPU。
  • 确保客户端使用持久 Session(requests)或长生命周期 ClientSession(aiohttp)。

3.2 HTTP/2 & Multiplexing

  • HTTP/2 在同一 TCP 连接上多路复用请求,适合目标支持 HTTP/2 的场景(尤其大量小请求)。
  • httpx 原生支持 HTTP/2,aiohttp 在 3.x 也有 HTTP/2 支持插件(注意成熟度)。

3.3 TCP/TLS 调优

  • 开启 TCP 快速打开、调节 tcp_tw_reuse 等内核参数(仅在你能控制部署机器时)。
  • TLS 协商成本:考虑使用会话复用(session ticket)与启用 HTTP/2。

3.4 DNS 缓存

  • 大量短链接请求要启用 DNS 缓存或预解析,避免频繁 DNS 查询成为瓶颈。
  • aiodns + aiohttp 或本地 dnsmasq。

四、代理池设计与使用注意

代理能扩展来源 IP 和地理位置,但增加复杂性:

4.1 代理池能力:

  • 健康检查(HTTP 请求 + RTT)
  • 分数机制(成功率、响应时延)
  • 地理/ASN 标签(按需选取)
  • 并发限制(每个代理单 IP 并发上限)
  • 自动替换/退役

4.2 典型策略

  • 优先使用高质量代理 做核心任务;低成本代理用于非关键任务。
  • 对每个代理做 rate limiter,避免 provider 侧短时间大量请求导致被封。
  • 监控代理失败率,自动把坏代理下线入 quarantine。

五、Scrapy 优化实战(生产级设置)

如果你在用 Scrapy,下面是常见的生产级 setting 调优:

# settings.py(摘)
CONCURRENT_REQUESTS = 200
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 0
DOWNLOAD_TIMEOUT = 20
RETRY_ENABLED = True
RETRY_TIMES = 3
DOWNLOAD_DELAY = 0.1       # 或使用 AutoThrottle
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 0.5
AUTOTHROTTLE_MAX_DELAY = 10
HTTPCACHE_ENABLED = False  # 抓取大量实时数据一般不开缓存

中间件

  • 开启 HttpProxyMiddleware 和自定义 ProxyMiddleware 做代理轮换。
  • 使用 Downloader Middlewares 做请求签名、header 注入、动态 UA。
  • 对抓取失败做精细化处理(分类错误、是否重试、是否降级)。

六、分布式架构:任务队列 + 无状态 Workers

6.1 为什么要分布式?

单机瓶颈往往在网络带宽、CPU、内存或被目标端限速。分布式让你弹性扩展并隔离失败。

6.2 常见组件组合

  • 调度/队列:Redis(轻量)、Kafka(高吞吐/持久)、RabbitMQ(可靠)
  • Worker:无状态,按需扩展(Docker + K8s)
  • 去重/状态:Redis + Bloom Filter(内存节省)或数据库(MongoDB)
  • 存储:MongoDB/ClickHouse/Postgres/S3(按分析/归档需求)
  • 监控:Prometheus + Grafana + ELK

6.3 Scrapy-Redis 示例

  • 把 Scrapy 的调度器改为 redis-based,实现分布式调度与去重。
  • 优点:内置断点续抓、任务队列共享。

6.4 无状态 Worker 模式(示意)

Producer -> Redis Queue -> Worker Group (Docker/K8s) -> Parser -> DB
  • Worker 只负责抓取与初步解析,解析结果入库。
  • 控制器根据队列长度自动扩容 Worker(Kubernetes HPA)。

七、存储与 IO 优化

  • 写入使用批量(bulk insert)以降低 I/O 开销(MongoDB insert_many、Postgres COPY、ClickHouse batch)。
  • 避免同步写入阻塞抓取,建议:抓取 -> 临时队列(Kafka/Redis)-> 后台写入进程。
  • 使用压缩(gzip/snappy)存储归档,节约磁盘与网络带宽。

八、可观测性:如何测量“快如闪电”

必须量化优化效果。关键指标:

  • 吞吐(requests/sec)
  • 平均延迟(P50/P95/P99)
  • 成功率(2xx / 总请求)
  • 错误分布(4xx/5xx/timeout)
  • 队列长度、worker 数量、代理失效率
  • 资源使用:CPU、内存、网络带宽

工具链建议:

  • 应用指标:Prometheus + Grafana(自定义 exporter)
  • 日志与跟踪:ELK / Loki + traces(Jaeger)
  • 压力测试:locust、wrk 或自写脚本对抓取系统做负载评估

九、性能实验范例(如何做对比测试)

  1. 设定基线:固定 URL 列表,n 次运行,记录平均耗时、成功率。
  2. 变更一项(例如开启连接池、使用 HTTP/2、增加并发数),重复跑 10 次取均值与方差。
  3. 使用 P50/P95/P99 判断尾延迟影响。
  4. 监控系统资源是否成为瓶颈(带宽/CPU/文件句柄)。

十、运维与生产化最佳实践

  • 配置管理:把并发、超时、重试、代理来源等参数化(配置中心/环境变量)。
  • 熔断与降级:监测错误率,若超过阈值立即降速并告警。
  • 白名单 / 黑名单:对关键目标使用高品质代理并白名单。
  • 成本控制:代理费用、存储与计算成本需量化并做 SLA 分级。
  • 安全与合规:始终遵守目标网站 robots.txt 与法律法规;不要提供破解验证码/绕过认证的实现。

十一、工程化交付清单(上线前必须做的 15 项)

  1. 完整的 rate-limiter 实现(per-domain)
  2. 健康的代理池 + 自动替换机制
  3. 请求/响应追踪 ID(可回溯)
  4. 指标监控(吞吐、延迟、错误)与告警
  5. 批量写入管道与补偿机制(失败重试)
  6. 去重机制(Redis set / Bloom filter)
  7. 断点续抓(checkpoint)与任务幂等性
  8. 限流、熔断与降级策略
  9. 资源(带宽/文件句柄)配置与系统级优化(ulimit)
  10. 测试环境的灰度与压力测试脚本
  11. 日志和 HAR 保存策略(用于问题定位)
  12. 身份与凭证管理(token 刷新 & 缓存)
  13. 隐私/PII 处理与最小化存储
  14. 数据版本与追踪(DVC/metadata)
  15. 发生故障后的应急计划和回滚流程

十二、实用代码片段与工具清单(快速复制落地)

  • aiohttp 高并发模板:见上文 aiohttp_high_concurrency.py
  • httpx (HTTP/2)
import httpx
with httpx.Client(http2=True, limits=httpx.Limits(max_connections=100)) as client:
    r = client.get('https://example.com')
  • Scrapy settings:见第 5 节
  • Redis Bloom Filter(去重)pybloom_liveredisbloom 模块

结语:你现在该怎么开始(行动路线)

  1. 从测量开始:先跑基线测试,记录 P50/P95/P99 与吞吐。
  2. 启用连接复用(Session/ClientSession),观察改进。
  3. 切换到异步(若当前是同步),再测。
  4. 按需引入代理池与分布式队列,把系统做成可水平扩展的微服务。
  5. 把监控与告警放到第一位,性能问题要“可见”。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐