摘要:面对微信读书里 17 万字的付费会员书籍,我没有一页页手动截图,而是花了一个下午写了一个 Python 小工具 BookSnap。它通过 ADB 控制安卓设备,自动翻页、截图、去重,最终帮我一次性保存了 754 页高清截图。拿到这些截图后,再喂给大模型做深度分析,一本书就能真正被"读透"。本文记录了我的完整思考、开发过程和关键代码。
一、为什么做这件事?
作为一个经常需要阅读电子书的开发者,我越来越习惯在手机或平板上用阅读 App 看书。但有几个问题一直让我不爽:
于是我想:能不能让手机/平板自己翻页、自己截图,把整本书"翻拍"成一套高清图片,再经过 OCR 识别交给大模型做深度分析?
答案是:可以,而且用 Python 就能做。
二、问题拆解与方案设计
把整个任务拆成 5 个步骤:
1. 在电脑上建立与手机的连接 → ADB2. 让手机截图并传回电脑 → adb shell screencap + adb pull3. 让手机翻到下一页 → adb shell input keyevent / swipe4. 循环执行,直到截完 → Python 循环 + 延时控制5. 判断是否截到了最后一页 → 图片相似度去重
整个流程最核心的技术栈就是:
三、BookSnap 诞生了
我给这个工具取名为BookSnap,意思是"像拍照一样把书保存下来"。
它的使用方式非常简单:
python booksnap.py devices# 查看已连接设备python booksnap.py capture# 开始自动截图
运行后会自动生成 page_0001.png、page_0002.png……一直到最后一页,整套流程全自动,无需人工干预。
四、关键技术点:自动翻页 + 去重
4.1 两种翻页方式
不同阅读 App 的翻页手势不同。微信读书支持左右滑动,也支持音量键翻页。因此我设计了两套翻页机制:
# config.yamladb: page_action: swipe# 可选 key 或 swipe keyevent: KEYCODE_PAGE_DOWN# 按键翻页 swipe: [900, 1000, 100, 1000, 300]# 从右往左滑动
4.2 图片去重:怎么知道"最后一页到了"?
这是整个工具最 tricky 的部分。如果到了最后一页,继续翻页,屏幕内容不会变化。如果一直无脑截图,就会生成一堆重复图片。
我的思路是:把当前截图和上一张截图做相似度对比,如果连续 N 张都几乎一样,就认为已经到最后一页,自动停止。
核心代码如下:
def images_are_similar(filepath1, filepath2, threshold=0.95): ”””判断两张图片是否相似(缩略图灰度对比,带容错)””” thumb_size = (128, 128) pixel_tolerance = 10 img1 = Image.open(filepath1).convert(”L”).resize(thumb_size) img2 = Image.open(filepath2).convert(”L”).resize(thumb_size) pixels1 = list(img1.getdata()) pixels2 = list(img2.getdata()) total = len(pixels1) same = sum(1 for p1, p2 in zip(pixels1, pixels2) if abs(p1 - p2) <= pixel_tolerance) similarity = same / total return similarity >= threshold
这里有几个细节:
五、完整代码结构
项目结构非常简洁,只有 3 个核心文件:
booksnap/├── booksnap.py# 主程序├── config.yaml# 配置文件└── pyproject.toml# 依赖与项目元信息
5.1 主程序 booksnap.py
”””BookSnap - 通过 ADB 自动翻页截图整本图书”””import subprocessimport sysimport timefrom pathlib import Pathimport clickimport yamlfrom PIL import Imagedef load_config(config_path: str) -> dict: with open(config_path, ”r”, encoding=”utf-8”) as f: return yaml.safe_load(f)def run_adb(*args) -> subprocess.CompletedProcess: ”””执行 adb 命令””” return subprocess.run([”adb”, *args], capture_output=True, text=True)def check_adb() -> None: ”””检查 adb 是否可用””” try: result = subprocess.run([”adb”, ”version”], capture_output=True, text=True) if result.returncode != 0: click.secho(”❌ adb 不可用,请确认已安装 Android SDK Platform Tools”, fg=”red”) sys.exit(1) except FileNotFoundError: click.secho(”❌ 未找到 adb 命令,请安装 Android SDK Platform Tools 并添加到 PATH”, fg=”red”) sys.exit(1)def check_device() -> str: ”””检查是否有设备连接,返回设备 ID””” result = run_adb(”devices”) lines = result.stdout.strip().split(”\n”)[1:] devices = [l.split(”\t”)[0] for l in lines if l.strip() and ”\tdevice” in l] if not devices: click.secho(”❌ 未检测到已连接的安卓设备,请通过 USB 连接并开启 USB 调试”, fg=”red”) sys.exit(1) return devices[0]# ... 图片相似度函数 images_are_similar ...@click.group()def cli(): ”””BookSnap - 通过 ADB 自动翻页截图整本图书””” pass@cli.command()def devices(): ”””列出已连接的安卓设备””” check_adb() result = run_adb(”devices”) click.echo(result.stdout) lines = result.stdout.strip().split(”\n”)[1:] devices_list = [l.split(”\t”)[0] for l in lines if l.strip() and ”\tdevice” in l] if devices_list: click.secho(f”\n✅ 已就绪,共 {len(devices_list)} 台设备”, fg=”green”) else: click.secho(”\n❌ 未检测到设备”, fg=”red”)@cli.command()@click.option(”--out”, ”-o”, default=”./output”, help=”截图输出目录”)@click.option(”--config”, ”-c”, ”config_path”, default=”config.yaml”, help=”配置文件路径”)@click.option(”--start”, ”-s”, default=1, help=”起始页码”)@click.option(”--action”, ”-a”, default=None, help=”翻页方式: key 或 swipe (覆盖配置文件)”)def capture(out, config_path, start, action): ”””开始截图整本图书””” check_adb() check_device() cfg = load_config(config_path) adb_cfg = cfg[”adb”] page_action = action or adb_cfg[”page_action”] keyevent = adb_cfg[”keyevent”] swipe_params = adb_cfg[”swipe”] remote_path = adb_cfg[”remote_path”] page_delay = adb_cfg[”page_delay”] max_pages = adb_cfg[”max_pages”] same_page_stop = adb_cfg[”same_page_stop”] similarity_threshold = adb_cfg.get(”similarity_threshold”, 0.95) out_dir = Path(out) out_dir.mkdir(parents=True, exist_ok=True) click.secho(f”📖 开始截图 ({page_action} 翻页, 最多 {max_pages} 页)”, fg=”cyan”) click.secho(f”📁 输出目录: {out_dir.resolve()}\n”, fg=”cyan”) prev_file = None same_count = 0 success_count = 0 try: for i in range(start, start + max_pages): local_file = out_dir / f”page_{i:04d}.png”# 1. 截图到设备 run_adb(”shell”, ”screencap”, ”-p”, remote_path)# 2. 下载到本地 result = run_adb(”pull”, remote_path, str(local_file)) if result.returncode != 0 or not local_file.exists(): click.secho(f” ⚠ 第 {i} 页截图失败,跳过”, fg=”yellow”) continue# 3. 去重检查 if prev_file is not None and images_are_similar( str(prev_file), str(local_file), threshold=similarity_threshold ): same_count += 1 if same_count >= same_page_stop: click.secho(f”\n🛑 连续 {same_page_stop} 页相似度 > 95%,已到达最后一页,自动停止”, fg=”yellow”) local_file.unlink(missing_ok=True) break else: same_count = 0 prev_file = local_file success_count += 1 file_size = local_file.stat().st_size click.echo(f” ✅ 第 {i} 页 → {local_file.name} ({file_size:,} bytes)”)# 4. 翻页 if page_action == ”key”: run_adb(”shell”, ”input”, ”keyevent”, keyevent) elif page_action == ”swipe”: x1, y1, x2, y2, duration = swipe_params run_adb(”shell”, ”input”, ”swipe”, str(x1), str(y1), str(x2), str(y2), str(duration)) else: click.secho(f”❌ 不支持的翻页方式: {page_action}”, fg=”red”) sys.exit(1) time.sleep(page_delay) except KeyboardInterrupt: click.secho(”\n\n⚠ 用户中断”, fg=”yellow”) finally: run_adb(”shell”, ”rm”, ”-f”, remote_path) click.secho(f”\n🎉 完成! 共截图 {success_count} 页”, fg=”green”)if __name__ == ”__main__”: cli()
5.2 配置文件 config.yaml
adb: page_action: swipe keyevent: KEYCODE_PAGE_DOWN swipe: [900, 1000, 100, 1000, 300] remote_path: /sdcard/booksnap_page.png page_delay: 0.5 max_pages: 800 same_page_stop: 3 similarity_threshold: 0.95
这些参数基本可以覆盖绝大多数阅读场景:
- page_delay: 0.5:每页之间留 0.5 秒,等翻页动画完成
- max_pages: 800:最多截图 800 页,防止失控
- same_page_stop: 3:连续 3 页相同则自动结束
六、项目依赖与运行方式
pyproject.toml 里只依赖了 3 个 Python 包:
[project]name = ”booksnap”version = ”0.1.0”description = ”通过 ADB 控制安卓设备,自动翻页截图整本图书”requires-python = ”>=3.11”dependencies = [ ”click>=8.0”, ”pyyaml>=6.0”, ”pillow>=10.0”,][project.scripts]booksnap = ”booksnap:cli”
运行前准备:
uv sync# 或 pip install -e .python booksnap.py capture
七、最终成果:754 页高清截图
我用 BookSnap 对一本微信读书上的书籍进行了完整截图。运行日志显示最终成功保存了754 张图片,从封面到最后一页一页不落。
打开截图检查一下:
- 文件名按 page_0001.png 到 page_0754.png 顺序排列,方便后续整理成 PDF 或做 OCR
这意味着一本 17 万字的书,可以在十几分钟甚至更短的时间内被完整"数字化"成图片集。之后可以:
- 用 OCR 工具(如 PaddleOCR、Tesseract)提取文字
- 用 ImageMagick 或 Python 脚本合并为 PDF
对我来说最激动人心的就是这条链路:BookSnap 把书"拿出来" → OCR 转成文本 → 交给大模型深度分析。一本书不再是单向输入,而是变成了可以和 AI 对话、深度交互的知识资产。
八、踩坑与优化
在开发过程中,我遇到了几个典型问题:
九、未来可以改进的方向
BookSnap 现在已经能很好地完成"截图整本书"的任务,但还有一些值得继续打磨的地方:
十、写在最后
有时候我们会遇到这样的问题:某些内容被锁在 App 里,无法自由使用。技术人的本能不是去抱怨,而是想:"我能不能自己写一个工具来解决?"
BookSnap 就是一次这样的尝试。它没有复杂的架构,也没有高大上的算法,只是用 Python 把 ADB 的命令串起来,加上一个去重判断,就解决了一个实际痛点。
最终,它帮我节省了数小时的重复劳动,也让我重新思考:很多看起来"只能手动做"的事,其实都可以自动化。
如果你也有类似需求,不妨试试这个项目。代码很短,思路很清晰,改一改参数就能用。
项目关键词:Python、ADB、自动化、截图、电子书、微信读书、OCR
适用场景:电子书归档、阅读笔记整理、纸质书数字化参考、批量页面采集研究
免责声明:本工具仅用于个人学习和资料整理,请尊重版权,不要将自动化截图用于非法传播或商业用途。