# 新电途数据抓取流程说明文档 本文件详细记录了新电途(微信小程序)数据抓取爬虫的架构、抓取流程与关键实现,包含识别方法、异步并发设计、去重策略、鲁棒性与持久化方案,便于后续研发人员理解与扩展。 --- ## 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` 等待残余任务,防止内存堆积。 ### 3.7 模板匹配与设备一致性(关键建议) - **识别原理**:使用 `d.image.click` 等基于 OpenCV 的图形学匹配方法时,本质上是像素级的特征对齐。 - **设备差异性**:不同手机的**屏幕分辨率**、**像素密度 (DPI)** 以及 **UI 缩放比例** 存在显著差异。同一张按钮截图(如 `qbsd.jpg`)在 A 设备上能匹配成功,但在 B 设备上可能因像素尺寸微调或渲染差异而识别失败。 - **一机一策**:在实际生产环境中,建议遵循“一机一议”原则。即针对不同的处理机(或同款但配置不同的手机),应分别采集对应的模板图片,确保匹配精度。 - **优化方案**:建议将模板文件按设备分辨率或型号分类存放(如 `Template/1080x2400/qbsd.jpg`),或者在无法匹配时使用视觉大模型(VL)作为兜底定位方案。 --- ## 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*