尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python后端爬虫专题24:一次日志把Cookie打出来之后——结构化日志、指标与任务追踪

Python后端爬虫专题24:一次日志把Cookie打出来之后——结构化日志、指标与任务追踪 Python后端爬虫专题24一次日志把Cookie打出来之后——结构化日志、指标与任务追踪上一篇练习完整答案四条样本可设为杭州 Python/FastAPI 10k—20k×13上海 python/PostgreSQL 20k—30k×12杭州 Scrapy 无薪资北京 Celery 15k—15k×12。手算城市杭州2、北京1、上海1Python 合并后2其余1有效年薪 195000、300000、180000平均 225000样本数3。测试应以这些手算常量断言不复用统计函数生成期望。“平均薪资 247500”至少要同时说明币种/单位、税前或税后、月薪区间中点算法、发薪月数、有效样本数与总职位数、时间范围、城市/经验等筛选、缺失薪资处理最好展示分布而不只平均。JSONL 验证应逐行json.loads断言每行schema_version jobradar.job.v1、source_url 为允许来源、行数等于该租户职位数tests/test_exporter.py已完整覆盖原子覆盖和跨租户导出。事故复盘错误日志比错误本身更危险某内部来源登录失败开发者为排障打印了整个请求 headers于是 Authorization 和 Cookie 进入集中日志。采集任务没有拿到数据却把可复用会话暴露给更多日志读者。正确做法不是“以后小心一点”而是设计日志合同只允许已知业务字段敏感键递归打码长正文截断异常只记录类型与安全消息。JobRadar 的JsonEventLogger.event(event, **fields)生成一行 JSON含 UTC timestamp 和 event在输出前调用sanitize_fields。字段名只要包含 password、secret、token、authorization 或 cookie就替换为[REDACTED]嵌套 dict/list 同样处理。未知对象转字符串长字符串限制 200 字符。为什么不扫描字符串内容猜秘密准确识别 token 格式不可能完整还容易误伤职位正文日志调用者首先就不应传秘密按字段名打码是纵深防御。生产可再使用日志平台的脱敏规则与短保留期。cd project.\.venv\Scripts\python.exe-m pytest tests\test_observability.py::test_json_logger_emits_one_machine_readable_line_without_secrets-q测试捕获真实输出行并json.loads断言 event 等字段可查询、密码不出现。它不是 grep 源码中有没有“REDACTED”而是运行组件检查可观察副作用。一条任务需要哪些关联字段最少包含业务 task_id、tenant_id、event、stage、status、error_type、source host不要把完整带查询参数 URL 默认写日志因为 query 可能有 token。Celery queue_task_id 用于基础设施排查但外部 API 追踪以稳定业务 task_id 为主。Worker 开始输出crawl_started失败输出crawl_failed含错误类型与 metrics结束输出crawl_finished含 completed/partial、CrawlReport 与 metrics。这样可按 task_id 拼出时间线。更完整系统可加入 trace_id将 API 入队、broker 消息和 Worker span 连接起来但不能把分布式追踪当业务状态数据库。日志、指标、任务记录回答不同问题日志回答“某次发生了什么细节”指标回答“整体速率、错误率、延迟是否异常”crawl_tasks 回答“用户提交的这一项现在是什么状态”。用日志算长期成功率成本高且口径不稳用 Prometheus 指标查询单个任务细节又缺少上下文。CrawlMetrics.record(stage, seconds, success...)为每阶段累计 attempts、successes、failures 与总耗时snapshot()计算平均值。课程是进程内实现Worker 在事件中输出快照生产可用 Counter/Histogram 导出但标签不能放 task_id、URL 等高基数字段否则时序数量爆炸。平均延迟不够平均值会掩盖长尾生产应记录 Histogram 并关注 P50/P95/P99。课程实现平均值是为了先理解分母没有 attempt 的 stage 不输出失败也必须进入耗时和 attempts。如果只记录成功请求故障时延迟图反而可能变好。建议核心指标crawl task completed/partial/failed 计数各阶段延迟HTTP status 分类retry 与 429discovered、created、updated、not_modified解析失败当前队列积压Worker 活跃数。告警要用比例和持续窗口例如 10 分钟解析失败率 20%不是一条坏页面就午夜叫醒。错误消息怎样既有用又安全记录error_typeAuthenticationFailed比记录带密码的表单有用记录 host 和响应状态不记录 Cookie解析错误记录字段名、快照 ID 和净化 URL不把整个 HTML 放日志。完整证据已在受控快照存储日志只给检索线索。sanitize_fields会截长文本却不是存储配额攻击者仍可制造很多日志事件。API 输入和异常路径要限速日志系统要做吞吐限制。换言之可观测性本身也有安全与容量边界。本篇完整可观测模块先看敏感字段递归处理再看阶段累积最后看单行 JSON 写出。输出目标通过 Callable 注入所以测试可捕获生产可接标准 logging handler。不会泄露凭据的结构化事件和分阶段采集指标。fromcollectionsimportdefaultdictfromcollections.abcimportCallable,Mappingfromdataclassesimportdataclassfromdatetimeimportdatetime,timezoneimportjsonfromtypingimportAny _SENSITIVE_PARTS(password,secret,token,authorization,cookie)defsanitize_fields(value:object,*,max_string:int200)-object:递归清理日志字段字段名决定是否隐藏不扫描正文猜秘密。ifisinstance(value,Mapping):cleaned:dict[str,object]{}forraw_key,childinvalue.items():keystr(raw_key)ifany(partinkey.casefold()forpartin_SENSITIVE_PARTS):cleaned[key][REDACTED]else:cleaned[key]sanitize_fields(child,max_stringmax_string)returncleanedifisinstance(value,(list,tuple)):return[sanitize_fields(child,max_stringmax_string)forchildinvalue]ifisinstance(value,str)andlen(value)max_string:returnvalue[:max_string]…ifisinstance(value,(str,int,float,bool))orvalueisNone:returnvaluereturnstr(value)dataclassclass_Stage:attempts:int0successes:int0failures:int0total_seconds:float0.0classCrawlMetrics:进程内教学实现生产可将同样字段导出到 Prometheus。def__init__(self)-None:self._stages:defaultdict[str,_Stage]defaultdict(_Stage)defrecord(self,stage:str,seconds:float,*,success:bool)-None:ifseconds0:raiseValueError(stage duration must not be negative)rowself._stages[stage]row.attempts1row.successesint(success)row.failuresint(notsuccess)row.total_secondssecondsdefsnapshot(self)-dict[str,dict[str,int|float]]:return{stage:{attempts:row.attempts,successes:row.successes,failures:row.failures,average_seconds:round(row.total_seconds/row.attempts,6),}forstage,rowinsorted(self._stages.items())}classJsonEventLogger:向调用者提供的行写入器输出一条严格 JSON 事件。def__init__(self,write_line:Callable[[str],object]print)-None:self._write_linewrite_linedefevent(self,event:str,**fields:object)-None:payload:dict[str,object]{timestamp:datetime.now(timezone.utc).isoformat(),event:event,**fields,}self._write_line(json.dumps(sanitize_fields(payload),ensure_asciiFalse,separators(,,:)))本篇课后练习构造含嵌套 password、Authorization、Cookie 和 500 字正文的事件给出完整代码并断言秘密消失、正文被截断。为 fetch 阶段记录一次成功0.1秒、一次失败0.3秒手算并验证 snapshot 的 attempts、failures 与 average_seconds。为“十分钟内解析失败率超过20%”写出分子、分母、排除条件和告警后先查哪些日志字段。下一篇会从攻击者可控 seed_url 出发系统复盘 SSRF 与下载边界。
返回列表