采集监控、日志与告警

采集系统最危险的状态不是「报错」,而是「静默失败」:进程还在跑、日志还在刷,但数据一条没进来——选择器被前端改版改坏了,成功率掉到 0 而没人发现。本章讲清该监控什么、结构化日志怎么写、失败样本怎么留、告警怎么降噪。

采集需要监控什么

指标含义异常信号
请求成功率2xx 响应占全部请求的比例低于 95% 说明被限流或站点异常
抓取速率每分钟成功请求数骤降或暴涨都要看
队列积压待爬 URL 数量(Redis llen持续增长说明消费能力不足
解析失败率解析出字段为空的比例突然升高几乎总是选择器失效
入库条数每批新增与更新的行数与成功率背离即是静默失败
异常类型分布超时、连接失败、403/429、解析异常定位问题类型的关键维度
单次任务耗时一批任务从开始到结束的时间逐渐变长预示站点变慢或数据量增长
重复率去重命中比例异常升高说明断点或 URL 规则有问题

监控不是「指标越多越好」,而是每个指标都要能指向一个具体动作:成功率低看限流与代理,解析失败率高看选择器,入库条数掉看管道。

结构化日志

文本日志方便人看,JSON 日志方便机器聚合。自定义一个 Formatter,让每条日志带上站点、阶段、trace 字段:

# log_setup.py —— Python 3.10+
import json, logging, sys
from logging.handlers import RotatingFileHandler

EXTRA_FIELDS = ("site", "trace_id", "url", "stage", "count", "duration_ms")

class JsonFormatter(logging.Formatter):
    """把日志序列化为单行 JSON,便于 ELK/Loki 直接解析"""
    def format(self, record: logging.LogRecord) -> str:
        payload = {"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
                   "level": record.levelname, "logger": record.name,
                   "msg": record.getMessage()}
        for field in EXTRA_FIELDS:
            value = getattr(record, field, None)
            if value is not None:
                payload[field] = value
        if record.exc_info:
            payload["exc"] = self.formatException(record.exc_info)
        return json.dumps(payload, ensure_ascii=False)

def setup_logger(name: str = "crawler", path: str = "logs/crawler.log") -> logging.Logger:
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    if logger.handlers:          # 避免重复 addHandler 导致日志翻倍
        return logger
    file_handler = RotatingFileHandler(path, maxBytes=50 * 1024 * 1024,
                                       backupCount=7, encoding="utf-8")
    file_handler.setFormatter(JsonFormatter())
    stream_handler = logging.StreamHandler(sys.stdout)
    stream_handler.setFormatter(JsonFormatter())
    logger.addHandler(file_handler)
    logger.addHandler(stream_handler)
    return logger

if __name__ == "__main__":
    log = setup_logger()
    log.info("解析完成", extra={"site": "example.com", "stage": "parse", "count": 120})
    try:
        1 / 0
    except ZeroDivisionError:
        log.exception("解析异常", extra={"site": "example.com", "stage": "parse"})

失败样本比统计值更有价值:把解析失败的 URL 与响应片段落盘(save_failed(url, reason, html[:5000], site) 写成 logs/failed/<site>-<hash>.json),第二天可以直接对着 HTML 改选择器,不必重现整批任务。

告警渠道与降噪

渠道适用注意
邮件日报、非紧急通知实时性差,容易被忽略
钉钉/企业微信群机器人团队实时告警用 Webhook,注意限频与 @ 人规则
Server 酱等推送个人项目有免费额度限制
Prometheus Alertmanager与指标阈值联动需部署,适合长期运行的系统
电话/短信核心业务中断成本高,只留给最严重的级别
降噪策略做法效果
连续失败才告警连续 3 次任务失败才发通知过滤单次抖动
聚合告警同站点同类型 5 分钟内只发一条防止消息轰炸
静默期相邻告警间隔至少 30 分钟给处理留出时间窗口
分级P0 电话、P1 群消息、P2 日报汇总注意力留给真正紧急的事
阈值带滞回成功率 < 90% 告警,> 95% 才恢复避免阈值附近反复告警

用 Prometheus + Grafana 做看板

Grafana 负责可视化,Prometheus 按间隔抓取指标,采集程序只需用 prometheus_client 暴露一个 HTTP 端口:

# metrics.py —— 依赖 prometheus_client
from prometheus_client import Counter, Gauge, Histogram, start_http_server

# Counter 只增不减,适合累计量;Gauge 可增可减,适合当前状态
REQUESTS = Counter("crawler_requests_total", "请求总数", ["site", "status"])
ITEMS = Counter("crawler_items_total", "入库条数", ["site"])
QUEUE_SIZE = Gauge("crawler_queue_size", "待爬队列长度", ["site"])
SUCCESS_RATE = Gauge("crawler_success_rate", "最近一批成功率", ["site"])
LATENCY = Histogram("crawler_request_seconds", "请求耗时分布", ["site"],
                    buckets=(0.1, 0.3, 1, 2, 5, 10))
start_http_server(9101)     # Prometheus 从 /metrics 抓取
# prometheus.yml 片段
scrape_configs:
  - job_name: crawler
    scrape_interval: 30s
    static_configs:
      - targets: ["10.0.0.21:9101", "10.0.0.22:9101"]

Grafana 里建议做四块图:成功率与队列长度(同图,两条曲线分离即异常)、解析失败率、请求耗时 P95、入库条数按小时柱状图。入库条数必须与成功率同屏,因为「成功率正常但入库为 0」正是静默失败的典型形态。

静默失败怎么检测

检测项规则示例说明
条数突降今日条数 < 近 7 日均值 × 30%最强的静默失败信号
成功率为 0 但进程存活连续 2 个周期无成功请求通常是站点封禁或出口故障
字段全空某字段空值率 > 90%选择器失效
队列不动llen 连续 3 次采样完全不变调度器卡死或 worker 全退出
时间戳陈旧最新数据的采集时间超过 2 个周期任务未真正执行
重复率异常去重命中率 > 95%URL 规则或断点逻辑出错

常见坑

现象原因处理
日志出现两份重复 addHandlerif logger.handlers: return
日志文件无限增长未用 RotatingFileHandler设置 maxBytesbackupCount
JSON 日志解析失败日志里有换行或未转义内容只输出单行,异常用 formatException
告警风暴每个失败都发一条连续 N 次失败才告警 + 聚合窗口
指标口径不一致手动统计与日志口径不同指标与日志的统计点放在同一处代码

合规提示:监控与日志本身也可能越界——记录 URL 与响应片段时,不要落盘个人隐私字段(手机号、身份证号、简历个人信息等)。只在合法授权范围内采集公开数据,遵守 robots.txt 与站点服务条款,控制频率与并发,不绕过登录、验证码与付费墙。日志与数据的留存、导出需符合《个人信息保护法》《数据安全法》《著作权法》,优先使用官方 API 与开放数据。

小结:采集监控要盯住成功率、队列积压、解析失败率与入库条数四个核心指标,其中「成功率正常而入库为 0」是静默失败的标准形态;日志用 JSON 结构化并配 RotatingFileHandler 切割,失败样本落盘便于次日复现;告警必须做连续失败判定、聚合与静默期,用 Prometheus 暴露指标、Grafana 做同屏看板。

笔记加载中…