ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI 爬虫为什么总把价格抓错?Python 证据校验与网页逆向排查实战

AI 爬虫为什么总把价格抓错?Python 证据校验与网页逆向排查实战 爬虫拿到 HTTP 200AI 也返回了合法 JSON价格却可能是原价、分期价甚至来自推荐商品。要解决这个问题先建立价格的业务定义与证据链再决定哪里需要大模型。本文用离线合成样本演示校验层不请求真实电商站点不调用模型候选 JSON 用来模拟 AI 输出。完整示例已在本地 Python 3.9.6 运行1 个正确候选通过9 个错误候选被拒绝。这个结果验证的是校验逻辑不代表某个模型的提取准确率。一、先定义你要抓的价格“价格”至少要绑定商品、SKU、地区、币种、数量和采集时间。本文只取当前 SKU 的单件公开售价不含运费不把会员价、券后价、分期金额当作公开售价。业务若需要券后价应单独存优惠条件而不是覆盖基础价格。例如页面同时出现 199.00、129.90、43.30它们可能分别是原价、现价、三期付款的每期金额。取最小值会得到一条看起来很优惠的错误数据。字段缺失应进入 unknown 状态不能自动填 0确实免费的商品则需要零价证据。二、网页逆向先追价格来源这里的逆向分析目标是还原“数据如何变成页面价格”。先固定同一个 SKU、地区和登录状态再比较三个位置原始 HTML检查商品区域、结构化数据和页面内嵌状态判断服务端是否已经返回价格。渲染后 DOM切换规格观察哪块价格随 SKU 更新留意推荐列表和隐藏节点。网络响应在 DevTools 的 Network 面板观察规格切换产生的请求核对请求参数与响应字段。Chrome 官方文档介绍了请求过滤和响应检查方法Inspect network activity。找到数值之后还要确认字段含义一个叫 price 的字段可能是区间起价JSON-LD 的 Offer 可以提供 price、priceCurrency但仍要核对它属于哪个商品和规格。字段定义见 Schema.org Offer。结构化数据与当前界面冲突时先保留两份证据并标记冲突不凭格式更“正规”就选它。解析器应输出证据记录snapshot_id、source_url、captured_at、sku、selector_or_json_path、原始文本及归一化值。模型只引用采集层分配的 evidence_id不要让模型自己编造来源后再用那份来源证明自己。三、AI 放在候选提取层固定接口或稳定结构可以直接解析。页面模板很多、标签语义不统一时再让 AI 从限定的商品区域识别候选。输入保留“原价”“每期”“会员专享”等邻近文本过度清洗反而丢失价格角色。建议输出结构包含 sku、amount、currency、evidence_id没有足够证据时允许返回空候选。confidence 可以帮助排序但不作为入库许可。页面正文中要求修改任务或输出固定价格的语句仍属于待分析内容不能变成采集指令。数据流可以写成页面快照 → 站点适配器/AI 候选 → 证据与业务校验 → 入库或待复查。四、可运行的 Python 校验示例下面的 EVIDENCE 是人工构造的离线样本模拟已经核对语义的站点适配器结果。它并没有实现 HTML 下载、自动识别价格角色或真实模型调用。把同一模型给出的 kind 原样当成可信证据会重新引入自证问题。金额限制为最多九位整数、最多两位小数的非负人民币金额传字符串给 Decimal不默默舍入多余小数也不自行猜测逗号属于千分位还是小数点。Python 官方 Decimal 文档解释了十进制精确表示及从浮点数构造的差异。importrefromdecimalimportDecimal EVIDENCE{e1:{sku:A,kind:current,currency:CNY,amount:129.90},e2:{sku:A,kind:original,currency:CNY,amount:199.00},e3:{sku:A,kind:installment,currency:CNY,amount:43.30},e4:{sku:B,kind:current,currency:CNY,amount:89.00},}defmoney(value):ifnotisinstance(value,str)ornotre.fullmatch(r[0-9]{1,9}(\.[0-9]{1,2})?,value):raiseValueError(invalid_amount)returnDecimal(value).quantize(Decimal(0.01))defvalidate(candidate,target_sku):ifnotisinstance(candidate,dict):raiseValueError(invalid_candidate)evidence_idcandidate.get(evidence_id)ifnotisinstance(evidence_id,str)orevidence_idnotinEVIDENCE:raiseValueError(missing_evidence)evidenceEVIDENCE[evidence_id]ifcandidate.get(sku)!target_skuorevidence[sku]!target_sku:raiseValueError(wrong_sku)ifevidence[kind]!current:raiseValueError(wrong_price_kind)ifcandidate.get(currency)!CNYorevidence[currency]!CNY:raiseValueError(currency_mismatch)amountmoney(candidate.get(amount))ifamount!money(evidence[amount]):raiseValueError(evidence_mismatch)return{sku:target_sku,amount:str(amount),currency:CNY,evidence_id:evidence_id}if__name____main__:good{sku:A,amount:129.90,currency:CNY,evidence_id:e1}assertvalidate(good,A)[amount]129.90cases[({evidence_id:e2,amount:199.00},wrong_price_kind),({evidence_id:e3,amount:43.30},wrong_price_kind),({evidence_id:e4,amount:89.00},wrong_sku),({amount:None},invalid_amount),({amount:0},evidence_mismatch),({amount:NaN},invalid_amount),({amount:129.900},invalid_amount),({currency:USD},currency_mismatch),({evidence_id:invented},missing_evidence),]forpatch,expectedincases:try:validate(dict(good,**patch),A)exceptValueErrorasexc:assertstr(exc)expected,(patch,str(exc))else:raiseAssertionError(patch)print(PASS: 1 valid candidate; 9 invalid candidates rejected)五、从离线示例接到真实爬虫保存为 price_demo.py 后运行 python3 price_demo.py输出为PASS: 1 valid candidate; 9 invalid candidates rejected这套校验能够发现候选与已知证据不一致却不能证明证据本身一定正确。接入真实站点前需要补上四件事证据作用域evidence_id 绑定本次快照避免拿昨天或其他页面的合法编号通过校验。时间与上下文保留地区、SKU、数量、会话条件和采集时间按业务设过期策略。来源冲突接口、JSON-LD 与页面不一致时输出 conflict交给更具体的规则或复查。多币种解析按站点地区规则处理小数位与分隔符不能把人民币的两位小数限制套到所有货币。价格角色的识别如果主要依赖 AI还应单独建立人工标注集评估这一层。保留原始快照可以在改规则、换模型后离线回放不必为了比较版本反复请求站点。六、看什么指标才知道系统在变好把抓取成功率、证据完整率、候选通过率和人工抽查正确率分开记录。通过率上升可能是规则变松不能直接解释为质量提升。失败原因按 missing_evidence、wrong_sku、wrong_price_kind 等统计再决定修请求链路、站点适配器还是模型输入。建议回归样本覆盖普通售价、促销、分期、会员条件、缺货、规格切换及地区切换。先用固定样本比较旧版和新版记录每个错误案例的去向之后再扩大覆盖范围。AI 可以减少模板适配的人工工作但价格数据能否交付最后仍取决于业务定义、可追溯证据和校验边界。先让每一条价格说清“哪件商品、什么条件、从哪里来”再优化调用成本。本文由 AI 辅助编写离线 Python 校验示例已运行验证不包含真实站点或模型性能测评。
返回列表