Counter用于累计值(如http_requests_total),需用rate()求导得速率;Gauge表示瞬时状态(如pending_urls),可直接查询当前值;错误率计算必须同窗口rate()相除,且Counter不可误作Gauge,否则导致计数重置错误。

用 Counter 和 Gauge 区分抓取速率与错误率的采集逻辑
抓取速率(如每秒请求数)不是直接“测出来”的,而是靠计数器差值推算;错误率则必须带标签维度才能做分母归一化。不区分这两类指标类型,会导致 Prometheus 查询时无法正确 rate() 或 sum(rate())。
关键点:
-
Counter用于累计值:如http_requests_total、parse_failures_total,必须用rate()函数在 PromQL 中求导才得速率 -
Gauge用于瞬时值:如pending_urls、current_concurrency,可直接查当前值或变化趋势 - 错误率 =
rate(http_errors_total{status_code=~"4..|5.."}[5m]) / rate(http_requests_total[5m])—— 分母必须是同一时间窗口的总请求数,否则比值失真 - 别把错误数写成
Gauge,否则rate()会报 “counter reset” 错误
暴露 metrics 接口时必须加 start_http_server(8000) 且避免端口冲突
很多爬虫跑在容器或已有 Web 服务旁,start_http_server() 默认监听 0.0.0.0:8000,若端口被占会导致整个进程启动失败,但错误不抛出,只静默跳过——这是最常被忽略的“监控没生效”原因。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 启动前先检查端口:
import socket; s = socket.socket(); s.bind(('', 8000)),捕获OSError后 fallback 到 8001 - 不要复用 Flask/FastAPI 的主端口暴露 metrics,Prometheus 客户端库不支持 WSGI/ASGI 集成,硬塞进路由会导致
/metrics返回 404 或内容错乱 - 在 Scrapy 中,务必在
Spider.open_spider之外初始化指标,否则每次重试新 spider 实例都会重复注册同名指标,Prometheus 抓取时报duplicate metrics
告警规则里写 rate() 必须配对使用相同时间窗口
比如你设了 rate(http_errors_total[5m]) > 0.1,但分母用了 rate(http_requests_total[1m]),那错误率可能虚高 5 倍——因为请求总数在短窗口内波动大,而错误数在长窗口里被平滑了。
真实可用的告警表达式示例:
- 持续 5 分钟错误率超 5%:
rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.05 - 过去 2 分钟零请求(爬虫卡死):
rate(http_requests_total[2m]) == 0 - 单个状态码突增(如 429 暴涨):
rate(http_errors_total{status_code="429"}[3m]) > 10 and rate(http_errors_total{status_code="429"}[3m]) > 2 * rate(http_errors_total{status_code="429"}[10m])
注意:所有 rate() 的窗口长度必须一致,且不能小于 scrape interval(默认 15s),否则数据稀疏导致计算失效。
日志和指标双上报时,错误计数必须对齐,否则告警不准
很多团队一边用 logging.error() 记日志,一边用 http_errors.inc() 上报指标,但两者逻辑不一致:比如网络超时没进 except 分支、或重试成功后又把失败计了一次。结果就是 Grafana 看到错误率飙升,但日志里找不到对应堆栈。
解决办法只有两个:
- 所有错误计数必须从同一个出口触发:封装一个
report_error(status_code, url)函数,内部同时调用logger.error()和http_errors.labels(status_code=status_code).inc() - 禁止在
except块外手动调用.inc(),尤其不要在重试循环里多次 inc ——Counter是累计值,不是“本次失败次数” - 调试时用
curl http://localhost:8000/metrics | grep http_errors对比日志时间戳,确认是否漏报或重复
最麻烦的是解析异常:它不走 HTTP 层,但业务上等同于失败。这类必须单独建 parse_failures_total Counter,并和 HTTP 错误一起纳入整体错误率公式,否则监控就缺了一块骨头。


















