7.2 KiB
7.2 KiB
新电途数据抓取流程说明文档
本文件详细记录了新电途(微信小程序)数据抓取爬虫的架构、抓取流程与关键实现,包含识别方法、异步并发设计、去重策略、鲁棒性与持久化方案,便于后续研发人员理解与扩展。
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. 开发建议
- 异步 QPS:并发识别会提高 API QPS 需求,注意密钥配额与限流。
- 设备适配:宽高从
d.window_size()获取,避免依赖可能缺失的设备信息键。 - 故障排查:保留关键截图并后台上传,便于复盘识别结果与 UI 流程。
- 性能优化:大型图片识别建议优先本地路径 + base64 方案,减少远程下载与上传等待。
7. 主要代码参考
- 列表页流程与异步上传:Crawler.py
- VL 列表识别(本地路径支持):ReadImageKit.parse_vl_image
- 地址识别与按钮坐标辅助:ReadImageKit.parse_address
- 多图价格解析与规整:ReadImageKit.parse_price_schedule_multi;hourly_full_day 用法处见 Service
- 去重与名称清理:Kit.clean_station_name
- 广告兜底识别与设备信息获取:Opener.py 兜底逻辑
文档更新日期:2026-01-12