Files
aiData/Apps/XinDianTu/Doc/流程说明.md
HuangHai f4b398227e 'commit'
2026-01-12 16:54:44 +08:00

109 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 新电途数据抓取流程说明文档
本文件详细记录了新电途(微信小程序)数据抓取爬虫的架构、抓取流程与关键实现,包含识别方法、异步并发设计、去重策略、鲁棒性与持久化方案,便于后续研发人员理解与扩展。
---
## 1. 项目概述
目标是自动采集新电途小程序中的充电场站信息:场站名称、价格与会员价、充电枪状态、距离、停车与标签信息、地址,以及 24 小时分时价格表。
---
## 2. 核心抓取流程
爬虫采用“列表 -> 详情 -> 价格 -> 返回 -> 列表”的闭环流程;UI 操作与视觉识别解耦,视觉识别在后台异步进行。
### 2.1 列表页
- 操作:每屏截取一张大图。
- 图形学切片:使用 OpenCV 对列表截图进行卡片分割,生成卡片 JSON 元数据与标注图 `_vl.jpg`(绿色框标注卡片区域),并提取每个卡片的可点击像素坐标 `click_point`。
- VL 模型识别:调用 `ReadImageKit.parse_vl_image(vl_img_path, json_metadata)` 对 `_vl.jpg` 进行识别,返回按从上到下顺序排列的场站语义信息(名称、价格、枪数、标签、距离等)。
- 匹配与点击:将几何卡片与 VL 结果按顺序对齐,读取卡片的 `click_point`,点击进入详情页。点击使用像素坐标的整数值,确保跨设备稳定。
- 去重策略:仅按“列表显示的场站名称”进行去重;名称先经 `clean_station_name` 去掉尾部省略号与空格,例如 `AAABBB...` → `AAABBB`;首次遇到该名称时处理并写入 Redis 键 `crawled:xdt:{name}`,后续同名直接跳过。
- 异步上传备查:截图 `_vl.jpg` 与详情页截图等通过后台线程池上传到 OBS,以便后续排查;UI 不等待上传完成。
### 2.2 详情页
- 进入:点击列表页卡片。
- 操作:截取详情页全图。
- 异步识别地址:调用 `ReadImageKit.parse_address(station_name, detail_path)` 后台提取完整场站名称与地址信息,处理完成后通过 `XinDianTuService.backfill` 回填 Profile。
- 入口定位:查找“全部时段”入口(图形学模板匹配为主,必要时可通过 VL 辅助 `find_all_time_button_coordinate`),点击进入价格页。
- 返回:完成入口点击与截图后,尽快返回列表页以继续 UI 流程。
### 2.3 价格页
- 操作:对价格列表进行多次滑动与截图,使用 MD5 去重防止重复采集。
- 异步识别价格:后台调用 `ReadImageKit.parse_price_schedule_multi(station_name, images)` 聚合并去重多图识别结果;随后使用 `ReadImageKit.hourly_full_day(rows)` 将不规则时段规整为 24 小时整点数据。
- 持久化:通过 `XinDianTuService.process_price_detail_data` 直接保存小时级价格数据。
- 返回:完成截图后立即返回上一级与列表页,保持 UI 流畅。
---
## 3. 关键技术点
### 3.1 识别方法(混合模式)
- 图形学切片:确定每个卡片的点击中心与像素矩形,保证 UI 点击的几何准确性。
- 视觉大模型(VL):`Qwen-VL` 负责卡片语义识别(名称、价格、枪数、距离、标签等),并提供名称与语义字段的稳健解析。
- 广告与功能入口识别:若本地图形学未检测到广告,`Opener.py` 使用 VL 作兜底识别,通过设备 `window_size` 构造宽高,提升跨设备稳定性。
### 3.2 异步并发设计
- 核心:UI 线程只负责点击/滑动/返回,视觉识别与上传在后台异步执行。
- 实现:
- 视觉识别:`AsyncOpenAI` 客户端异步调用;后台任务通过 `asyncio.create_task` 管理。
- 上传备查:使用 `loop.run_in_executor(None, uploader.upload_file, ...)` 将 OBS 上传丢给线程池,避免事件循环类型不匹配导致卡住。
- 好处:UI 流程不阻塞;整体采集吞吐提升显著。
### 3.3 本地路径输入与上传策略
- 本地路径:VL 调用对本地图片自动转 base64 data URL,不再等待 OBS 上传完成,显著降低识别延迟。
- 上传后台:仍将关键图片在后台上传到 OBS 作为备查资料,但不影响主流程推进。
### 3.4 坐标系与点击
- VL 坐标:0–1000 归一化或 bounds_norm(0–1)。
- UI 坐标:屏幕像素坐标(如 1080×2400)。
- 点击策略:使用切片生成的像素坐标整数值 `click_point`,避免相对坐标在不同设备的偏移误差。
- 安全排除:对过于靠近顶部或底部功能区的坐标进行安全排除(`SAFE_EXCLUDE_RATIO` 与 `BOTTOM_SAFE_EXCLUDE_RATIO`),降低误触风险。
### 3.5 去重策略
- 仅按名称去重:`clean_station_name` 去掉尾部省略号与空格,首次遇到即记录 `crawled:xdt:{name}`;后续同名直接跳过。
- 设计原因:直观且符合业务语义,避免前缀/模糊匹配带来的误判。
- 一致性:与艾特吉易充采用相同策略,保持跨项目行为一致。
### 3.6 鲁棒性设计
- JSON 提取:`ReadImageKit._extract_json` 提升对杂乱文本的 JSON 提取能力。
- 中文路径与空文件校验:`Kit.py` 统一图片读写与校验,避免并发读写导致崩溃。
- 异步任务清理:每页结束清理已完成任务,最终 `asyncio.gather` 等待残余任务,防止内存堆积。
---
## 4. 数据持久化
- Service 层统一落库:
- `process_station_list_vl`:按站点生成 Profile 与 Status;解析枪数与当前价格。
- `process_price_detail_data`:保存 24 小时规整后的价格数据。
- `process_station_address`:回填完整名称与地址信息到 Profile。
- Model 结构:`StationProfile`、`StationStatus`、`StationPriceSchedule`。
---
## 5. 配置与参数
- 翻页距离:`SCROLL_DISTANCE_RATIO` 控制 `swipe_ext("up")` 的滑动尺度。
- 等待时间:`WAIT_DETAIL_PAGE_LOAD`、`WAIT_BACK_TO_LIST`、`WAIT_AFTER_SCROLL` 控制页面加载与返回的等待时长。
- Redis 过期:`REDIS_STATION_EXPIRE` 控制去重键的有效期。
---
## 6. 开发建议
1. 异步 QPS:并发识别会提高 API QPS 需求,注意密钥配额与限流。
2. 设备适配:宽高从 `d.window_size()` 获取,避免依赖可能缺失的设备信息键。
3. 故障排查:保留关键截图并后台上传,便于复盘识别结果与 UI 流程。
4. 性能优化:大型图片识别建议优先本地路径 + base64 方案,减少远程下载与上传等待。
---
## 7. 主要代码参考
- 列表页流程与异步上传:[Crawler.py](file:///d:/dsWork/aiData/Apps/XinDianTu/Crawler.py#L150-L299)
- VL 列表识别(本地路径支持):[ReadImageKit.parse_vl_image](file:///d:/dsWork/aiData/Apps/XinDianTu/ReadImageKit.py#L421-L532)
- 地址识别与按钮坐标辅助:[ReadImageKit.parse_address](file:///d:/dsWork/aiData/Apps/XinDianTu/ReadImageKit.py#L972-L1034)
- 多图价格解析与规整:[ReadImageKit.parse_price_schedule_multi](file:///d:/dsWork/aiData/Apps/XinDianTu/ReadImageKit.py#L1034-L1055);[hourly_full_day 用法处见 Service](file:///d:/dsWork/aiData/Apps/XinDianTu/Service.py#L515-L538)
- 去重与名称清理:[Kit.clean_station_name](file:///d:/dsWork/aiData/Apps/XinDianTu/Kit.py#L690-L701)
- 广告兜底识别与设备信息获取:[Opener.py 兜底逻辑](file:///d:/dsWork/aiData/Apps/XinDianTu/Opener.py)
---
*文档更新日期:2026-01-12*