GitHub开源项目OCR It,专门解决扫描文档、截图等无法直接复制文字的场景,便于后续LLM处理。
一次锁定区域,按快捷键翻页,整本书变成文本。

一款用于读取被困在阅读器中的分页文档的 Chrome 扩展——扫描版书籍、幻灯片、PDF,以及无法选中文本的阅读器。
只需要拖拽出一个捕获区域一次。之后每次按快捷键,都会截取该矩形区域的截图、进行 OCR、然后将文本追加到一份持续增长的抄本中。也可以把整个流程交给扩展自动完成:⌥⇧A 启动自动运行,不断截取、翻页,直到文档结束。
然后把结果粘贴到任何需要的地方——最典型的场景是丢给 LLM:几百页无法选中的文档,现在变成了一份可以交给 Claude 或 ChatGPT 进行摘要、搜索或提问的文本文件。
OCR 在本地运行,使用内置的 Tesseract 构建版本。不需要 API key,不需要网络,图片不会离开你的机器——该扩展完全不发出任何对外请求。
下载此仓库或执行 git clone
打开 chrome://extensions 并开启开发者模式
点击加载已解压的扩展程序 → 选择该文件夹
固定扩展——工具栏图标同时充当页面计数器
所有依赖都已提交。没有构建步骤:npm install 只是为了运行测试或重新内置 Tesseract。
然后检查 chrome://extensions/shortcuts 确认快捷键已生效——当其他程序已经占用了这些快捷键时,Chrome 会静默留空。
安装时不请求任何站点访问权限。单次捕获依赖 activeTab,Chrome 只在你按快捷键或打开弹出窗口时才交付该权限。有两件事需要持久授权——页面加载结束后仍需继续的自动运行,以及在一个跨域 iframe 内翻页——弹出窗口会在需要时为你当前所在的站点提供一个允许按钮。

按 ⌥⇧R,然后拖拽出一个框选中文字。在确认之前可以拖动调整位置、拉动控制点、或用方向键逐像素微移(按住 ⇧ 调整大小)。回车确认。
在文字边缘稍微画小一点——矩形内的所有内容都会被读取,包括页码和页眉。
每翻一页按一次 ⌥⇧S。截图立即拍摄,OCR 在后台运行,两页之间无需等待——捕获会排队,徽章显示正在识别的页数。
配置好下一页控制(见下文)后,⌥⇧A 完全接管:截取、翻页、截取、翻页,直到文档结束。在页面上按 Esc 停止。

每一页都会列出精确裁剪后的缩略图,这样区域偏移能一目了然,而不是等到八十页之后才发现。文本可直接在原位编辑;识别错误的页面可以单独重新识别。
全部复制和下载 .txt 会按顺序输出各页,用 --- page N --- 分隔。
标记为 DUPLICATED(重复)的页面是指文本与前一页完全相同——几乎都是因为文档实际上并没有翻过去。

启用"截取后自动翻页",然后:
点击一个控件——点击"选取控件"然后点击阅读器的下一页按钮。存储的是一个坐标点,而不是 CSS 选择器。
按键——向捕获区域中心所在的那个 frame 发送键盘事件(默认是 ArrowRight),这样阅读器会收到按键而不是宿主页面。
"立即测试"会立即执行一次翻页,不做截取,并报告结果——在开始长任务之前值得先用一下。
存储的坐标点能承受那些会常规性地让 CSS 选择器失效的 DOM 重渲染,而且它能到达选择器无法触及的两个地方:
跨域 iframe。大多数嵌入式阅读器都是 iframe,顶级 frame 无法用选择器表达 iframe 内部的元素。
Shadow DOM。document.querySelector 无法查询 shadow root 内部。
在执行翻页时,坐标点会被分发给每一个 frame,真正拥有它的那个 frame 会响应。Frame 通过向上遍历同源祖先来确定自己在顶级视口内的位置;跨域边界时父 frame 通过 postMessage 把偏移量传下来。(window.screenX 帮不上忙——在 iframe 内部它报告的是浏览器窗口的位置,而不是 frame。)拥有坐标点的 frame 解析坐标穿过所有 shadow root,向上找到最近的实际控件,然后发出完整的 pointerdown → mousedown → pointerup → mouseup → click 序列,这样监听 pointerdown 翻页的阅读器和使用 click 监听的阅读器行为一致。
每次尝试都会记录一个判定结果,显示在弹出窗口和页面 toast 上:
因为目标是屏幕上的一个固定点,在运行过程中调整窗口大小或缩放会破坏它——这和捕获区域的破坏方式完全一样。
⌥⇧A——或点击"开始自动运行"——自动截取、翻页、重复。
每个周期都会等待该页 OCR 完成后再翻页。实际上这几乎没有代价(OCR 比翻页快),而且换来的是无人值守循环最需要的东西:可靠的结束检测。如果只按计时器发截图,可能会越过最后一页,然后在抄本里填满重复内容。
在页面上按 Esc、快捷键、或弹出窗口都可以停止。遇到以下情况也会自动停止:
终止原因会显示在弹出窗口里,所以你离开后回来不会对运行停止的原因一头雾水。如果没有可用的下一页控件,扩展会拒绝启动,而不是卡在第一页反复空转。
Chrome 内置的 PDF 阅读器可用——文字直接从中提取。在页面区域(不是缩略图侧边栏)上画区域,用自己的 ↓ / PageDown 翻页。
自动翻页在 PDF 阅读器内两种模式都不生效:该阅读器是一个插件,任何扩展都无法注入,所以点击会落在 <embed> 上,而它的翻页是原生滚动行为,模拟键盘事件无法驱动。既然每页都要自己按快捷键,再用自己的 PageDown 也没什么成本。
对于本地 PDF(file:///…),打开 chrome://extensions → OCR It 的详情 → 启用允许访问文件 URL。Chrome 在此之前不会给任何扩展开放 file://。
扩展自带三种语言——英语、葡萄牙语和西班牙语。Tesseract 的其他约 100 种语言都可以添加,但运行时不会去拉取,所以模型必须先内置到扩展里。
npm install # 一次性,运行工具链
npm run vendor -- fra deu jpn # 任意 tesseract 语言码
这会把每个 .traineddata.gz 拉到 vendor/lang/。然后把语言码加到 src/shared.js 的 LANGUAGES 里,这样它们就会出现在弹出窗口的下拉框中:
export const LANGUAGES = [
{ code: 'eng', label: 'English' },
{ code: 'por', label: 'Portuguese' },
{ code: 'spa', label: 'Spanish' },
{ code: 'fra', label: 'French' }, // 新增
];
重新加载扩展,语言码 Tesseract 用的三位码:fra 法语,deu 德语,ita 意大利语,nld 荷兰语,rus 俄语,jpn 日语,chi_sim 简体中文,ara 阿拉伯语。完整列表在 tessdata 仓库中。
同时使用两种语言效果也很好——给一个 eng+por 的码,Tesseract 会把两个模型加载到同一个 worker 中,读取混合语言的页面:
{ code: 'eng+por', label: 'English + Portuguese' },
这会稍微牺牲一点速度和准确率,所以如果文档只有一种语言,优先用单一语言。
体积。每种语言大约给扩展增加 0.7–3 MB——英语最大 2.9 MB,法语最小 0.7 MB。模型来自 @tesseract.js-data/<code>/4.0.0_best_int:即"最佳"模型量化到整数,比快速变体明显更准确。
移除语言:从 vendor/lang/ 删除对应的 .gz,并从 LANGUAGES 删除对应条目。
MV3 service worker 没有 DOM 也没有 Worker,所以大量计算活在一个 offscreen document 里。
run loop ─┐ (⌥⇧A: capture → turn → repeat)
hotkey ───┴▶ background.js ─▶ hide our own HUD, wait for a paint
─▶ chrome.tabs.captureVisibleTab (whole viewport)
─▶ offscreen: crop to the region, upscale, greyscale
─▶ store the page + thumbnail, turn the page
─▶ queue ─▶ offscreen: Tesseract ─▶ text into storage
区域以 CSS 像素为单位相对于视口存储。截取时用截图自身的宽度除以实时的 innerWidth,所以缩放变化和 retina/非 retina 差异都能正确处理,无需依赖存储的 DPR。
HUD 先隐藏,给它两个动画帧的时间消失,然后再截屏,这样扩展自己的 toast 不会被裁进截图中。
截取是串行的,OCR 一次只处理一个任务,所以快速连按快捷键只会把工作排队,而不会搞乱页面列表。
全尺寸裁剪只在页面成功识别后才丢弃;缩略图会保留用于核对。
运行通过递增一个 token 来取消,循环在每个 await 处重新检查,所以停止发生在检查点而不是写操作中途。循环内的存储读取同时充当 service worker 的保活信号,如果 worker 还是被回收了,一分钟的闹钟会重启循环。
npm install
npm test # 加 -- --headed 可以看运行过程
npm run shots # 重新生成 docs/ 里的截图
测试套件通过 DevTools 协议把未打包的扩展安装到一个真实的 headless Chrome 中,提供 fixture 文档,驱动真实产品:用模拟鼠标事件拖出区域、触发截取、检查 OCR 文本与渲染内容是否一致、验证区域外的内容没有泄露、检查发布的 manifest 没有请求任何主机访问且一次工具栏点击就足够普通截取、测试重复检测、自动前进在三种 DOM 形态下驱动——普通页面、跨域 iframe 和打开的 shadow root——确认配置错误的自动前进会自我报告而不是静默失败、驱动无人值守循环跑完有限文档并断言它自行停在了最后一页且所有页面按序排列、检查运行可以在请求时立即停止。
Chrome 137+ 忽略 --load-extension,所以测试工具通过 CDP 的 Extensions.loadUnpacked 和 --enable-unsafe-extension-debugging 来安装扩展。Headless Chrome 无法显示权限提示,所以行为测试安装的是一个内置授权的扩展副本——即用户点击了 Allow 之后的状态——而权限部分检查真实的 manifest 并证明未授权路径仍可通过 Extensions.triggerAction 工作,这是一个真正的工具栏点击。
chrome:// 页面、Web Store 和其他扩展的页面对任何扩展都不可访问,包括这一个。
只能截取可见视口——区域必须在屏幕上。
Chrome 对截图限速到每秒几张;截取失败会重试并退避,所以快速连按只会把工作排队。
准确率取决于源文件。清晰的印刷文字识别置信度可达 90%+;低分辨率扫描件和手写文字需要后续整理。
本地 file:/// 文档需要开启"允许访问文件 URL"。
activeTab 无法到达跨域 iframe。如果你的阅读器在 iframe 里,在设置自动前进之前先从弹出窗口授予该站点权限。
MIT——见 LICENSE。内置的 Tesseract 组件保留自己的许可证:vendor/LICENSE.tesseract-core 和 vendor/tesseract.min.js.LICENSE.txt。
基于 tesseract.js 构建。