- 新增 AIExecutor 类用于调用 OpenAI API 并解析结构化指令 - 在 Settings 中增加 OpenAI 相关配置项(API Key、模型、超时等) - 扩展 ActionContext 以支持注入 AI 服务实例 - 实现 AIAction 类处理自然语言提示并执行 AI 规划的浏览器操作 - 支持通过 max_step 参数限制 AI 操作步数(默认5,最大20) - 支持 include_html 和 include_variables 控制上下文内容传递 - 支持运行时覆盖模型和温度参数 - 增加详细的错误处理与日志记录机制- 更新 README 文档说明 AIAction 使用方法与配置选项 - 更新 XSD schema 支持 ai 类型动作定义- 在 FlowRunner 中完善步骤记录逻辑,支持 AI 执行结果输出 - 添加 plan_ai_action.md 设计文档描述实现细节与规划
16 KiB
xspider 模板爬虫
基于 XML 模板驱动的浏览器自动化采集引擎,负责监听 Redis 队列获取任务模板,利用 DrissionPage 浏览器执行登录与业务流程,并将抽取结果写入 MongoDB。本项目同时集成变量服务、验证码识别及分页、下载等常见采集场景的基础能力。
功能亮点
- 使用标准化 XML 模板描述站点、流程与字段,无需改动代码即可上线新任务。
- Redis 队列拉取模板地址,支持多实例横向扩展。
- 内置 Redis 变量服务,自动处理站点作用域与全局作用域的变量读写。
- 提供可扩展的 Action 注册机制,可按需新增自定义动作。
- 采集结果默认落地 MongoDB,可按数据类型、唯一键去重。
目录结构
├── main.py # 应用入口,初始化日志并启动循环
├── xspider/
│ ├── app.py # TemplateCrawlerApp,协调整体流程
│ ├── runner.py # FlowRunner,调度动作与抽取逻辑
│ ├── browser.py # 浏览器会话封装,对接 DrissionPage
│ ├── actions/ # 内置动作定义、注册
│ ├── extraction.py # HTML 解析与字段抽取
│ ├── storage.py # MongoDB 存储实现
│ ├── redis_queue.py # Redis BLPOP 队列封装
│ ├── variables.py # 变量服务访问与缓存
│ ├── xml_parser.py # XML 模板解析
│ └── utils/ # 辅助方法(选择器判断等)
环境要求
- Python 3.10+
- 浏览器驱动建议使用 DrissionPage 配套环境
安装依赖示例:
pip install drissionpage redis requests pymongo lxml cssselect
如需验证码识别、自定义变量服务或下载逻辑,请在业务环境中提供对应服务。
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
XSPIDER_REDIS_URL |
redis://localhost:6379/0 |
Redis 连接串 |
XSPIDER_REDIS_LIST_KEY |
xspider:config |
待消费的模板地址所在 list |
XSPIDER_REDIS_BLOCK_TIMEOUT |
30 |
Redis BLPOP 阻塞秒数 |
XSPIDER_MONGO_URI |
mongodb://localhost:27017 |
MongoDB 连接串 |
XSPIDER_MONGO_DB |
xspider |
MongoDB 数据库名称 |
OPENAI_API_KEY |
"" |
调用 gpt-4o 时使用的 API Key(留空则禁用 AI 动作) |
OPENAI_API_BASE |
https://api.openai.com/v1 |
OpenAI API 基础地址,可按需切换代理 |
OPENAI_MODEL |
gpt-4o |
默认模型名称 |
OPENAI_TIMEOUT |
60 |
OpenAI 请求超时,秒 |
OPENAI_TEMPERATURE |
0.0 |
OpenAI 采样温度 |
变量作用域说明
- 对变量名使用
site:变量名将强制读取/写入当前站点作用域;global:变量名将强制使用全局作用域。 - 无前缀的变量名会先从站点作用域(依赖
site_id)查找,未命中再自动回退到全局作用域。 - 写入变量时默认落到当前站点作用域(若缺少
site_id则写入全局),也可通过var_scope="global"强制写入全局。 var_ttl、var_single_use会转换为 Redis 过期时间与一次性读取语义,所有数据均保存在XSPIDER_REDIS_URL指定的实例中。
启动方式
python main.py
程序将持续拉取 Redis 队列中的 XML 模板地址,下载模板后解析 site → flow → action,并完成采集与存储。如需停止可使用 Ctrl+C。
XML 模板结构
Schema 提示
若希望编辑器自动提示必填项与可选属性,可在 XML 顶部引入项目内置的 Schema:
<site xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../schema/xspider.xsd"
id="example"
base="https://example.com">
...
</site>
- Schema 位于
schema/xspider.xsd,涵盖站点、配置、动作、抽取等全部节点。 - 修改为相对路径或网络路径后,可在常见 IDE(VS Code、IntelliJ、XMLSpy 等)中获得自动补全、枚举提示与校验。
- Schema 对可扩展字段使用
anyAttribute,因此自定义参数不会报错。
顶层元素
| 元素 | 说明 |
|---|---|
<site id base?> |
必填站点定义,可配置默认 base URL |
<config> |
站点级配置:代理、重试、请求头等 |
<login> |
登录流程,允许缺少 extract;执行完可配置校验选择器 |
<flows> / <flow> |
业务流程,至少包含动作与抽取配置 |
模板示例
<site id="example" base="https://example.com">
<config enable_proxy="false" rotate_ua="false" retry="3">
<header name="Accept-Language" value="zh-CN,zh;q=0.9"/>
</config>
<login url="https://example.com/login" selector="div.dashboard" mode="css">
<action type="wait_dom_show" selector="form#login"/>
<action type="type" selector="//input[@name='username']" text="${account}" mode="xpath"/>
<action type="type" selector="//input[@name='password']" text="${password}" mode="xpath"/>
<action type="click" selector="button.submit"/>
</login>
<flows>
<flow id="orders" entry="/orders" data_type="sales" unique_keys="custom" columns="order_id">
<action type="wait_dom_show" selector="table.data-list"/>
<extract record_css="table.data-list tbody tr">
<field name="order_id" selector="td:nth-child(1)" mode="css"/>
<field name="customer" selector="td:nth-child(2)" mode="css"/>
</extract>
<paginate selector="button.next" mode="css" max_pages="10"/>
</flow>
</flows>
</site>
Action 基础属性
所有 <action> 元素都会被解析为 ActionConfig,其通用属性如下:
| 属性 | 类型 / 默认值 | 说明 |
|---|---|---|
type |
str(必填) |
决定实际执行的动作类 |
selector |
str / None |
DOM 选择器;部分动作必须提供 |
mode |
xpath(默认)或 css |
选择器解析模式 |
timeout_ms |
int / 10_000 |
等待元素的超时,单位毫秒(仅在需要等待元素/动作时使用,部分动作会忽略该值) |
after_wait |
int / 0 |
执行完动作后的额外等待时间 |
params |
dict / {} |
其它自定义键值对,会透传给动作 |
params 支持 ${变量名} 形式的变量占位符,运行时通过 VariableResolver 在站点上下文中解析。下文动作表格中出现的 params.xxx 表示 <action> 元素上的额外属性(除通用字段外),在解析时会放入 params 字典中。
通用字段包括:type、selector、mode、timeout_ms、after_wait,这些字段会直接映射到 ActionConfig 对象,对应的属性不会出现在 params 中。
params 字段举例
解析器会把 <action> 标签上的自定义属性收入 params,例如:
<!-- url 属性不是通用字段,因此会进入 params -->
<action type="goto" url="https://example.com/dashboard"/>
<!-- download_filename 也会进入 params,执行时通过 self.config.params['download_filename'] 读取 -->
<action type="click"
selector="button.export"
download_filename="orders.xlsx"/>
<!-- 多个参数同样会被保留在 params 中 -->
<action type="set_var"
var_name="latest_token"
var_value="${token}"
var_scope="global"
var_ttl="86400"/>
上面的三个动作在 Python 中最终对应的 ActionConfig.params 分别为:
{"url": "https://example.com/dashboard"}{"download_filename": "orders.xlsx"}{"var_name": "latest_token", "var_value": "${token}", "var_scope": "global", "var_ttl": "86400"}
动作类内部通过 self.config.params[...] 访问这些值,因此 README 中的 params.url、params.download_filename、params.var_ttl 等写法,正是指这些自定义属性在 params 字典里的访问方式。
内置 Action 类型
| 类型 | 必填字段 | 可选字段 | 说明 |
|---|---|---|---|
goto |
params.url 或 selector |
无 | 跳转到指定 URL;为空时回退到 entry_url/base_url。 |
click |
selector |
params.button(支持 left/right/middle)、params.download_filename |
点击元素,可触发下载。 |
type |
selector、params.text |
无 | 输入文本;执行前会清空原内容。 |
wait_dom_show |
selector |
无 | 等待元素出现并返回元素引用。 |
wait_dom_gone |
selector |
无 | 等待元素从 DOM 中移除。 |
wait_dom_hide |
selector |
无 | 等待元素隐藏(style 含 display: none)。 |
wait_time |
无 | params.timeout_ms(毫秒,若缺省使用 timeout_ms) |
单纯 sleep 指定时间。 |
run_js |
params.script 或 params.text |
无 | 在当前页面执行 JavaScript,并返回结果。 |
set_header |
params.header_name、params.header_value |
无 | 为当前会话追加请求头。 |
set_attr |
selector、params.attr_name |
params.attr_value(默认空字符串) |
修改 DOM 属性值。 |
set_var |
params.var_name、params.var_value |
params.var_scope、params.var_ttl、params.var_single_use |
将变量写入变量服务,可带过期策略。 |
captcha |
无(若缺省会截取 selector 或整页截图) |
params.image、params.captcha_type、params.captcha_url、params.captcha_config、params.variable |
调用远程接口识别验证码并写入变量。 |
ai |
params.prompt |
params.max_step、params.model、params.include_html、params.include_variables、params.temperature |
调用 gpt-4o 生成 DrissionPage 操作步骤并执行,支持限制最大步骤数。 |
提示
- 当
selector与mode不匹配时(例如 CSS 模式中使用 XPath),解析阶段会抛出异常。- 所有
params字段均支持变量解析与自定义透传。- 若需要扩展新动作,继承
BaseAction并设置type_name后自动被ActionRegistry注册。
SetVarAction 使用示例
SetVarAction 用于把临时生成的数据写入 Redis 变量库,并同步到当前站点上下文,供后续步骤通过 ${变量名} 或 ctx.site_context[...] 复用。其主要参数含义如下:
| 参数 | 说明 |
|---|---|
var_name |
变量名称,支持 ${...} 占位符,最终会作为变量服务的 name 字段。 |
var_value |
变量值,可引用已存在的变量或静态字符串。 |
var_scope |
变量作用域,可选,支持 global/site。未指定时默认写入当前站点作用域。 |
var_ttl |
过期时间,单位秒,可选。变量服务可据此设置生命周期。 |
var_single_use |
是否一次性变量,可选,常用于验证码、令牌等场景。 |
示例(写入站点作用域中的 latest_token,并设置 1 小时过期):
<action type="set_var"
var_name="latest_token"
var_value="${jwt_token}"
var_ttl="3600"
var_single_use="false"/>
动作执行步骤:
- 解析所有参数并进行变量替换,例如
${site_id}、${jwt_token}。 - 根据
var_scope与上下文自动选择站点或全局作用域,并附带 TTL/一次性读取标记写入 Redis。 VariableResolver会将值缓存在内存中,后续解析${latest_token}或${site:latest_token}均可命中(前者会先查站点再回退全局)。
如需写入全局变量,可设置 var_scope="global" 或直接在变量名上使用 global:token_name 前缀。
CaptchaAction 使用示例
CaptchaAction 会将验证码截图或指定的 base64 图片提交到远程接口,并把识别结果写入 Redis 变量库以及 site_context。使用方式如下:
<action type="captcha"
selector="//img[@id='captcha']"
mode="xpath"
captcha_type="text"
variable="login:captcha_code"
captcha_config='{"url": "https://captcha.example.com/api", "timeout": 60}'/>
执行流程:
- 若提供
params.image,会直接使用该 base64 字符串;否则优先根据selector截取元素截图,再退回到整页截图。 - 请求地址取
params.captcha_url、captcha_config.url,最后回退到默认https://captcha.lfei007s.workers.dev。 - 请求体默认为
{"image": "...", "type": params.captcha_type},并支持通过captcha_config添加额外键值(如{"headers": {...}})。 - 服务返回 JSON 后,会尝试读取
result、text、value、code、data等字段作为识别结果。 - 结果最终写入 Redis(默认键为站点作用域下的
captcha_result,或使用params.variable覆盖),并同步到site_context供后续动作引用。
AIAction 使用示例
AIAction 通过调用 OpenAI gpt-4o 规划并执行一组 DrissionPage 原生操作,适合处理动态页面或复杂交互。请确保已经在环境变量或 Settings 中配置 OPENAI_API_KEY。
<action type="ai"
prompt="下载当前页面的首个报表附件,并在完成后返回列表页"
max_step="6"
include_html="true"
include_variables="false"/>
prompt:必填,自然语言描述任务目标,支持变量占位符。max_step:限制模型输出的步骤数量(默认为 5,最大 20)。include_html/include_variables:控制是否向模型提供页面 HTML 片段和当前变量上下文。- 执行过程中会在
task_logs的steps字段中记录每个 AI 子步骤的执行结果、异常信息与返回值。
若未配置 API Key 或 OpenAI 调用失败,动作会抛出异常并在任务日志中记录具体原因。
抽取与存储
<extract>支持record_css/record_xpath指定列表元素,<field>定义字段名称、选择器、取值模式及可选的value_type。<paginate>可配置下一页元素,并限制max_pages。<excel_extract>用于声明需下载并解析 Excel 文件,当前实现会给出提示,具体逻辑可在FlowRunner._handle_excel_extract自行扩展。MongoRepository.save_records会按照flow.unique_keys与unique_columns控制去重策略。
任务日志
每次执行登录流程或业务流程都会写入 MongoDB 的 task_logs 集合,记录执行状态与关键步骤,方便排查问题。
- 基础字段:
_id:UUID,全局唯一的任务编号。site_id/flow_id:对应模板中的站点与流程。status:start、success、error、aborted。started_at/ended_at:UTC 时间戳,duration_ms为执行耗时。steps:执行步骤列表,包含动作类型、分页点击、抽取结果等摘要。metadata:附加信息(如保存记录数量、分页页数、是否登录流程)。xml_source:原始 XML 的来源地址;xml_snapshot:当前执行所用 XML(超出 50k 字符会截断并标记)。error/abort_reason:当流程异常或被中止时记录详细信息(包含堆栈)。
- 日志写入策略:
- 流程开始时插入
status=start的文档; - 正常结束时更新为
success并补充steps、summary、metadata; - 运行中出现异常时标记为
error并写入堆栈; - 收到
KeyboardInterrupt或其它不可恢复的中断时标记为aborted。
- 流程开始时插入
可以根据 site_id + flow_id + started_at 建立索引用于快速查询,也可按 status 过滤待排查的任务。
开发与调试建议
- 日志级别默认
INFO,可自行设置环境变量PYTHONLOGGING或修改configure_logging。 - 建议在本地通过伪造 XML 模板直接调用
FlowRunner.run_site,避免频繁依赖 Redis。 - 若需要 Network/Proxy、自定义 Headers,可在
<config>中补充;所有配置都会在浏览器会话创建前应用。 CaptchaAction默认使用https://captcha.lfei007s.workers.dev,如需替换可在params.captcha_config传入 JSON(例如{"url": "...", "timeout": 60})。
扩展方向
- 自定义 Action:在
xspider/actions新增子类并继承BaseAction,设置type_name与_execute逻辑即可使用。 - 变量服务实现:可基于
VariableService接口扩展自定义存储,例如不同的 Redis 集群或 REST 服务。 - 采集结果落地:若不使用 MongoDB,可实现新的存储类并在初始化
FlowRunner时注入。