【实用程序】网页公开信息智能采集系统详细设计指南

概述

在互联网信息采集领域,长久以来存在两种主流范式:基于固定规则的爬虫 ,效率极高、成本极低,但面对页面结构变化时脆弱不堪;

基于大语言模型的智能提取 ,适应性强、理解力好,但延迟和成本较高。

本系统采用"规则引擎与LLM协同路由 "的混合架构,将两者优势互补------高频稳定站点走规则,长尾动态页面由LLM兜底。通过智能决策中枢,在性能、准确率与成本之间取得动态平衡,提供从URL输入到Excel导出的完整技术实现路径。


1. 系统架构概览:五层分离的数据流

系统采用分层解耦设计,自下而上(或从输入到输出)划分为五个核心层级。每一层通过定义良好的数据接口与相邻层通信,使得单层可独立替换、测试和扩展。下图展示了从用户提交URL到最终获得结构化Excel的完整数据流:
#mermaid-svg-SrhkutPIREJGqetz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SrhkutPIREJGqetz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SrhkutPIREJGqetz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SrhkutPIREJGqetz .error-icon{fill:#552222;}#mermaid-svg-SrhkutPIREJGqetz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SrhkutPIREJGqetz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SrhkutPIREJGqetz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SrhkutPIREJGqetz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SrhkutPIREJGqetz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SrhkutPIREJGqetz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SrhkutPIREJGqetz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SrhkutPIREJGqetz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SrhkutPIREJGqetz .marker.cross{stroke:#333333;}#mermaid-svg-SrhkutPIREJGqetz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SrhkutPIREJGqetz p{margin:0;}#mermaid-svg-SrhkutPIREJGqetz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SrhkutPIREJGqetz .cluster-label text{fill:#333;}#mermaid-svg-SrhkutPIREJGqetz .cluster-label span{color:#333;}#mermaid-svg-SrhkutPIREJGqetz .cluster-label span p{background-color:transparent;}#mermaid-svg-SrhkutPIREJGqetz .label text,#mermaid-svg-SrhkutPIREJGqetz span{fill:#333;color:#333;}#mermaid-svg-SrhkutPIREJGqetz .node rect,#mermaid-svg-SrhkutPIREJGqetz .node circle,#mermaid-svg-SrhkutPIREJGqetz .node ellipse,#mermaid-svg-SrhkutPIREJGqetz .node polygon,#mermaid-svg-SrhkutPIREJGqetz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SrhkutPIREJGqetz .rough-node .label text,#mermaid-svg-SrhkutPIREJGqetz .node .label text,#mermaid-svg-SrhkutPIREJGqetz .image-shape .label,#mermaid-svg-SrhkutPIREJGqetz .icon-shape .label{text-anchor:middle;}#mermaid-svg-SrhkutPIREJGqetz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SrhkutPIREJGqetz .rough-node .label,#mermaid-svg-SrhkutPIREJGqetz .node .label,#mermaid-svg-SrhkutPIREJGqetz .image-shape .label,#mermaid-svg-SrhkutPIREJGqetz .icon-shape .label{text-align:center;}#mermaid-svg-SrhkutPIREJGqetz .node.clickable{cursor:pointer;}#mermaid-svg-SrhkutPIREJGqetz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SrhkutPIREJGqetz .arrowheadPath{fill:#333333;}#mermaid-svg-SrhkutPIREJGqetz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SrhkutPIREJGqetz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SrhkutPIREJGqetz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SrhkutPIREJGqetz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SrhkutPIREJGqetz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SrhkutPIREJGqetz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SrhkutPIREJGqetz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SrhkutPIREJGqetz .cluster text{fill:#333;}#mermaid-svg-SrhkutPIREJGqetz .cluster span{color:#333;}#mermaid-svg-SrhkutPIREJGqetz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-SrhkutPIREJGqetz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SrhkutPIREJGqetz rect.text{fill:none;stroke-width:0;}#mermaid-svg-SrhkutPIREJGqetz .icon-shape,#mermaid-svg-SrhkutPIREJGqetz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SrhkutPIREJGqetz .icon-shape p,#mermaid-svg-SrhkutPIREJGqetz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SrhkutPIREJGqetz .icon-shape .label rect,#mermaid-svg-SrhkutPIREJGqetz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SrhkutPIREJGqetz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SrhkutPIREJGqetz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SrhkutPIREJGqetz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 校验与增强
路由与解析
采集与预处理
命中 site_id 且覆盖率≥90%
未命中 / 字段缺失
动态AJAX页面
校验通过
失败/空值
用户输入: URLs + 目标Schema
任务调度器
采集层
预处理层
路由决策层
规则命中?
规则引擎: lxml解析
LLM推理引擎
数据校验器
内容增强器
降级策略: 正则兜底
导出器: Pandas
Excel / CSV / JSON

各层职责速览

  • 采集层 :支持静态请求(Requests)与动态渲染(Playwright)双模式,内置UA池、代理轮换与反反爬策略。
  • 预处理层:对原始HTML进行DOM压缩、去噪、正文抽取,并转换为Markdown,控制输入LLM的Token长度。
  • 路由决策层:根据域名、规则覆盖率、页面动态性,智能选择最优解析路径------规则引擎(零AI成本)或LLM引擎。
  • 解析层:规则引擎处理高频固定站点(XPath/CSS),LLM引擎处理未知结构与动态页面。
  • 校验与增强层:字段级非空/格式校验,失败时触发正则兜底,并通过向量化自动分类,确保输出质量。

2. 核心数据模型(DDD)

系统使用 SQLite 作为轻量级任务状态库(单机部署),生产环境可无缝切换至 MySQL / PostgreSQL。所有表结构遵循领域驱动设计,通过 schema_hash 关联任务与提取目标,支持断点续传、失败重试与人工纠偏。

表名 核心字段 职责
tasks id, url, schema_hash, status, retry_count, created_at 存储任务生命周期(pending → running → success/failed),支持断点续传与失败重试
site_configs id, domain, rule_yaml, is_active, hit_count 存储固定站点的XPath/CSS规则,hit_count 用于评估规则热度与自动淘汰
extraction_results id, task_id, field_name, field_value, confidence, source_snippet 每个提取字段独立存储,包含置信度(0-1)及原文引用片段,便于溯源与人工审核
human_feedbacks id, result_id, corrected_value, operator 记录人工修正数据,构成模型微调数据集,持续提升准确率

设计要点 :采用"一任务多行"的字段级存储,支持单个字段独立降级重试;source_snippet 保存原文定位依据,方便人工复核定位。


3. 核心模块详细实现

3.1 采集与预处理模块:从原始HTML到干净文本

采集模块首先根据任务配置选择静态或动态方式获取页面源码,随后进入预处理流水线,其目标是将杂乱的HTML转化为结构清晰、Token可控的Markdown文本。


#mermaid-svg-cgEhmXMa9172w9bx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cgEhmXMa9172w9bx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cgEhmXMa9172w9bx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cgEhmXMa9172w9bx .error-icon{fill:#552222;}#mermaid-svg-cgEhmXMa9172w9bx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cgEhmXMa9172w9bx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cgEhmXMa9172w9bx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cgEhmXMa9172w9bx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cgEhmXMa9172w9bx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cgEhmXMa9172w9bx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cgEhmXMa9172w9bx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cgEhmXMa9172w9bx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cgEhmXMa9172w9bx .marker.cross{stroke:#333333;}#mermaid-svg-cgEhmXMa9172w9bx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cgEhmXMa9172w9bx p{margin:0;}#mermaid-svg-cgEhmXMa9172w9bx .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cgEhmXMa9172w9bx .cluster-label text{fill:#333;}#mermaid-svg-cgEhmXMa9172w9bx .cluster-label span{color:#333;}#mermaid-svg-cgEhmXMa9172w9bx .cluster-label span p{background-color:transparent;}#mermaid-svg-cgEhmXMa9172w9bx .label text,#mermaid-svg-cgEhmXMa9172w9bx span{fill:#333;color:#333;}#mermaid-svg-cgEhmXMa9172w9bx .node rect,#mermaid-svg-cgEhmXMa9172w9bx .node circle,#mermaid-svg-cgEhmXMa9172w9bx .node ellipse,#mermaid-svg-cgEhmXMa9172w9bx .node polygon,#mermaid-svg-cgEhmXMa9172w9bx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cgEhmXMa9172w9bx .rough-node .label text,#mermaid-svg-cgEhmXMa9172w9bx .node .label text,#mermaid-svg-cgEhmXMa9172w9bx .image-shape .label,#mermaid-svg-cgEhmXMa9172w9bx .icon-shape .label{text-anchor:middle;}#mermaid-svg-cgEhmXMa9172w9bx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cgEhmXMa9172w9bx .rough-node .label,#mermaid-svg-cgEhmXMa9172w9bx .node .label,#mermaid-svg-cgEhmXMa9172w9bx .image-shape .label,#mermaid-svg-cgEhmXMa9172w9bx .icon-shape .label{text-align:center;}#mermaid-svg-cgEhmXMa9172w9bx .node.clickable{cursor:pointer;}#mermaid-svg-cgEhmXMa9172w9bx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cgEhmXMa9172w9bx .arrowheadPath{fill:#333333;}#mermaid-svg-cgEhmXMa9172w9bx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cgEhmXMa9172w9bx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cgEhmXMa9172w9bx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cgEhmXMa9172w9bx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cgEhmXMa9172w9bx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cgEhmXMa9172w9bx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cgEhmXMa9172w9bx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cgEhmXMa9172w9bx .cluster text{fill:#333;}#mermaid-svg-cgEhmXMa9172w9bx .cluster span{color:#333;}#mermaid-svg-cgEhmXMa9172w9bx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cgEhmXMa9172w9bx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cgEhmXMa9172w9bx rect.text{fill:none;stroke-width:0;}#mermaid-svg-cgEhmXMa9172w9bx .icon-shape,#mermaid-svg-cgEhmXMa9172w9bx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cgEhmXMa9172w9bx .icon-shape p,#mermaid-svg-cgEhmXMa9172w9bx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cgEhmXMa9172w9bx .icon-shape .label rect,#mermaid-svg-cgEhmXMa9172w9bx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cgEhmXMa9172w9bx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cgEhmXMa9172w9bx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cgEhmXMa9172w9bx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始HTML
UA池轮换 & 动态渲染
DOM噪声移除

移除 script/style/nav/footer
文本密度算法提取正文
html2text 转 Markdown
Token控制 ≤ 4000

关键策略

  • UA池轮换:循环使用10个最新浏览器UA,每100次请求自动轮换,降低被识别为爬虫的风险。
  • 动态渲染检测 :通过配置 use_browser: true 启用Playwright,设置 wait_for_selector 确保SPA渲染完成。
  • DOM噪声移除 :使用 lxml 剔除 <script><style><nav><footer>display:none 节点。
  • 文本密度算法:基于文本密度识别正文区域,剔除行数少于2行的零散文本块,聚焦核心内容。
  • Markdown转换 :采用 html2text 保留标题层级与链接,平均Token消耗控制在4000以内,平衡成本与信息完整性。

3.2 智能路由器:决策中枢

路由器接收URL与目标Schema作为输入,综合三个维度(域名匹配、规则覆盖率、页面动态性)进行决策,将任务导向最优解析路径。


#mermaid-svg-W4LPysmLJ9X7tAqH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W4LPysmLJ9X7tAqH .error-icon{fill:#552222;}#mermaid-svg-W4LPysmLJ9X7tAqH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W4LPysmLJ9X7tAqH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W4LPysmLJ9X7tAqH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W4LPysmLJ9X7tAqH .marker.cross{stroke:#333333;}#mermaid-svg-W4LPysmLJ9X7tAqH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W4LPysmLJ9X7tAqH p{margin:0;}#mermaid-svg-W4LPysmLJ9X7tAqH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster-label text{fill:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster-label span{color:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster-label span p{background-color:transparent;}#mermaid-svg-W4LPysmLJ9X7tAqH .label text,#mermaid-svg-W4LPysmLJ9X7tAqH span{fill:#333;color:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH .node rect,#mermaid-svg-W4LPysmLJ9X7tAqH .node circle,#mermaid-svg-W4LPysmLJ9X7tAqH .node ellipse,#mermaid-svg-W4LPysmLJ9X7tAqH .node polygon,#mermaid-svg-W4LPysmLJ9X7tAqH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W4LPysmLJ9X7tAqH .rough-node .label text,#mermaid-svg-W4LPysmLJ9X7tAqH .node .label text,#mermaid-svg-W4LPysmLJ9X7tAqH .image-shape .label,#mermaid-svg-W4LPysmLJ9X7tAqH .icon-shape .label{text-anchor:middle;}#mermaid-svg-W4LPysmLJ9X7tAqH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W4LPysmLJ9X7tAqH .rough-node .label,#mermaid-svg-W4LPysmLJ9X7tAqH .node .label,#mermaid-svg-W4LPysmLJ9X7tAqH .image-shape .label,#mermaid-svg-W4LPysmLJ9X7tAqH .icon-shape .label{text-align:center;}#mermaid-svg-W4LPysmLJ9X7tAqH .node.clickable{cursor:pointer;}#mermaid-svg-W4LPysmLJ9X7tAqH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W4LPysmLJ9X7tAqH .arrowheadPath{fill:#333333;}#mermaid-svg-W4LPysmLJ9X7tAqH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W4LPysmLJ9X7tAqH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W4LPysmLJ9X7tAqH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W4LPysmLJ9X7tAqH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W4LPysmLJ9X7tAqH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W4LPysmLJ9X7tAqH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster text{fill:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH .cluster span{color:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W4LPysmLJ9X7tAqH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W4LPysmLJ9X7tAqH rect.text{fill:none;stroke-width:0;}#mermaid-svg-W4LPysmLJ9X7tAqH .icon-shape,#mermaid-svg-W4LPysmLJ9X7tAqH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W4LPysmLJ9X7tAqH .icon-shape p,#mermaid-svg-W4LPysmLJ9X7tAqH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W4LPysmLJ9X7tAqH .icon-shape .label rect,#mermaid-svg-W4LPysmLJ9X7tAqH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W4LPysmLJ9X7tAqH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W4LPysmLJ9X7tAqH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W4LPysmLJ9X7tAqH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
≥ 90%
< 90%



输入: URL + 目标字段列表
域名匹配固定站点?
计算规则覆盖率

所需字段是否都有规则?
路径A: 规则引擎

零AI成本, 毫秒级
页面是否动态AJAX?
路径B: 浏览器 + AI

Playwright + GPT-4o-mini
路径C: LLM直接推理

常规页面用GPT-4o-mini

决策逻辑伪代码

python 复制代码
def route_task(url: str, required_fields: list) -> tuple[str, dict]:
    site = SiteMatcher.match(url)
    if site and site.rule_completeness(required_fields) >= 0.9:
        return "RULE", site.rule
    elif is_dynamic_ajax_page(url):
        return "BROWSER_AI", {"model": "gpt-4o-mini"}
    else:
        return "LLM", {"model": "gpt-4o-mini"}

成本优化效果:60%--70%的高频站点请求走规则引擎(零AI成本),仅30%--40%的长尾与动态页面需要LLM,大幅降低整体运营成本。


3.3 LLM推理引擎:结构化提取的艺术

对于规则无法覆盖的页面,LLM引擎通过精心设计的提示词、强制JSON输出和证据引用机制,确保提取结果结构化、可追溯、可校验。

模型选型策略

模型 输入价格 输出价格 适用场景
GPT-4o-mini(默认) $0.15 / 1M tokens $0.60 / 1M tokens 通用提取,成本与质量最佳平衡
Claude-4(可选) $0.25 / 1M tokens $1.25 / 1M tokens 中文长文本理解更优
Qwen3-9B-Instruct(本地) 仅推理算力 --- 数据敏感场景,完全离线部署

System Prompt 模板(核心原则)

复制代码
你是数据提取专家。根据给定网页内容提取字段。
规则:
1. 相对时间(如"昨天")需基于当前时间戳 {current_unix} 转换为绝对时间。
2. 必须返回合法JSON,格式为 {"field": "value", "evidence": "原文片段"}。
3. 若找不到证据,value 设为 null,禁止编造。
4. 价格字段去除货币符号,返回纯数字。
5. 列表字段使用 "|" 分隔符拼接。

Function Calling Schema 示例

json 复制代码
{
  "name": "extract_product",
  "parameters": {
    "type": "object",
    "properties": {
      "title":     { "type": "string" },
      "price":     { "type": "number" },
      "publish_date": { "type": "string", "format": "date-time" },
      "summary":   { "type": "string" }
    },
    "required": ["title"]
  }
}

关键约束 :强制开启 response_format: { type: "json_object" },确保输出始终可解析;同时通过 evidence 字段引用原文,实现结果溯源。


3.4 向量化增强模块:自动分类

提取完成后,通过向量化将产品自动归入预置类目体系,减少人工分类工作量。

  • Embedding模型 :本地部署 BAAI/bge-m3 或调用 text-embedding-3-small API,支持中英文混合。
  • 分类映射 :将 title+summary 向量化后与 category_lib(20个类目描述向量)计算余弦相似度。
  • 阈值判定:相似度 > 0.75 自动归类,否则标记为"待人工分类"并高亮提示。

3.5 降级与容灾机制:优雅应对异常

系统在多个层级设计了优雅降级策略,确保外部服务波动时仍能交付可用结果:

  • HTTP层 :指数退避重试(2^retry × 1s + random),最多3次,应对429/503。
  • 字段层 :若LLM返回的price为null,自动触发正则库 re.findall(r'¥([\d.]+)', html) 兜底。
  • 存储层 :数据库写入失败时,结果缓存至 fallback.json,下次启动自动同步恢复。
  • 分类层 :向量库无匹配时默认赋值 uncategorized,前端高亮提示人工介入。

4. API接口定义

基于FastAPI封装核心能力,提供同步预览与异步批量两种调用模式,便于前端Gradio界面集成。

端点 方法 请求体 响应 说明
/api/v1/extract/single POST {"url": "str", "schema": {"fields": [...]}} {"code":0, "data": {...}, "cost_time": 1.2} 单条实时预览,同步返回
/api/v1/task/batch POST {"file": "csv", "site_config": "yaml"} {"task_id": "uuid"} 异步批量提交,立即返回任务ID
/api/v1/task/status GET ?task_id=xxx {"progress":"60%", "download_url": "..."} 轮询任务进度与下载链接
/api/v1/feedback POST {"result_id":1, "correct_value":"Apple"} {"status":"saved"} 人工纠偏录入,用于模型优化

5. 站点配置规则:零AI成本的秘密

对于高频采集的固定结构站点,运维人员可按以下YAML规范配置提取规则。配置完成后,系统将完全跳过LLM调用,通过规则引擎直接解析,实现零AI成本、毫秒级响应

yaml 复制代码
site_id: "jd_product"
match: "item.jd.com/*"

fields:
  title:
    selector: "css:.sku-name"
    type: "text"
  price:
    selector: "xpath://span[@class='price J_price']"
    type: "attr"
    attr_name: "data-price"
  images:
    selector: "css:ul#preview img"
    type: "list"
    join_char: "|"

pagination:
  type: "click_next"
  selector: "css:a.next-page"

headers:
  User-Agent: "Mozilla/5.0 ..."

配置说明

  • selector 支持CSS与XPath,通过 css: / xpath: 前缀区分。
  • type 支持 text(文本内容)、attr(元素属性)、list(多条结果拼接)。
  • 分页支持 click_next(点击翻页)与 url_pattern(URL模板翻页)。

6. 非功能性指标(SLA)

指标 目标值 监控方式
单页AI提取延迟 P95 < 8s(含网络) Prometheus + Grafana
规则提取QPS > 50 req/s 日志分析
字段提取准确率 规则引擎 > 98%,LLM > 92% 人工抽检 + 自动化对比测试
系统可用性 99.5%(剔除外部API故障) 健康检查端点
成本控制 单页AI成本 < $0.005 每日Token用量告警(阈值 $10/天)

7. 项目结构与核心类图

目录结构

复制代码
web2sheet_ai/
├── src/
│   ├── core/
│   │   ├── fetcher.py      # class Fetcher: fetch_static(), fetch_dynamic()
│   │   ├── compressor.py   # class DOMCompressor: compress_to_md()
│   │   ├── router.py       # class SmartRouter: decide()
│   │   └── extractor.py    # BaseExtractor(ABC), RuleExtractor, LLMExtractor
│   ├── ai/
│   │   ├── prompt_manager.py
│   │   ├── llm_client.py   # 统一封装 OpenAI / Anthropic / Ollama
│   │   └── embedder.py     # class Embedder: vectorize(), similarity_search()
│   ├── storage/
│   │   ├── models.py       # SQLAlchemy ORM
│   │   └── dao.py          # CRUD操作
│   └── api/
│       ├── routes.py       # FastAPI路由
│       └── schemas.py      # Pydantic模型
├── config/
│   ├── settings.yaml
│   └── sites/              # 各站点 .yaml 规则
├── scripts/
│   └── fine_tune.py        # 拉取feedback微调Qwen
└── requirements.txt

核心类依赖关系

#mermaid-svg-4VqaCMwlbtvv1A9R{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4VqaCMwlbtvv1A9R .error-icon{fill:#552222;}#mermaid-svg-4VqaCMwlbtvv1A9R .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4VqaCMwlbtvv1A9R .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4VqaCMwlbtvv1A9R .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4VqaCMwlbtvv1A9R .marker.cross{stroke:#333333;}#mermaid-svg-4VqaCMwlbtvv1A9R svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4VqaCMwlbtvv1A9R p{margin:0;}#mermaid-svg-4VqaCMwlbtvv1A9R g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-4VqaCMwlbtvv1A9R g.classGroup text .title{font-weight:bolder;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster-label text{fill:#333;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster-label span{color:#333;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster-label span p{background-color:transparent;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster text{fill:#333;}#mermaid-svg-4VqaCMwlbtvv1A9R .cluster span{color:#333;}#mermaid-svg-4VqaCMwlbtvv1A9R .nodeLabel,#mermaid-svg-4VqaCMwlbtvv1A9R .edgeLabel{color:#131300;}#mermaid-svg-4VqaCMwlbtvv1A9R .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-4VqaCMwlbtvv1A9R .label text{fill:#131300;}#mermaid-svg-4VqaCMwlbtvv1A9R .labelBkg{background:#ECECFF;}#mermaid-svg-4VqaCMwlbtvv1A9R .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-4VqaCMwlbtvv1A9R .classTitle{font-weight:bolder;}#mermaid-svg-4VqaCMwlbtvv1A9R .node rect,#mermaid-svg-4VqaCMwlbtvv1A9R .node circle,#mermaid-svg-4VqaCMwlbtvv1A9R .node ellipse,#mermaid-svg-4VqaCMwlbtvv1A9R .node polygon,#mermaid-svg-4VqaCMwlbtvv1A9R .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4VqaCMwlbtvv1A9R .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R g.clickable{cursor:pointer;}#mermaid-svg-4VqaCMwlbtvv1A9R g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-4VqaCMwlbtvv1A9R g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-4VqaCMwlbtvv1A9R .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-4VqaCMwlbtvv1A9R .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-4VqaCMwlbtvv1A9R .dashed-line{stroke-dasharray:3;}#mermaid-svg-4VqaCMwlbtvv1A9R .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-4VqaCMwlbtvv1A9R #compositionStart,#mermaid-svg-4VqaCMwlbtvv1A9R .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #compositionEnd,#mermaid-svg-4VqaCMwlbtvv1A9R .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #dependencyStart,#mermaid-svg-4VqaCMwlbtvv1A9R .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #dependencyStart,#mermaid-svg-4VqaCMwlbtvv1A9R .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #extensionStart,#mermaid-svg-4VqaCMwlbtvv1A9R .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #extensionEnd,#mermaid-svg-4VqaCMwlbtvv1A9R .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #aggregationStart,#mermaid-svg-4VqaCMwlbtvv1A9R .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #aggregationEnd,#mermaid-svg-4VqaCMwlbtvv1A9R .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #lollipopStart,#mermaid-svg-4VqaCMwlbtvv1A9R .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R #lollipopEnd,#mermaid-svg-4VqaCMwlbtvv1A9R .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-4VqaCMwlbtvv1A9R .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-4VqaCMwlbtvv1A9R .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4VqaCMwlbtvv1A9R .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4VqaCMwlbtvv1A9R .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4VqaCMwlbtvv1A9R :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Fetcher
+fetch_static(url)
+fetch_dynamic(url)
DOMCompressor
+compress_to_md(html)
SmartRouter
+decide(url, fields)
<<abstract>>
BaseExtractor
+extract(html, schema)
RuleExtractor
+extract(html, schema)
LLMExtractor
+extract(html, schema)
Embedder
+vectorize(text)
+similarity_search(vec)


8. 部署与环境要求

资源配置

  • 最低配置:4C 8G内存(用于Playwright浏览器实例及本地Embedding模型推理,适用于纯API调用模式,不部署本地LLM)。
  • 推荐配置:8C 32G + NVIDIA T4 GPU(支持本地部署Qwen-7B完全离线运行,GPU显存 ≥ 16GB)。

容器化架构

提供 Dockerfiledocker-compose.yml,依赖服务包括:

  • Redis:任务队列缓存、分布式锁、URL去重集合
  • MinIO:存储导出文件(Excel/CSV/JSON),支持S3兼容下载
  • Prometheus + Grafana:指标采集与可视化监控面板

关键环境变量

bash 复制代码
OPENAI_API_KEY=sk-xxx
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
LOG_LEVEL=INFO
MAX_CONCURRENT_REQUESTS=5
EMBEDDING_MODEL=BAAI/bge-m3
DEFAULT_LLM_MODEL=gpt-4o-mini

9. 异常处理清单

异常场景 系统行为 用户感知
403 目标URL拒绝访问 自动切换Playwright浏览器模式重试;失败后标记 status=blocked 任务状态显示"已封禁",不影响其他任务
LLM API超时(> 15s) 截断输入文本至2000字符重试;3次失败则保存 raw_html,触发人工复核 任务状态显示"待复核",附带原始HTML
数据库写入失败 本地缓存至 fallback.json,下次启动自动同步 对用户透明,数据不丢失
向量库无匹配分类 默认赋值 uncategorized 前端高亮提示"待人工分类"
HTTP 429 / 503 限流 指数退避重试(2^retry × 1s + random),最多3次 任务自动排队等待,无需手动干预
LLM返回字段为 null 自动触发正则库兜底提取(如价格正则) 字段标注"正则兜底",置信度降低

相关推荐
凌杰8 小时前
AI 学习笔记: 深度学习的训练与评估
人工智能
MartinYeung58 小时前
[论文学习]AgentDAM:自主Web Agent的隐私泄露评估
学习
jijihusong0068 小时前
KMP全栈开发教程:从Android到AI Agent
android·人工智能
薛定e的猫咪8 小时前
【模型推理】深度学习模型部署核心:算子Lower拆解、计算图优化与PNNX源码解析
人工智能·深度学习
武子康8 小时前
2026 开源视频生成模型全景图:按 9 类任务路由比按参数量选模型更可靠
人工智能·ai·chatgpt·openai·claude·世界模型·视频模型
2601_956414148 小时前
AI3D工作流应该包括哪些环节?用V2Fun从生成推进到可动资产
人工智能·3d
Urbano8 小时前
稳产提质、快返增效:慧拿智能模板机重塑服装厂精细化盈利模式
大数据·人工智能
光影6278 小时前
MySQL 进阶篇 —— 事务 / 外键 / 索引 / 高级查询
数据库·笔记·sql·学习·mysql
新知图书9 小时前
测试与发布(新闻早报智能体开发)
人工智能·agent·ai agent·智能体·扣子