This commit is contained in:
HuangHai
2026-01-12 16:54:44 +08:00
parent f263c133b8
commit f4b398227e

View File

@@ -0,0 +1,108 @@
# 新电途数据抓取流程说明文档
本文件详细记录了新电途(微信小程序)数据抓取爬虫的架构、抓取流程与关键实现,包含识别方法、异步并发设计、去重策略、鲁棒性与持久化方案,便于后续研发人员理解与扩展。
---
## 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 坐标01000 归一化或 bounds_norm01
- 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*