用Prometheus+Grafana构建Python爬虫全链路监控体系

发布时间:2026/10/14 19:11:58

用Prometheus+Grafana构建Python爬虫全链路监控体系 做爬虫项目最让人头疼的其实不是把数据抓下来而是不知道爬虫现在到底跑得怎么样今天有没有在稳定采集响应是不是已经慢到快要挨超时了抓回来的数据里到底有多少是重复的这些信息如果全靠“等用户投诉反馈”或“每天看日志”那你基本上就是在裸奔。我自己维护过好几个长跑型采集任务最深的一个体会就是给爬虫做监控这件事不是可有可无的锦上添花而是必须赶在被反爬策略、坏数据和服务器故障打趴下之前就先把监控系统架好。这个实战项目要解决的问题非常明确用Prometheus Grafana 这套经典监控组合给 Python 爬虫建立一套全链路监控体系实时量化采集成功率、请求耗时、数据吞吐量、重复率四大核心指标并且在 Grafana 里搭出一块可以一眼看清全局的可视化大屏。这里说的“全链路”指的是从爬虫任务调度开始到发送 HTTP 请求、接收响应、数据解析、去重、最后落库的完整过程。每个环节都要留下可量化的痕迹而不是只盯一个“请求成功与否”。这篇文章会把完整的设计思路、指标定义、埋点代码、Prometheus 配置、Grafana 大屏搭建以及我实际踩过的坑全部写出来。无论你是正在维护单机脚本还是手里跑着几十个采集任务这套思路都可以直接搬过去用。1. 为什么爬虫必须做监控只靠日志远远不够很多开发者对爬虫监控的理解还停留在“打印 log 每天看一眼”。说实话对于半小时就结束的一次性抓取任务这套凑合够用但只要是运行期超过一天、数据量超过几十万条的持续型采集任务日志方案就开始失灵了。1.1 日志监控的致命短板日志本身确实是全链路数据的载体但你得先回答几个问题你的日志有没有统一的格式有没有把请求耗时、返回状态、解析数量、去重命中这些字段结构化成可统计的形态就算都做到了面对几十万行日志你怎么在 10 秒内算出过去 5 分钟的采集成功率曲线日志是典型的“事后分析”工具。任务崩了你确实能从日志里翻出原因但那是几十分钟之后的事儿了。而爬虫任务最需要的是“当时就能感知趋势”的实时指标——比如成功率在下跌但还没跌穿阈值请求耗时在爬升但还没有触发超时。有了这种实时曲线你才能在故障发生之前动手而不是等用户报“数据不对”才惊觉问题。1.2 四个核心指标解决什么具体问题这里把标题里的“四大核心指标”逐个拆开讲清楚因为指标设计是整个监控体系的灵魂指标定义错了后面的可视化做得再漂亮也是白搭。采集成功率。这不是简单数一遍 2xx 状态码而是要分层来看。第一层是 HTTP 层面的成功率发了 1000 个请求返回 200 的有多少个第二层是业务层面的成功率返回了 200但页面结构和预期不符解析出来是 0 条记录的算不算成功第三层是落库成功率解析出来的 item 写进存储时到底成了没有。我之前在一个采集项目上就踩过这种坑请求成功率和业务成功率都显示 99%但打开数据库一看三万条数据因为字段长度超限全被拒了。所以设计指标时我会把成功率拆成crawler_requests_total、crawler_parse_success_total、crawler_store_success_total三个计数器缺一不可。请求耗时。耗时指标要能算出 P50、P95、P99 这类分位数。为什么不做平均值因为平均耗时会掩盖长尾问题。比如 1000 个请求里 990 个都是 0.5 秒剩下 10 个是 15 秒平均值大概 0.65 秒看着一切正常但 P99 已经是 15 秒——这已经接近超时阈值了说明部分请求来源的代理/网络链路已经出现问题。用直方图记录耗时样本再通过 PromQL 的histogram_quantile算分位数才能看到真实的长尾分布。另外耗时我还建议拆成“总耗时”和“连接建立耗时”两个维度如果总耗时在涨而连接耗时稳那多半是响应体读取慢如果连接耗时就在涨则要优先怀疑 DNS 和网络链路。数据吞吐量。吞吐量的作用是回答“当前采集速度是否够用”。比如你有 100 万条数据要抓按昨天的吞吐量 500 条/分钟算估计 33 个小时能跑完但实际跑了半天发现吞吐量掉到了 200 条/分钟说明下载链路或目标源响应已经出现降速需要扩容线程或调整调度。吞吐量一般用rate()或irate()函数基于计数器计算看趋势曲线而不是只看瞬时数字。重复率。重复率比你想象中更重要。我见过有项目抓了一个月产出了 600 万条数据结果跑重跑出来有一大半是重复的。重复率一旦失控后面的清洗、存储、计算全部都在烧钱。重复率有两种算法一种是“去重器拦截数 / 总解析数”一种是“入库后经过唯一键检测出来的重复数 / 入库总数”。我推荐的是第一种因为它在管道里前置能实时暴露问题第二种要等落库之后才发现反馈链路太长。实际埋点时用去重模块内部返回的“hit_duplicate”事件去递增计数器这样重复率指标就和去重模块天然耦合不会出现“说好去重了但指标没记录”的漏洞。1.3 实时量化的核心价值这四个指标合在一起覆盖了采集任务最关键的三个担忧链路通不通、跑得快不快、数据好不好。而且它们全部是量化指标不是泛泛的“状态正常”是可计算、可阈值告警、可对比历史的。有了这套量化体系你可以往下游团队扔出这样一句话“今天采集成功率 99.2%P95 耗时 1.8 秒吞吐量 420 条/分钟重复率 1.1%都在阈值以内”而不是“今天好像挺顺利的”。2. 监控架构与方案选型为什么是 Prometheus Grafana市面上监控方案很多有 Zabbix、OpenTelemetry、ELK 日志统计甚至你还可以自研一个上报接口。但我最终选了 Prometheus Grafana它们能扛住爬虫监控场景的核心诉求且社区体系和生态最成熟。2.1 架构设计总览整个监控链路可以分成三条线采集线爬虫进程内Python 代码中通过prometheus_client库定义各类指标爬虫任务运行时不断更新指标值同时在进程内开启一个 HTTP 端口提供/metrics端点拉取线PrometheusPrometheus Server 按照配置的时间间隔默认 15s 到 60s主动从爬虫进程的/metrics地址拉取指标快照存入内置时序数据库中展示与告警线Grafana AlertmanagerGrafana 从 Prometheus 查询数据绘制大屏Prometheus 内部通过告警规则计算出异常状态推给 Alertmanager 触发通知邮件/企业微信/钉钉均可。这里最关键的设计选择是Pull 模型。Prometheus 默认采用服务端主动拉取的模式这对爬虫场景尤其有利当爬虫进程挂了Prometheus 在下一个拉取周期拿不到数据时target 的up状态会变成 0你可以第一时间感知进程存活问题。如果用 Push 类方案比如自己搭个上报接口爬虫挂了以后最后一个心跳可能还是“正常”的从而造成“看似活着实则死亡”的误判。2.2 为什么不用 Zabbix 或 ELKZabbix 强在主机层面的监控CPU、内存、磁盘、网络流量。但它的指标设计偏向“机器维度”虽然你也可以塞入自定义指标但写起来比较重对业务指标请求成功率、重复率这类的建模、聚合能力也远不如 Prometheus 的标签体系和 PromQL 灵活。而爬虫监控的核心恰恰是业务指标所以 Zabbix 在起点上就不太合适。ELK 场景则是“日志聚合 搜索”。它适合做故障排查日志都收进去想查什么查什么。但它不是实时指标系统严格说需要额外建立指标抽取流程比如在写入 Elasticsearch 之前先用 Logstash 把关键字段拆出来再送往另一个指标库。链路长、成本高而且实时性优势全无。对我这种目标是“30 秒内看到成功率掉下来了”的人来说ELK 太笨重。2.3 Prometheus 指标模型与爬虫场景的契合点Prometheus 的指标模型是“指标名 标签集合”。比如crawler_requests_total{siteexample, taskproduct_list}和crawler_requests_total{siteexample, taskproduct_detail}这就是两个不同的时序序列但共享同一个指标名。这个模型对于爬虫来说太舒服了你可以用site标签区分不同的采集目标源用task标签区分同一个源下面的不同任务类型而在查询时又可以用sum()聚合所有目标源的整体数据想拆想合全看 PromQL 怎么写。相比之下如果在指标名里硬编码站点名那标签维度就彻底失去了之后想加个筛选条件都只能改代码重新上线。2.4 整体组件版本与选型清单组件用途我用的版本说明Python爬虫主体语言3.103.8 以上即可f-string 和类型注解支持更好prometheus_client埋点 SDK0.19.0官方库支持 Counter/Histogram/Gauge/Summaryrequests发送 HTTP 请求2.31.0配合超时和重试策略APScheduler任务调度3.10.x也可以用threadingwhile True替代Prometheus Server时序指标存储计算2.47.x单机版就够数据量大了再考虑远端存储Grafana可视化大屏10.x免费版完全够用Alertmanager告警通知配套 Prometheus不需要通知可以暂时不装页面底层的组件用prometheus_client的start_http_server(8000)即可它会在进程内起一个独立的 HTTP 服务线程不影响主业务逻辑。3. 核心实现Python 爬虫侧的指标埋点与全链路采集这一章是整个项目中最需要动手的部分。我会先给出一个完整、可运行的示例再解释每个埋点位置的设计意图。3.1 基础环境准备在写代码之前先把需要的 Python 包装好。如果你的环境比较干净建议用虚拟环境隔离依赖避免和系统环境互相污染。python -m venv crawler_monitor_env source crawler_monitor_env/bin/activate # Windows 下用 crawler_monitor_env\Scripts\activate pip install prometheus-client requests apscheduler这里prometheus-client就是埋点 SDKrequests负责发请求APScheduler用来做定时调度。如果你的爬虫是多线程模型后面会补充多进程场景下的注意事项。3.2 指标埋点前的关键设计决策开始写指标之前必须先定几个规范否则后续指标会乱成麻。指标命名规范。统一采用crawler_前缀后面的名词遵循模块_行为_单位的模式比如crawler_requests_total是请求模块的计数器结尾用_total表示 Countercrawler_request_duration_seconds是直方图单位写在名字里crawler_inflight_requests表示当前正在处理的请求数是 Gauge。标签使用边界。site、task、status这类高基数标签可以加但要控制基数量级。Prometheus 的标签组合越多时序数据量越大。如果你一个指标挂了 10 个标签每个标签又有 20 个值那一晚上就能生成上百万条序列单机版 Prometheus 很快就会撑不住。一般爬虫项目控制在 2~3 个标签为佳。派生指标不要用旧值覆盖。这一点非常重要埋点代码应尽量只维护最原始的 Counter 和 Histogram至于“成功率”“重复率”这类派生指标全部交给 PromQL 在查询时计算。这样做的原因是避免在 Python 代码里维护复杂的数值状态同时保证 Grafana 面板的时间窗口、聚合方式可以随时调整而不是写死在代码里。3.3 完整可运行示例全链路埋点爬虫下面这个crawler_monitor.py是核心示例。它模拟了一个简单的列表页采集任务拉取 JSON 数据解析出 item做内存去重然后打印代表落库成功并且每一步都更新对应的指标。import time import hashlib import random from threading import Thread import requests from apscheduler.schedulers.blocking import BlockingScheduler from prometheus_client import Counter, Histogram, Gauge, start_http_server # 1. 指标定义区 # 计数器只增不减用于计算成功率、吞吐量 TOTAL_REQUESTS Counter( crawler_requests_total, Total number of HTTP requests sent, [site, task], ) SUCCESS_REQUESTS Counter( crawler_success_requests_total, HTTP 2xx responses, [site, task], ) PARSE_SUCCESS_ITEMS Counter( crawler_parse_success_items_total, Items successfully parsed from response, [site, task], ) STORE_SUCCESS_ITEMS Counter( crawler_store_success_items_total, Items successfully written to storage, [site, task], ) DUPLICATE_ITEMS Counter( crawler_duplicate_items_total, Items filtered by deduplication, [site, task], ) # 直方图记录请求耗时用于计算 P50/P95/P99 REQUEST_DURATION Histogram( crawler_request_duration_seconds, HTTP request duration in seconds, [site, task], buckets(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0), ) # 仪表盘记录当前值比如实时去重集合大小 DEDUP_QUEUE_SIZE Gauge( crawler_dedup_queue_size, Current size of the deduplication queue, [site], ) # 简单内存去重集合仅演示 _seen_fingerprints set() DEDUP_SITE example_site def _fingerprint(item: dict) - str: 生成 item 的唯一指纹用于去重。实际项目中可以用 URL 标题 时间戳组合。 raw f{item.get(url, )}|{item.get(title, )} return hashlib.md5(raw.encode(utf-8)).hexdigest() def fetch(site: str, task: str, url: str) - requests.Response | None: 发送 HTTP 请求并统计请求总数、成功数和耗时。 TOTAL_REQUESTS.labels(sitesite, tasktask).inc() start time.perf_counter() try: resp requests.get(url, timeout(3, 10)) elapsed time.perf_counter() - start REQUEST_DURATION.labels(sitesite, tasktask).observe(elapsed) if resp.status_code // 100 2: SUCCESS_REQUESTS.labels(sitesite, tasktask).inc() return resp return None except Exception as exc: # 异常也要记录耗时否则超时类异常会从耗时统计中消失 elapsed time.perf_counter() - start REQUEST_DURATION.labels(sitesite, tasktask).observe(elapsed) print(f[fetch error] {exc}) return None def parse_and_store(site: str, task: str, resp) - int: 解析响应并模拟落库。返回待落库 item 数量。 try: data resp.json() # 假设接口返回 {items: [...]} items data.get(items, []) valid_count 0 for item in items: # 业务层成功率只有含标题和链接的才算有效数据 if item.get(title) and item.get(url): valid_count 1 # 去重逻辑 fp _fingerprint(item) if fp in _seen_fingerprints: DUPLICATE_ITEMS.labels(sitesite, tasktask).inc() continue _seen_fingerprints.add(fp) # 模拟落库成功真实项目中这里是写入数据库或消息队列 STORE_SUCCESS_ITEMS.labels(sitesite, tasktask).inc() PARSE_SUCCESS_ITEMS.labels(sitesite, tasktask).inc(valid_count) DEDUP_QUEUE_SIZE.labels(sitesite).set(len(_seen_fingerprints)) return valid_count except Exception as exc: print(f[parse error] {exc}) return 0 def run_task(): 单次采集任务请求 - 解析 - 去重 - 入库 site example_site task list_page # 示例 URL实际项目中替换为目标数据源 url https://example.com/api/v1/list?page1size20 resp fetch(site, task, url) if resp is None: print(fetch failed, skip this round) return count parse_and_store(site, task, resp) print(fparsed {count} items, total dedup queue: {len(_seen_fingerprints)}) def main(): # 1. 先启动 Prometheus 指标暴露端口 # 这样任何时刻访问 localhost:8000/metrics 都能拉到最新指标 start_http_server(8000) # 2. 再启动定时任务调度 scheduler BlockingScheduler() scheduler.add_job(run_task, interval, seconds15, max_instances1, coalesceTrue) print(crawler monitor started on :8000) scheduler.start() if __name__ __main__: main()代码里的关键点我逐条解释。TOTAL_REQUESTS和SUCCESS_REQUESTS必须放在发送请求之前的入口处进行inc()也就是无论如何这个请求都会计入分母。如果兄弟姐妹写代码时不小心把.inc()放到了try分支之后那异常请求就不会进入分母成功率就会变成无限接近 100% 的无效指标。所以埋在“入口”和埋在“出口”的语义完全不同。REQUEST_DURATION.observe()在异常分支也有调用这是第二个我特别在意的坑。如果不记录超时异常耗时那么所有“慢到超时”的请求都会从耗时直方图中消失你会看到 P99 一直停在某个界值但实际上已经有一堆请求死在超时阈值之外了。把异常耗时的样本照样丢进直方图才能让 P99 真实反映长尾情况。parse_and_store里我区分了“有效 item 数”和“成功落库数”。有些接口返回的列表里会混入残缺数据比如缺链接、缺标题、字段内容为空。业务成功率就是用PARSE_SUCCESS_ITEMS除以TOTAL_REQUESTS对应的有效 item 总量来算的。而STORE_SUCCESS_ITEMS则是真正写进库的数量两者分开统计后面排查问题时能直接看到是哪一环在掉链子。自定义直方图 buckets这里我给耗时设置的是(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0)。为什么不用默认桶因为爬虫请求的耗时分布往往比较极端大部分在 1 秒以内但偶尔会有 30 秒以上的长尾。如果桶范围太宽P95 甚至 P99 的精度都会很差。设了这些桶配合histogram_quantile计算时就能保证 1 秒以内有相对精细的分桶30 秒以上的异常也能被捕捉到。注意最大桶要大于你的超时时间否则超时请求会被丢进最后一个桶分位数计算照样失真。3.4 多进程 / 多线程场景下的额外配置示例里是单进程模型如果你的爬虫是 Scrapy 这类多进程架构prometheus_client有一个必须处理的特性它默认采用进程内共享内存的方式存储指标数据多进程下每个进程会各自维护一份指标集合如果不做处理Prometheus 拉取到的指标会相互覆盖数值忽高忽低。正确做法是开启多进程模式export PROMETHEUS_MULTIPROC_DIR/tmp/prometheus_multiproc然后在代码最前面加from prometheus_client import multiprocess from prometheus_client import CollectorRegistry, generate_latest registry CollectorRegistry() multiprocess.MultiProcessCollector(registry)同时start_http_server需要传入自定义 registrystart_http_server(8000, registryregistry)这个PROMETHEUS_MULTIPROC_DIR目录是多个进程共享指标文件的地方进程启动时要保证目录存在并且定期清理历史.db文件不然占用的磁盘会逐渐膨胀。我在实践中踩过这个坑跑了两周之后发现目录下积攒了上千个临时文件把磁盘直接写满连监控数据都拉不出来了。提供一个简单的清理方案——在任务调度里用find命令清理三天前的文件或者写个定时清理函数。3.5 任务调度与监控暴露的先后顺序代码中我刻意把start_http_server(8000)放在调度器启动之前。这个顺序是有讲究的如果scheduler.start()放在前面那么任务调度会阻塞主线程start_http_server没有机会被执行进程就永远无法暴露指标端口Prometheus 拉取直接失败。很多新手写代码会随手把start_http_server放在main()的最后一行然后程序跑起来发现/metrics一直connect refused。原因就是主线程被调度器占住了根本走不到后面那行代码。如果你希望调度器在后台运行而主线程继续做别的可以把调度器放线程里thread Thread(targetscheduler.start) thread.daemon True thread.start()不过通常情况下爬虫进程的任务就是采集用BlockingScheduler在主线程里阻塞反而让整个进程的生命周期更清晰。4. Prometheus 配置与告警规则把指标真正“采集”起来爬虫端的/metrics准备好了接下来需要让 Prometheus 知道该去哪拉数据、多久拉一次以及拉出来的数据什么情况下要报警。4.1 最小可用的 prometheus.yml安装 Prometheus 之后默认配置在/etc/prometheus/prometheus.yml源码安装则在工作目录。我们增加一个 job 来指向爬虫进程global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: crawler static_configs: - targets: [127.0.0.1:8000] metrics_path: /metricsscrape_interval在这里我建议 10~15 秒不需要更短。爬虫监控通常关心的是分钟级趋势15 秒的采样频率完全足够还能显著降低 Prometheus 的存储压力。如果你把scrape_interval提到 5 秒甚至 1 秒就会增加每个目标源的拉取负担而且很多告警规则的最小时间窗口也会随之变短很容易出现抖动误报。这里需要强调一个数据语义问题Prometheus 是拉取模型每个指标序列的时间间隔和scrape_interval严格绑定。你用rate(crawler_success_requests_total[5m])查询时Prometheus 会读取最近 5 分钟的所有样本点默认 15 秒一个点那就是 20 个点足够平滑。如果用 1 分钟窗口就只有 4 个点很容易受单次拉取影响产生毛刺。4.2 使用 PromQL 计算四大指标在配置完 Prometheus 之后你可以在 Prometheus 的 UI 页面默认 9090 端口里直接输入查询表达式验证。四大指标对应的 PromQL 如下这些表达式后面也会直接用于 Grafana 面板。采集成功率按任务维度sum(rate(crawler_success_requests_total[5m])) by (site, task) / sum(rate(crawler_requests_total[5m])) by (site, task)这个表达式用rate计算每秒的增长率再相除得到最近 5 分钟内的滚动成功率。用rate而不是increase是因为成功率是比率型指标需要对两个流量做归一化对比用increase(..., [5m])也可以但写法稍长而且rate自带平滑效果。请求耗时分位数P95histogram_quantile(0.95, sum(rate(crawler_request_duration_seconds_bucket[5m])) by (le, site, task) )histogram_quantile是 Prometheus 直方图分位数计算的专用函数。它接收一个分位目标0.95和一个由le小于等于标签维度的桶计数序列最终输出对应分位数的近似值。sum(...) by (le)是为了把多个实例/标签维度合并起来再算分位否则每个标签组合都会独立计算且结果不稳定。我建议在 Grafana 里同时看 P50、P95、P99 三条曲线不要只做一个 P95。P50 看整体水平P95 看普遍长尾P99 看极端异常三个视角各有用处。数据吞吐量每秒解析成功 item 数sum(rate(crawler_parse_success_items_total[1m]))这里把速率窗口设为 1 分钟是希望吞吐量曲线能更灵敏地反映当前采集速度。你甚至可以加上by (site, task)来看每个任务的分别吞吐量方便定位是哪个任务在拖后腿。重复率基于去重器拦截计数sum(increase(crawler_duplicate_items_total[5m])) by (site, task) / (sum(increase(crawler_parse_success_items_total[5m])) sum(increase(crawler_duplicate_items_total[5m])) by (site, task))重复率的语义是“去重器拦截的 item 数占实际解析到的 item 总数的比例”。分母必须是“有效解析数 重复拦截数”否则重复率会因丢弃重复导致的“分母缩小”而失真。这里直接用increase累加窗口内的增量比rate更直观。4.3 告警规则让异常主动找上门光有指标和曲线还不够告警才是让监控系统产生实际价值的一环。下面是一个针对爬虫场景的 Prometheus 告警规则示例保存在rules/crawler.ymlgroups: - name: crawler_alerts rules: - alert: CrawlerSuccessRateLow expr: | sum(rate(crawler_success_requests_total[5m])) by (task) / sum(rate(crawler_requests_total[5m])) by (task) 0.9 for: 5m labels: severity: warning annotations: summary: 任务 {{ $labels.task }} 成功率低于 90% description: 当前成功率 {{ $value | humanizePercentage }} - alert: CrawlerRequestDurationHigh expr: | histogram_quantile(0.95, sum(rate(crawler_request_duration_seconds_bucket[5m])) by (le, task) ) 10 for: 10m labels: severity: warning annotations: summary: 任务 {{ $labels.task }} P95 耗时超过 10 秒 - alert: CrawlerThroughputDrop expr: | sum(rate(crawler_parse_success_items_total[5m])) by (task) 50 for: 15m labels: severity: critical annotations: summary: 任务 {{ $labels.task }} 吞吐量低于 50 items/s这里有一个很重要的告警设计心得for字段一定要设置不能省。for: 5m表示指标需要连续 5 分钟满足条件才触发告警这能有效过滤掉单次抖动、网络波动带来的误报。如果设为for: 0m任何一个瞬时毛刺都会发告警几分钟后你的手机就会被通知轰炸最后大家会直接屏蔽告警渠道——这是比“没有告警”更糟糕的情况。告警阈值怎么定不要拍脑袋。先让系统跑一天记录正常趋势下的聚合值然后取“低于正常值 20%~30%”作为初步阈值。成功率阈值从 0.85~0.9 起步耗时阈值从“P95 超过超时时间的一半”起步逐步调参。不同任务的耗时差异极大统一阈值只会导致误报频发。5. Grafana 可视化大屏从指标到决策面板Prometheus 负责存储和计算数据Grafana 负责把冷冰冰的数字变成人话。这一章的实操目标是把一个仪表盘配出来让它一眼能看出爬虫整体健康度。5.1 接入 Prometheus 数据源Grafana 安装完成之后打开 Web UI默认 3000 端口依次进入 Configuration Data Sources Add data source选择 Prometheus。在 HTTP URL 一栏填入http://localhost:9090如果 Grafana 和 Prometheus 在同一台机器上点 Save Test 即可。如果测试失败重点排查两件事跨端口是否被防火墙拦截以及 Prometheus 是否监听在0.0.0.0而不是127.0.0.1。生产环境建议在配置里把 Prometheus 的监听地址改为0.0.0.0否则 Grafana 无法通过网络访问。5.2 创建大屏配置四个核心面板进入 Dashboards New New dashboard创建一个空的仪表盘开始添加面板。成功率面板可视化类型选择Time series。查询表达式就是前面写好的“成功率 成功请求速率 / 总请求速率”再加一层avg()或保持原始分组均可。如果只关心整体成功率可以用sum(rate(crawler_success_requests_total[5m])) / sum(rate(crawler_requests_total[5m]))。Legends 里设置为success_rate在面板右侧 Standard options 里把 Unit 设为percent (0-100)并将 Min 设为 0Max 设为 1这样纵轴从 0% 到 100%曲线走势更直观。耗时面板可视化类型选择Time series同时添加三个查询A:histogram_quantile(0.50, sum(rate(crawler_request_duration_seconds_bucket[5m])) by (le))LegendP50B:histogram_quantile(0.95, sum(rate(crawler_request_duration_seconds_bucket[5m])) by (le))LegendP95C:histogram_quantile(0.99, sum(rate(crawler_request_duration_seconds_bucket[5m])) by (le))LegendP99Unit 设为seconds。曲线重叠时可以使用面板右侧的 “Graph styles” 给 P95 / P99 加粗或颜色加权。这一块在优化时最有用如果 P50 平稳、P99 持续爬升说明有个别目标源响应到了临界点多半是该换代理或调整重试策略的时候了。吞吐量面板Time series表达式sum(rate(crawler_parse_success_items_total[1m]))。Unit 设为ops/sec或不设置直接是 “items/s”。在这个面板下面可以叠加一个柱状图面板显示每个任务的吞吐量占比哪个任务贡献了大头一目了然。重复率面板用Stat可视化类型更合适它直接显示一个当前值适合“实时概览”。查询表达式sum(increase(crawler_duplicate_items_total[5m])) / (sum(increase(crawler_parse_success_items_total[5m])) sum(increase(crawler_duplicate_items_total[5m])))Unit 设为percent (0-100)阈值在 10% 设置为黄色20% 设置为红色。5.3 大屏布局与使用建议Grafana 面板布局核心逻辑是“金字塔”最上面放最宏观的健康指标下面是分维度下钻。我习惯的布局方案是第一行采集成功率、P95 耗时、当前吞吐量、重复率四个 Stat/Time series 卡片并排这是“总览驾驶舱”第二行请求成功率按site或task分组的柱状图以及 P50/P95/P99 耗时趋势这是“分维度体检”第三行吞吐量趋势曲线、重复率趋势曲线、去重队列大小 Gauge 值这是“趋势与容量”第四行目标源存活状态即up 1/up 0用于第一时间发现抓取源不可达。对于多任务项目我还会在 Grafana 中设置模板变量Dashboard Settings Variables比如site变量取值来自label_values(crawler_requests_total, site)。之后所有面板的查询里都能写成sum(rate(crawler_requests_total{site$site}[5m]))这样你就有了一个可以切换站点筛选的大屏而不必为每个源单独建一块屏。5.4 告警通知接入Grafana 自身也内置了告警功能可以不依赖 Alertmanager。在 Alerting Contact points 里添加 webhook、邮件或企业微信机器人地址再到 Alert rules 里为面板配置阈值告警即可。但我个人更推荐在 Prometheus 里配规则、让 Alertmanager 统一发通知。这样可以保证告警产生、路由、抑制、静默都是同一套体系Grafana 只做展示职责分离更干净。6. 实战踩坑记录我在这套监控上摸出来的经验理论和示例代码都比较理想真正实施时总会有各种意外。这一章把我实际跑这套监控系统时踩过的坑和对应解决思路整理成一份速查表基本上能cover 掉 80% 的入门问题。问题现象根因解决方案指标全部为 0 或不存在Grafana 面板“No data”埋点位置在请求入口之后确认Counter.inc()在请求发出前已执行先访问/metrics检查是否有指标输出多进程任务指标错乱成功率一会儿 100% 一会儿 0%prometheus_client 未开启多进程模式设置PROMETHEUS_MULTIPROC_DIR环境变量并使用多进程 CollectorPrometheus 标红 target 不可达UI Targets 中 show 0 up防火墙拦了端口 / 服务地址绑定在了 127.0.0.1使用ss -lntp检查监听地址必要时绑定0.0.0.0验证curl localhost:8000/metrics是否通histogram_quantile结果为 NaNP95 曲线消失查询窗口内没有样本通常因为select时间范围小于scrape_interval将[5m]改为较大的窗口至少大于 3 倍scrape_interval耗时分布严重夸大P95 显示几十秒把resp.json()解析耗时也算进了请求耗时将observe()在“收到响应完毕”时刻调用不要在解析完成后调用重复率恒为 0面板显示 0%去重逻辑在指标埋点之前执行了continue导致没走到inc()确认duplicate_items.inc()在去重拦截分支内、continue之前执行吞吐量面板出现断崖式下降曲线偶尔掉到 0时间窗口内没有采集任务运行例如调度间隔大于窗口将rate()的窗口改成至少覆盖 2 个调度周期或调整调度间隔告警风暴凌晨连续 30 条通知阈值设置过低 /for未设置增加for: 5m或for: 15m并基于历史数据校准阈值磁盘被/tmp/prometheus_multiproc塞满Prometheus 无法创建新文件多进程指标临时目录没有清理定时清理超过一天的.db文件让子进程正常退出不要强制 kill这里单独展开讲两个最值得注意的坑。第一个坑请求耗时的测量边界。有一个项目我发现 P95 耗时从 2.8 秒涨到 12 秒但实际请求用curl测只有 1 秒多。后来看了代码发现埋点的observe()写在了resp.json()之后也就是说解析耗时全部被计入了请求耗时。如果解析逻辑本身就慢比如用了复杂的 XPATH或者数据清洗特别重那耗时指标就完全失真了。正确做法是只统计“从发起请求到响应完整接收”这一段网络耗时解析耗时要单独用另一个直方图crawler_parse_duration_seconds统计。这样定位问题时你才知道慢在网络还是慢在解析。第二个坑重复率指标和去重逻辑的先后顺序。我见过一个兄弟的代码去重时先if fp in seen: continue然后才想起duplicate_items.inc()结果重复计数永远少一条——因为continue已经把循环跳出去了。这个问题的本质是“计数逻辑跑到了控制流的下游”属于埋点代码位置不当。正确的做法是if fp in _seen_fingerprints: DUPLICATE_ITEMS.labels(sitesite, tasktask).inc() # 先计数 continue # 再跳过不要小看这类细节重复率指标失真会直接让你错判数据质量后面清洗数据时才发现异常那代价就大得多了。第三个值得说的经验多进程下临时文件的清理。使用 Prometheus 多进程模式时子进程退出会删除它自己的.db文件。但如果用了 Celery、fork 方式启动 worker某些子进程可能没有优雅退出文件就会残留。我在生产环境里写过一个简单的定时清理脚本通过 APScheduler 每 6 小时执行一次import shutil import os PROMETHEUS_MULTIPROC_DIR os.environ.get(PROMETHEUS_MULTIPROC_DIR, /tmp/prometheus_multiproc) def clean_old_metric_files(): now time.time() for fname in os.listdir(PROMETHEUS_MULTIPROC_DIR): fpath os.path.join(PROMETHEUS_MULTIPROC_DIR, fname) if os.path.isfile(fpath) and now - os.path.getmtime(fpath) 3600 * 24: os.remove(fpath)当然如果子进程还在运行它的 mmap 文件会有新的写入不会被误删。这个清理脚本部署了半年没有再出现过磁盘占满的问题。7. 这套监控体系的项目实践心得最后聊一点我个人实操中的体会。把爬虫监控做扎实最核心的不是 Grafana 面板有多花哨而是你对监控指标的语义理解是否到位。成功率要分层、耗时要抓长尾、吞吐量要看趋势、重复率要和去重逻辑耦合这四个指标背后其实是四个不同的运维问题链路健康、性能预算、产能评估、数据质量。指标之间不是孤立的它们组合起来才能构成完整画像。比如成功率掉 P95 涨 吞吐量降这大概率是目标源的反爬策略升级响应变慢且部分请求被拦截如果只是 P99 涨但成功率不变那多半是网络抖动或某个代理节点故障。在项目里实践这套体系时我也建议你把“加一个指标”的门槛放低。看到一段处理逻辑先问自己“这里如果出错我怎么通过指标发现”答不出来就值得加一个埋点。比如解析字段缺失、入库数据长度超限、调度器漏跑这些环节都能单独建模。等指标多到一定程度你会发现“恢复故障”不再是玄学而是在 Grafana 里对照曲线快速定位到具体模块的问题。大屏搭好之后我还顺手给团队做了两件事一是把关键告警规则做成配置模板新任务接入时直接复制改标签二是定期导出 Grafana 面板 JSON 到版本库配合指标命名规范一起做评审避免一段时间后指标垃圾遍地、命名混乱大家谁都不敢删。这种治理意识和监控系统本身一样重要毕竟爬虫项目往往是一波接着一波上线没有统一规范两三个月后这套平台就会变成新的“日志泥潭”。
延伸阅读

更多相关文章

2026/10/14 19:11:58

Python构建微信智能客服系统:多通道集成与NLP实战

1. 项目背景与核心价值微信生态作为国内最大的社交平台之一,其商业价值早已突破单纯的通讯工具范畴。根据最新统计,微信月活用户已突破13亿,公众号数量超过2000万,小程序日活达到4亿。在这个庞大的数字生态中,企业客服…

2026/10/14 20:37:20

Django部署性能调研:Gunicorn、Uvicorn与Docker实测对比

1. 为什么突然想搞一次 Django 部署性能调研先说个大实话:Django 项目本地跑得飞起,一上服务器就卡成 PPT,这种事儿我见过太多次了。而且更诡异的是,同样的代码,有的人用 Nginx Gunicorn 部署,有的人用 Uv…

2026/10/14 20:37:20

达美乐中国1405家门店标准化运营与数字化管理解析

1. 达美乐中国第1405家门店开业背后的商业逻辑三亚夏日百货一楼最近新添了一抹红蓝相间的亮色——达美乐比萨中国第1405家门店正式开业。这个数字背后,隐藏着国际餐饮品牌在中国市场的精耕细作。作为全球比萨外卖领域的标杆企业,达美乐自2004年进入中国以…

2026/10/14 20:37:20

TPU薄膜技术解析:应用场景与供应商选择指南

1. TPU薄膜行业现状与核心应用场景热塑性聚氨酯(TPU)薄膜作为一种高性能弹性体材料,近五年在国内市场的年均复合增长率达到12.3%。不同于普通塑料薄膜,TPU兼具橡胶的弹性和塑料的加工性能,其拉伸强度可达40-60MPa&…

2026/10/14 20:37:20

COMSOL模拟裂隙岩体注浆加固的非牛顿流体特性

1. 项目背景与核心价值裂隙岩体注浆加固是地下工程中控制围岩稳定性的关键技术手段。传统注浆设计多依赖经验公式和简化假设,难以准确预测浆液在复杂裂隙网络中的扩散行为。COMSOL Multiphysics作为一款多物理场耦合仿真平台,能够充分考虑浆液黏度随时间…

2026/10/14 20:32:19

ViewFaceCore:C#零抽象层调用SeetaFace6实现人脸检测与识别

简介:ViewFaceCore 是一款面向 .NET 开发者的轻量级 C# 人脸识别开源库,适用于人脸检测、特征提取与比对等基础视觉任务,特别适合初学者快速集成或中小型项目快速验证。资源包共 57 个文件,涵盖 35 个核心 C# 源码(含 …

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑