书签注入:预填充浏览器书签
如何在启动时通过 JSON 值预填充浏览器书签,让测试和隐私研究会话从有文档记录、可重复的书签状态开始。
BotBrowser Team
BotBrowser 可以在启动时写入有文档记录的书签状态,但不能让网站推断或信任该状态。本文把书签视为浏览器状态的一项输入,并明确说明隐私与可复现边界。
要点
- 让 JSON 保持精简、可审查,并绑定到明确的 Profile。
- 在浏览器中验证最终书签树,不要只看启动是否成功。
- 书签不能证明身份,也不保证网站如何处理它。
内容导航
为什么空书签栏是个不均衡的起点
每天使用的浏览器会在数周乃至数月里积累书签。它们被整理进文件夹,反映用户所在地区、工作和兴趣。为自动化或隐私研究全新启动的浏览器实例,书签栏是空的,这与配置文件其余部分所描述的状态并不一致。如果配置文件描述的是柏林一位说德语的开发者,空书签栏就是一套本来自洽的配置里的一处小小不一致。
BotBrowser 提供 --bot-bookmarks 标志,在浏览器启动时预填充书签存储。该标志接受由 url 和 folder 条目组成的 JSON 数组,因此每个测试会话都可以从你写下并能复现的书签状态开始。
书签是浏览器状态的一层
一个浏览器会话包含多层状态:cookies、localStorage、IndexedDB、浏览历史、区域设置、时区、网络配置和书签。每一层都是独立的数据,可以分别设置。书签是最容易控制的层之一,因为它们是结构很小的普通数据,脚本运行期间除非有东西写入,否则不会变化。
对隐私研究人员来说,控制书签状态对可重复实验很重要。如果你要比较某个网站在全新浏览器和带有一些历史的浏览器中的表现,就必须确切知道两次运行之间究竟哪个状态不同。把书签集写进启动参数,就能把一个看不见的差异变成可记录的差异,你可以审阅它、纳入版本控制并重复运行。
对管理多个身份的团队而言,每个身份都适合配一套与其余配置一致的书签。一个使用德语、柏林时区和德语区域设置的配置文件,在书签指向德语网站时更容易理解。开发者角色的书签是技术类时也更容易理解。目标是得到一套自洽且有文档记录的配置,而不是对任何网站如何反应作出承诺。
空书签栏对某些工作流也是实际问题。如果你需要操作与书签相关的界面元素,例如书签栏、书签管理器或地址栏建议,预填充的存储能让你从第一次页面加载起就有内容可用。
Chromium 如何存储书签
Bookmarks 文件
Chromium 把书签存储在用户数据目录下配置文件文件夹内的 Bookmarks JSON 文件中,例如 Default/Bookmarks。该文件包含一棵树,其根节点包括显示在书签栏上的 bookmark_bar,以及“其他书签”文件夹 other(synced 根节点保存移动设备书签)。本文末尾引用的 Chrome 和 BotBrowser 页面没有记录这种磁盘格式,因此请把下面的字段名当作背景信息,并在你自己的配置文件文件夹里对照 Bookmarks 文件确认。
树中的每个条目都有这些属性:
- name:在书签栏或书签管理器中显示的文本
- url:URL 类型书签的目标地址
- type:
url表示书签,folder表示包含子项的文件夹 - children:嵌套书签和文件夹的数组(用于文件夹类型条目)
- date_added:书签创建时间的时间戳
浏览器启动时会读取该文件并填充书签栏和书签管理器。Chromium 还会为每个条目保存内部元数据,例如标识符和采用其内部格式的时间戳。正是这些元数据,让手写的 Bookmarks 文件很容易出现不易察觉的错误。
预填充书签会出现在哪里
书签会出现在浏览器的几个位置:
- 书签栏:最显眼的位置,启用后显示在地址栏下方
- 书签管理器:可通过
chrome://bookmarks访问 - 地址栏建议:输入时地址栏可能建议已加书签的 URL
书签管理器是确认预填充条目最可靠的地方,因为它列出整棵树,包括文件夹。其他位置取决于界面设置。
不用该标志预填充书签的方式
手动编辑 Bookmarks 文件
你可以创建一个用户数据目录,编写或编辑 Bookmarks JSON 文件,再用该目录启动浏览器。这样可行,但有代价:
- 文件格式包含元数据字段,例如 Chromium 内部格式的标识符和时间戳,必须正确
- 你需要显式管理用户数据目录,并记录哪个目录属于哪个身份
- 书签数据与同一目录中持久化的其他状态之间没有清晰的分隔
使用 chrome.bookmarks API
chrome.bookmarks API 可以通过编程方式创建书签,但 Chrome 文档把它列为扩展 API。普通页面 JavaScript 和自动化框架无法使用它。使用它意味着要安装扩展并授予书签权限,这会增加一个需要维护、审阅并在各身份间保持同步的组件。
通过框架操作书签界面
Playwright 和 Puppeteer 都没有提供书签 API。借助这些框架,唯一的内置途径是打开 chrome://bookmarks 并在界面中点击操作,这既慢又脆弱,而且每个新会话都要重复一遍。
--bot-bookmarks 的工作方式
BotBrowser 通过 --bot-bookmarks 标志处理书签预填充,因此上面三种途径都不需要。
启动时填充
传入 --bot-bookmarks 后,BotBrowser 读取 JSON 值,并在启动时填充浏览器的书签存储。这些条目随即出现在书签管理器以及你的配置会显示书签的其他位置。
JSON 格式
该标志接受描述书签结构的 JSON 字符串:
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"News","type":"folder","children":[{"title":"BBC","type":"url","url":"https://www.bbc.com"},{"title":"Reuters","type":"url","url":"https://www.reuters.com"}]}]'
每个条目都有 title 和 type。url 条目还需要 url,folder 条目则需要包含更多条目的 children 数组。公开的 Bookmark Seeding 文档对字段的描述如下:
- title(必填):书签或文件夹的显示名称
- type(必填):
url或folder - url(
url条目必填):书签地址 - children(
folder条目必填):子条目数组
文件夹可以包含其他文件夹,因此“工作”下再分“项目”、再放具体链接这样的结构,可以用一个值表达。
不涉及扩展
由于填充发生在浏览器内部的启动阶段,无需安装或维护任何扩展。已安装的扩展集合仍然是你选定的那一套,书签状态完全由启动参数描述。
与其他身份设置配合
书签可以自然地与其他启动设置组合。完整的身份设置可以包括书签、cookie、浏览历史、时区、区域设置和网络配置,每一项都显式设定,使会话在内部保持一致。
启动、校验并检查预填充书签
简短的书签栏
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"YouTube","type":"url","url":"https://www.youtube.com"}]'
嵌套文件夹
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.com"},{"title":"News","type":"folder","children":[{"title":"BBC","type":"url","url":"https://www.bbc.com"},{"title":"Reuters","type":"url","url":"https://www.reuters.com"}]},{"title":"Shopping","type":"folder","children":[{"title":"Amazon","type":"url","url":"https://www.amazon.com"}]}]'
通过 Puppeteer
const puppeteer = require('puppeteer-core');
const bookmarks = JSON.stringify([
{ title: 'Google', type: 'url', url: 'https://www.google.com' },
{ title: 'YouTube', type: 'url', url: 'https://www.youtube.com' },
{
title: 'News',
type: 'folder',
children: [
{ title: 'BBC', type: 'url', url: 'https://www.bbc.com' },
{ title: 'Reuters', type: 'url', url: 'https://www.reuters.com' },
],
},
]);
(async () => {
const browser = await puppeteer.launch({
executablePath: 'path/to/botbrowser/chrome',
args: ['--bot-profile=path/to/profile.enc', `--bot-bookmarks=${bookmarks}`],
headless: true,
defaultViewport: null,
});
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
})();
与其他身份设置一起使用
chrome --bot-profile="path/to/profile.enc" \
--bot-bookmarks='[{"title":"Google","type":"url","url":"https://www.google.de"},{"title":"Amazon","type":"url","url":"https://www.amazon.de"}]' \
--bot-inject-random-history \
--bot-cookies="@path/to/cookies.json" \
--bot-timezone=Europe/Berlin \
--bot-locale=de-DE \
--bot-languages=de-DE,de,en \
--proxy-server=socks5://user:pass@de-proxy:1080
在这个组合里,所有地区相关设置都指向德国:书签、时区、区域设置、语言列表和网络出口。该示例要说明的是各层彼此一致,并不保证任何网站会如何对待该会话。
这些标志中,--bot-cookies 和 --bot-inject-random-history 属于 PRO,--bot-timezone、--bot-locale 和 --bot-languages 属于 ENT Tier1,因此完整组合需要包含它们的套餐。--bot-bookmarks 本身属于 Core。
避免引号错误地构建 JSON
使用 --bot-bookmarks 时,常见的问题来源是 JSON 值本身。三个习惯有助于避免其中大部分问题。
第一,条目超过几个之后,不要在 shell 命令里手写 JSON。在脚本中构建普通的数据结构,并用 JSON.stringify() 序列化,就像 Puppeteer 示例那样。Bookmark Seeding 文档给出相同的建议:用 JSON.stringify() 构建该值,不要再额外套引号。
第二,确实要在 shell 中书写该值时,用单引号包住它,让 JSON 内部的双引号得以保留。含有撇号的标题会破坏单引号包裹,这是改用代码序列化的又一个理由。
第三,启动前验证结构。一个小检查就能发现文档所列的两种书签缺失的典型原因:缺少 title 或 type 的条目,以及 children 数组中包含无效条目的文件夹。
function validateBookmarks(entries, path = 'root') {
if (!Array.isArray(entries)) throw new Error(`${path}: expected an array`);
entries.forEach((entry, index) => {
const where = `${path}[${index}]`;
if (!entry.title) throw new Error(`${where}: missing title`);
if (entry.type === 'url') {
if (!entry.url) throw new Error(`${where}: url entries need a url`);
} else if (entry.type === 'folder') {
if (!Array.isArray(entry.children)) throw new Error(`${where}: folder entries need a children array`);
validateBookmarks(entry.children, `${where}.children`);
} else {
throw new Error(`${where}: type must be "url" or "folder"`);
}
});
return entries;
}
const bookmarks = JSON.stringify(validateBookmarks(entries));
每次启动前运行这个检查,可以把悄无声息的空文件夹变成带有问题条目路径的错误信息。在会启动大量会话的测试矩阵中,这就是快速修正与在结果里长时间排查之间的区别。
为每个身份选择书签集
预填充书签与配置文件其余部分遵循同样的决策时效果最好。一种便于管理的做法是在同一个地方为每个身份定义一个具名书签集,并在启动时查找:
const sets = {
de: [
{ title: 'Google', type: 'url', url: 'https://www.google.de' },
{ title: 'Spiegel', type: 'url', url: 'https://www.spiegel.de' },
{ title: 'Amazon', type: 'url', url: 'https://www.amazon.de' },
],
us: [
{ title: 'Google', type: 'url', url: 'https://www.google.com' },
{ title: 'Reuters', type: 'url', url: 'https://www.reuters.com' },
{ title: 'Amazon', type: 'url', url: 'https://www.amazon.com' },
],
};
const args = ['--bot-profile=path/to/profile.enc', `--bot-bookmarks=${JSON.stringify(sets[identity])}`];
构建书签集的一些建议:
- 选择具有该地区、语言和角色的人合理会保存的网站,例如全国性新闻媒体、本地零售商或文档站点。
- 有地区域名时使用地区域名。指向德国零售商的德国身份,比只指向全球站点的身份更容易解释。
- 让书签集小到可以审阅。十到五十个条目是手动维护可以应付的范围,较短的书签集也更便于在不同运行之间比较。
- 在角色会使用文件夹的地方使用文件夹,例如为开发者准备工作文件夹,为消费者角色准备购物文件夹。
- 把每个书签集与启动配置一起放进版本控制,使任何结果都能追溯到产生它的确切书签状态。
一个实际例子可以说明它如何融入测试计划。假设你想比较同一页面的两次运行:一次使用空书签栏,另一次使用上面的德国书签集。用相同的配置文件、时区和区域设置启动两次,只改变 --bot-bookmarks 的值,并把两条启动命令记在笔记里。在每次运行中打开 chrome://bookmarks,确认状态确实不同,然后比较你所研究的输出。如果输出一致,你就知道书签状态对该页面没有影响。如果不同,你就得到一个有文档记录、可重复的差异,可以继续研究。两种结果都有用,也都无需猜测。
请把这些书签集当作你决定测试的角色的描述。它们不能证明某个网站会读取或权衡书签,该标志也没有改变这一点。
启动后检查结果
使用 --bot-bookmarks 启动后,打开书签管理器,并与你传入的值对照:
const page = await browser.newPage();
await page.goto('chrome://bookmarks');
一份简短的清单能让对照更严谨:
- 打开
chrome://bookmarks,确认 JSON 中每个顶层条目都存在,并检查顺序是否符合你的预期。 - 展开每个文件夹,确认子项数量与
children数组一致。 - 查看书签栏本身。如果界面设置把它隐藏了,书签仍可能存在于管理器中。
- 在地址栏输入某个预填充 URL 的域名,看建议列表是否显示该书签条目。
- 把确切的启动值与结果记在一起,以便重复运行。
如果缺少内容,文档给出的排查步骤很简短。书签没有出现时,确认 JSON 有效且每个条目都有 title 和 type 字段。文件夹显示为空时,确保其 children 数组包含有效的书签条目。浏览器报告 JSON 解析错误时,用 JSON.stringify() 构建该值,不要在外面再加引号。
局限与值得保持的习惯
- 书签与配置文件身份匹配。 德语用户配置文件应该有德语书签,如 google.de、spiegel.de 和 amazon.de。
- 不同身份使用不同书签。 每个身份应有自己的书签集,避免结果在不同角色之间被意外共用。
- 保持数量便于审阅。 过大的书签集难以审计,而空书签集又失去了预填充的意义。
- 混合多种类别。 人们会收藏搜索、新闻、购物、社交和工作页面。多样的书签集比单一类别更能描述一个角色。
- 使用文件夹组织。 “工作”“购物”“新闻”之类的文件夹很常见,也能覆盖嵌套格式。
- 显式设置其他层。 书签与
--bot-cookies、--bot-inject-random-history以及区域设置和时区设置配合使用。它们各自都是独立的标志。
BotBrowser 支持 --bot-bookmarks 标志,可在启动时根据由 url 和 folder 条目组成的 JSON 数组填充浏览器的书签存储,让你无需扩展就能为每个测试会话提供有文档记录、可重复的书签状态。它不能保证任何网站或追踪系统如何对待书签状态,不替代 cookie、浏览历史等其他身份层,会话之外的持久化取决于用户数据目录。
关于预填充书签的问题
--bot-bookmarks 接受什么 JSON 格式?
该标志接受书签对象的 JSON 数组。每个对象都有 title、type("url" 或 "folder"),URL 条目还有 url,文件夹条目则有 children 数组。JSON 以字符串形式直接放在命令行参数中传入。
能否从文件加载书签而不是内联 JSON?
CLI 参考文档把 --bot-bookmarks 记为接受 JSON 字符串。对于较大的书签集,请在启动脚本中读取你的书签文件,验证后把序列化的值作为启动参数传入。
启动后书签何时可用?
BotBrowser 在启动时根据你传入的值填充书签存储。打开 chrome://bookmarks 即可确认内容。
书签是否跨会话持久化?
根据 Bookmark Seeding 文档,书签在会话期间保持,并与其他会话数据一起存放在用户数据目录中。因此,是否有内容延续到之后的会话,取决于你使用的用户数据目录。如果想让各身份的书签状态相互分开,请为每个身份提供独立的目录。
能否与 --bot-inject-random-history 一起使用?
可以。这两个标志设置的是不同的层,即书签和浏览历史,可以在同一次启动中一并传入。
--bot-bookmarks 是否需要特定层级?
CLI 参考文档把 --bot-bookmarks 列为 Core 标志。各标志当前的可用性请查看 CLI 标志页面。
应该包含多少书签?
足够描述该角色,又少到可以手动审阅。分布在几个类别中的十到五十个条目,对测试身份来说是个实用的范围。合适的数量取决于你要测试什么。
预填充书签会改变网站对会话的处理方式吗?
不要想当然地认为会。BotBrowser 设置的是浏览器的书签状态。它不声称任何网站或追踪系统会读取或权衡该状态,而且在 Chrome 文档中 chrome.bookmarks 是扩展 API,并非页面 JavaScript 所使用的接口。
值得带走的要点
通过 --bot-bookmarks 预填充书签,让每个 BotBrowser 会话都有一个写明、可审阅、可重复的书签状态。结合 cookie、浏览历史和一致的区域设置,它让测试身份的又一层处于你的控制之下。请在 chrome://bookmarks 中验证结果,把 JSON 放进版本控制,并把该功能视为众多状态层之一。
有关相关主题,请参阅 Cookie 管理 了解会话持久化,合成浏览历史 了解历史填充,以及 配置文件管理 了解完整身份集的组织。
实用结论
用预填充把浏览器状态输入变得明确且可复现,然后验证可见书签树,只保留测试所需元数据。BotBrowser 控制启动输入,但网站最终显示或观察什么仍由浏览器和网站决定。
来源
本文属于指南: 浏览器身份表面:一致性实用总览