概述
在互联网信息采集领域,长久以来存在两种主流范式:基于固定规则的爬虫 ,效率极高、成本极低,但面对页面结构变化时脆弱不堪;
基于大语言模型的智能提取 ,适应性强、理解力好,但延迟和成本较高。
本系统采用"规则引擎与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-smallAPI,支持中英文混合。 - 分类映射 :将
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)。
容器化架构
提供 Dockerfile 与 docker-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 |
自动触发正则库兜底提取(如价格正则) | 字段标注"正则兜底",置信度降低 |