电脑端 ADB 多线程控制安卓手机,采集美团App"看病买药"页面的药品数据。
- ✅ 多设备并发:同时控制多台安卓手机,每台独立执行任务
- ✅ 可视化界面:PySide6 桌面端界面,全中文显示
- ✅ 任务导入:每台设备独立导入 xlsx 任务文件
- ✅ 暂停/继续:支持随时暂停,继续后不重复采集
- ✅ 状态持久化:JSON 文件保存采集进度,支持断点续跑
- ✅ 去重机制:基于"分类名+药品名+价格"生成唯一key
- ✅ 失败截图:控件查找失败自动截图,便于调试选择器
meituan_pharmacy_scraper/
├── main.py # 程序入口
├── config.json # 配置文件(选择器/参数)
├── requirements.txt # Python依赖
├── README.md # 本文档
├── ui/
│ ├── __init__.py
│ └── main_window.py # PySide6 主界面
├── core/
│ ├── __init__.py
│ ├── logger.py # 日志模块
│ ├── selectors.py # 控件选择器工具
│ ├── automator.py # uiautomator2 封装
│ ├── task_loader.py # xlsx 任务加载
│ ├── state_store.py # 状态持久化
│ ├── exporter.py # Excel 导出
│ ├── device_manager.py # 设备管理
│ └── worker.py # 任务执行器
├── output/ # 输出目录(运行时生成)
│ └── {device_serial}/ # 每个设备独立文件夹
│ ├── logs/ # 设备运行日志
│ ├── screenshots/ # 失败截图
│ ├── state/ # 采集进度持久化
│ └── results/ # 采集结果(xlsx)
└── examples/
└── tasks_template.xlsx # 示例任务文件
- Python 3.11+
- Android 手机(开启 USB 调试)
- ADB 工具(已添加到 PATH)
cd meituan_pharmacy_scraper
pip install -r requirements.txt下载 Android SDK Platform Tools,解压后将目录添加到系统 PATH。
验证安装:
adb version- 手机开启"开发者选项"和"USB调试"
- 用USB线连接电脑
- 手机上允许USB调试授权
验证连接:
adb devices应显示设备序列号和 device 状态。
首次使用需要在手机上安装 ATX Agent:
python -m uiautomator2 initpython main.py- 刷新设备:点击"刷新设备"按钮,更新设备列表
- 选择设备:点击左侧设备列表选中目标设备
- 导入任务:点击"导入xlsx任务"按钮,选择任务文件
- 开始采集:点击"开始"按钮
- 暂停/继续:随时暂停,点击"继续"从断点恢复
- 查看日志:右下角实时显示运行日志
xlsx 文件需包含以下列(第一行为表头):
| poi | shop_name | note |
|---|---|---|
| 天河区体育西路 | 好药师大药房 | 备注1 |
| 海珠区江南西 | 大参林药房 | 备注2 |
poi: 定位点关键词(用于搜索地点)shop_name: 店铺名(用于搜索店铺)note: 备注(可选)
编辑 config.json 可调整:
每个步骤对应多个候选选择器,按顺序尝试:
"home_waimai": [
{"text": "外卖"},
{"textContains": "外卖"},
{"description": "外卖"}
]支持的选择器属性:
text: 精确文本匹配textContains: 包含文本textMatches: 正则匹配resourceId: 控件IDclassName: 控件类名description: 无障碍描述
{
"timeouts": {
"default_timeout": 10, // 默认超时(秒)
"long_timeout": 20,
"short_timeout": 5
},
"scroll": {
"max_scroll_times": 30, // 最大滑动次数
"scroll_pause": 1.0, // 滑动间隔(秒)
"no_new_data_threshold": 2 // 连续无新数据次数
},
"retry": {
"max_retries": 3, // 最大重试次数
"retry_delay": 2 // 重试间隔(秒)
}
}每个店铺生成独立的 xlsx 文件:
- 路径:
output/{设备序列号}/results/{店铺名}_{任务ID}.xlsx - 字段: 分类名、药品名、月销、价格
每台设备独立日志:
- 路径:
output/{设备序列号}/logs/{设备序列号}.log
控件查找失败时自动截图:
- 路径:
output/{设备序列号}/screenshots/{时间戳}_{步骤}.png
- 检查
output/screenshots/目录的截图 - 使用
uiautomator2查看控件树:import uiautomator2 as u2 d = u2.connect("设备序列号") print(d.dump_hierarchy())
- 根据实际控件属性修改
config.json中的选择器
- 检查 USB 连接和授权
- 重启 adb 服务:
adb kill-server adb start-server
- 确认 uiautomator2 已初始化:
python -m uiautomator2 init
程序通过去重 key 保证不重复。如果出现重复:
- 检查
output/state/{设备序列号}_state.json是否保存正确 - 删除状态文件重新开始
在 worker.py 的 _check_control() 方法中添加验证码/滑块检测:
def _check_risk_control(self) -> bool:
# 检测是否出现验证码
if self.selector.find_one("captcha_dialog", timeout=1):
self.logger.warning("检测到风控验证,请人工处理")
self.pause()
return False
return True当前断点精确到分类级别。如需精确到药品位置:
- 在
state_store.py添加last_drug_name字段 - 在
_collect_products_in_category()恢复时滚动到上次位置
创建 configs/ 目录,按机型存放不同配置:
configs/
├── default.json
├── huawei_p40.json
└── xiaomi_12.json
Worker 初始化时根据设备型号加载对应配置。
如需更稳定的跨平台支持,可用 Appium 替代 uiautomator2:
from appium import webdriver
desired_caps = {
'platformName': 'Android',
'deviceName': 'device_serial',
'appPackage': 'com.sankuai.meituan',
'appActivity': 'MainActivity'
}
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)为了验证系统的稳定性和并发处理能力,进行了 Mock 设备压力测试:
- 测试环境:
- 模拟 10 台并发设备 (
MOCK-001到MOCK-010) - 每台设备分配 3 个模拟店铺任务
- 模拟 10 台并发设备 (
- 改动点:
- 输出隔离:引入
output/{serial}/目录结构,彻底解决多设备日志和结果文件冲突问题。 - 去重增强:去重 Key 加入
shop_name维度,确保多店铺并发采集时数据不误删。 - Mock 机制:新增
MockAutomator模块,支持在无物理手机情况下进行全流程逻辑压测。
- 输出隔离:引入
- 测试结果:
- ✅ 并发稳定性:10 台设备同时运行,CPU/内存占用平稳,未出现线程死锁或资源竞争。
- ✅ 数据隔离:各设备结果准确写入各自目录,文件名包含任务 ID,无覆盖现象。
- ✅ 断点续爬:模拟中途停止并重启,系统能准确识别已采集药品,跳过重复项。
MIT License