使用用户脚本为拥有 24 年历史的政府网站添加键盘快捷键
原文:Adding Keyboard Shortcuts to a 24 Year Old Government Website with Userscripts
背景 (Background)
在过去的一年里,我一直在清理美国食品药品监督管理局(FDA)的 510k 数据库的数据[1]。
该数据库收录了 510k 项目[7]的申请,包含 FDA 的审批流程,几乎所有(99%)供人使用的医疗器械都采用这一程序。[2]。
在 archive.org[3] 上的搜索显示,这个网站至少从 2000 年 10 月 18 日就存在了。事实上,我们甚至可以看到它当年的样子。顶上配有官方的 Comic Sans 字体设计的标志。
这是截至 2024 年的网站。令人惊讶的是,在过去的 24 年里,它的外观竟然没什么变化。
从那时起,其主界面几乎没什么变化,更像一个简单时代的活化石。它的样式相当简陋,并且页面都是服务器渲染的。除了用于日期选择器的一些代码,它几乎不包含任何 JavaScript。
根据 .cfm 文件扩展名,它似乎是由一个 1995 年的构建工具 Adobe ColdFusion 构建的[4]。
数据录入
为了清理数据,我使用该数据库的搜索功能按名称查找医疗器械。
然而,数据中存在一些问题,减慢了我的速度。
器械和公司名称没有标准化,可能存在缩写、首字母缩略词或常见的拼写错误。该网站的搜索功能不提供模糊字符串匹配,因此查找一个器械通常需要反复试错。我的工作流程是点击搜索输入框,输入一个名称(可能会输入几次),然后用鼠标高亮文本并将其复制到另一个程序中。
这个过程感觉非常低效。每次搜索,我都必须将手从鼠标和键盘之间多次移动。
我手动搜索了数千个器械,因此优化流程的每一步都是值得的。此外,写代码比手动录入数据更有趣,这给了我一个有趣的休息机会。
我的目标是扩展网站的功能,以便我可以在不离开键盘的情况下完成大部分任务。
用户脚本
什么是用户脚本?用户脚本[5]实际上是一个用 JavaScript 编写的程序,旨在为网站提供原始开发者意图之外的附加功能。
在这个案例中,是为了给 FDA 的 510k 数据库网站提供键盘快捷键。
我想要实现以下任务的快捷键:
- 打开搜索页面
- 聚焦到“器械名称”的搜索输入框
- 复制器械的 510K ID 号码
ViolentMonkey
有许多支持用户脚本的浏览器扩展,但我使用的是一个名为 ViolentMonkey 的工具。它是更流行的扩展 TamperMonkey 的开源替代品。
这个工具提供了一种在不同网站上运行自定义 JavaScript 的好方法。它提供了一个浏览器内的 JavaScript 编辑器,也允许用户从各种用户脚本库安装其他人的脚本。
幸运的是,由于网站使用的是非常简单的 HTML,因此编写这些快捷键的代码非常简单。
快捷键
ViolentMonkey 通过它的快捷键扩展[6],使得注册快捷键变得非常容易。通过在头部(header)添加这一行代码,我可以轻松注册快捷键:
1 | // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1 |
打开搜索页面
这是最容易写的快捷键了。我们只需将位置(location)设置为搜索页面的
URL。当按下 Ctrl + Alt + N
时,该标签页会被重定向到搜索页面。
1 | VM.shortcut.register("ctrl-alt-n", () => { |
聚焦搜索输入框
打开搜索页面后,我想要聚焦到器械名称输入字段。
这是此输入框的 HTML 代码。开发人员很有帮助地给了它一个
ID:DeviceName,所以我们可以使用
document.getElementById() 在页面上找到它。
1 | `<input type="text" name="DeviceName" id="DeviceName" size="20" maxlength="20"> |
这是用户脚本。我们找到该元素,然后使用 focus()
将我们的浏览器焦点放在它上面。
1 | VM.shortcut.register("ctrl-alt-s", () => { |
复制器械 ID
最后一个快捷键稍微复杂一些,因为它必须处理两种情况。在网站上提交搜索表单后,下一个页面可能以两种方式渲染:
- 如果只有一个结果,网站会显示该结果的详细信息,包括 510k 编号。

- 如果有多个结果,网站会显示一个表格,其中包含每个 510k
编号以及指向每个提交详情的链接。

在我们的代码中,我们检查 URL 中是否存在字符串
?ID=,该字符串仅出现在单结果详情页面上。
1 | if (location.href.includes("?ID=")) { |
不幸的是,显示器械 510k 编号的 HTML 元素没有使用 id
属性。因此,我们需要使用该元素的 xpath(XML Path
Language)。
xpath 描述如何从树的根部(文档的起点)走到树上某个特定成员(某个节点)的 “地址” 或 “导航路线”。如果元素在页面上移动,xpath 将不再准确。幸运的是,由于该页面是服务器模板化(server templated)的 HTML,该元素在页面上并不会真正移动。如果这是一个现代的 JavaScript 网页应用,我们就要使用一种不同的方法。
我们可以使用 Firefox 方便的开发者工具中的“复制 xpath”选项来快速找到这个值。
现在我们可以使用 window.navigator.clipboard.writeText()
将该节点的 innerText 值复制到我们的剪贴板。
目前我们有了:
1 | if (location.href.includes("?ID=")) { |
现在我们处理有多个响应的情况。有时我们对同一个器械有多个 510k 提交。我武断地使用最旧的一个值作为打破僵局的规则,所以我的脚本也会这样做。
与之前类似,我们使用表格的 xpath 在文档中找到它。然后我们找到表格的最后一行,并获取它的第三个子元素,即包含 510K 编号的列。一旦我们有了这一列,我们就获取它的第一个子元素,并将其文本写入剪贴板。
1 | } else { |
最后,我们将这段代码封装在 VM.shortcut.register()
的回调函数中,以获得我们的最终脚本。现在,当我按下
Ctrl + Shift + C 时,510k
编号会自动写入我的剪贴板,省去了我必须用鼠标手动高亮 510k
编号的麻烦。
1 | VM.shortcut.register("ctrl-shift-c", () => { |
结论
如果你发现自己被网站上一些重复性的任务所困扰,我强烈建议尝试用用户脚本来自动化其中的一部分。
最酷的部分在于你可以自己动手解决这个问题,并为自己节省一些时间。
很难量化我在这里为自己节省了多少时间,但现在我减少了将手离开键盘的次数,我的工作流程无疑变得更轻松了。
脚注 (Footnotes)
- 1.https://www.accessdata.fda.gov/scripts/cdrh/cfdocs/cfpmn/pmn.cfm ↩︎
- 2.https://www.ncbi.nlm.nih.gov/pmc/articles/PMC10465388/ ↩︎
- 3.https://web.archive.org/web/20001015000000*/https://www.accessdata.fda.gov/scripts/cdrh/cfdocs/cfpmn/pmn.cfm ↩︎
- 4.https://en.wikipedia.org/wiki/Adobe_ColdFusion ↩︎
- 5.https://en.wikipedia.org/wiki/Userscript ↩︎
- 6.https://github.com/violentmonkey/vm-shortcut ↩︎
- 7.510k 项目 是美国食品药品监督管理局(FDA)针对大多数医疗器械在美国上市前要求进行的一项审批流程 ↩︎