欢迎光临
我们一直在努力

Mika 站点知识实战:bilibili.com 的 SITE.md 逐字段解析与网页办事链路

【免费下载链接】Mika

Mika — 你手机上的私人 AI 助理,替你操作 App、上网、把事办成。An on-device AI agent for Android that operates apps and the web to get things done for you.

项目地址:
https://gitcode.com/gh_mirrors/mika2/Mika

点击查看 免费下载

Mika 内置的网站知识以纯文本 SITE.md 随 App 发布,用来告诉模型“这个网站是什么、页面和网址怎么组织、能看到什么”。本文以 bilibili.com 的 SITE.md 为主体,逐项拆解它的头部字段、网址规则、功能边界和已知现象,并结合仓库中的索引构建、知识注入、网页读取与节奏控制源码,说明这份十几行的文件为什么能在真实任务里稳定生效,以及照着同一格式补写一份站点知识需要满足哪些校验条件。

这份 SITE.md 在 Mika 的知识体系里处于什么位置

Mika 的内置知识分三类,各管一件事,见 docs/knowledge-format.md:knowledge/apps/<id>/APP.md 描述一个 App 的界面与功能,knowledge/sites/<主域名>/SITE.md 描述一个网站的页面、网址与公开接口,skills/<id>/SKILL.md 描述“一类事怎么做”的可复用方法。行为规定(授权、确认、停止条件、“不要编造”等)不在知识里写,而归通用准则与审批层;这正是为什么 bilibili 的 SITE.md 通篇只有事实,而“只看不要点赞、投币”这类约束出现在评测任务的目标里,例如 eval/tasks/read/content/bili-search-first-video.json。

目录约定与索引在 :core 的 KnowledgeIndex 中固化:dirOf(kind) 把 site 映射到 knowledge/sites,fileOf(kind) 映射到 SITE.md。运行时的 App 内无法列出资源目录,所以 core/build.gradle.kts 在构建阶段注册 knowledgeIndex 任务,扫描 knowledge/ 与 skills/ 两棵目录树、校验头部并生成 knowledge/index.json;校验不通过会直接让构建失败,而不是悄悄丢条目。

bilibili 条目同时具备 App 与站点两份知识:APP.md 与 SITE.md,两者通过头部的 sites: ["bilibili.com"] 与 apps: ["bilibili"] 双向关联,交叉校验会检查“一边写了、另一边必须写回来”(KnowledgeIndex.crossCheck)。这种互相关联在运行时被折叠成同一个“服务”:KnowledgeEntry.service 在站点条目上优先取关联的 App 名,因此知识匹配时同一服务只给排序靠前的一份,另一份等真正进入 App 或网站时再按包名/域名补上(KnowledgeStore.matchTask)。

头部字段逐项解析

原文件的头部是:

—
name: bilibili.com
display: 哔哩哔哩
domains: ["bilibili.com"]
aliases: ["B站", "哔哩哔哩", "bilibili", "b站"]
apps: ["bilibili"]
open: bilibili.com
mode: desktop
description: 视频网站,搜实测、拆机、评测视频并看评论区反馈。
—

头部由 FrontMatter.parse 解析:以 — 包裹,逐行按第一个冒号切 key: value;方括号形式 [a, b] 解析成字符串列表,且引号内的逗号不参与分隔(splitList 会跟踪引号状态)。这意味着 aliases 里写带逗号的别名是安全的。

各字段在构建期与运行期的作用与硬性约束如下:

字段本文件取值约束(构建期校验)运行期作用
name bilibili.com 必须与目录名一致;站点目录名必须是小写主域名(DOMAIN 正则) 条目主键;site:<域名> 形式的读取参数;与 App 互相关联时的键
display 哔哩哔哩 必填 注入上下文时的标题,也是任务文字匹配的候选词
domains ["bilibili.com"] 必填,非空,且 name 必须在其中;www. 会被去掉并转小写 按主机名认站点(含子域名,最具体域名优先)
aliases B站、哔哩哔哩、bilibili、b站 App/网站用 aliases,不能写 terms(terms 只留给技能) 任务文字匹配、"打开/进入/去 + 别名"的命名式命中
apps ["bilibili"] 指向的 App 条目必须存在,且对方向本条目回写 sites 决定 service 名,用于渠道合并与关联技能索引
open bilibili.com 可选(网站不强校验) 需要打开这个站点时使用的地址
mode desktop 只能是 desktop 或 mobile 声明该站点用哪一版页面;实际版本以当前标签为准(见下文)
description 视频网站,搜实测… 必填,按 Unicode 码点计不超过 60 knowledge_search 结果里的说明、检索打分的描述项
pace 未写 只有网站能写,只认 careful 是否按“限制浏览快慢”的节奏停留(本文件未写,即普通节奏)
links 未写 每条写作「名称 | uri」或「名称 | uri | 包名」,且要过 LinkPolicy 检查 深度链接清单,开启 deepLinks 时附在知识正文后并放行对应链接
package_aliases 未写 每条「别名 | 包名」,包名必须在 packages 里 同一 App 多包名时的名称映射(站点一般不用)

还有两条容易踩的构建期规则:不认得的键会让构建失败(例如把 assistant 拼错),以及迁移遗留的 risk、status、updated、verified_on 一律不接受(见 KnowledgeIndexTest 的断言)。正文长度上限为 2500 字(BODY_LIMIT),超长的站名代码表、网址格式或搜索语法应放到同目录的 references/ 附件里,正文只写“什么时候去读哪个附件”。

mode: desktop 与 Mika 的默认策略一致:内置浏览器默认打开桌面版页面(见 prompts/capabilities/web.md),只有在桌面版不可用或这块屏幕上看不清时才用 web_site_mode 切到 mobile,切换会重新加载页面并使已有元素编号作废。值得说明的是,从源码结构看,mode 进入索引并在构建期被严格校验,但运行时的实际版本由浏览器当前标签决定,并由 ContextBuilder.runtimeState 以「网页 bilibili.com(桌面版)」的形式写进每一步的运行时状态(ContextBuilder.kt);也就是说,判断“现在是哪一版”永远以当前标签的真实状态为准,而不是照抄知识头部。

「是什么」:品类偏向如何转成搜索策略

正文第一节写的是这个网站的内容分布:内容覆盖广,3C 数码的拆机、横评、长期使用类视频较多,白电、家居、母婴相对少;作者里既有独立测评者,也有厂商或带商业合作的账号。

这一节不是背景介绍,而是搜索与采信策略的事实来源。它与通用网页规则里“搜索结果摘要和排名只是线索,挑两三条打开读正文”“也搜反面:避雷、后悔、退货、投诉、故障”“只讲优点、照抄参数、带推广链接的稿子降权”配合使用时,模型会自然形成一套动作:先用“拆机 / 横评 / 长期使用”这类词检索,再打开正文与评论区交叉核对。仓库的评测集里也沿用了这一侧重,例如 eval/tasks/read/content/bili-popular-titles.json、eval/tasks/read/content/bili-search-first-video.json 和需要「搜索 → 用户结果 → 主页 → 投稿 → 排序 → 读三条」的长链路用例 eval/tasks/read/content/bili-up-latest-three.json。

页面与网址:把搜索规则写成可执行的形式

原文件给出的两条硬事实是:

  • 搜索:https://search.bilibili.com/all?keyword=<关键词>,关键词需 URL 编码;要限定发布时间,加 pubtime_begin_s、pubtime_end_s,值是 Unix 秒级时间戳。
  • 视频页:/video/BV…。番剧和专栏的地址不是 BV 视频。

把它落到实操上,有几个细节值得单独说明:

关键词编码。中文关键词必须编码后再拼进 keyword,例如“拆机”的 UTF-8 百分号编码是 %E6%8B%86%E6%9C%BA:

https://search.bilibili.com/all?keyword=%E6%8B%86%E6%9C%BA

在 web_evaluate_js 或 web_script 里构造地址时,可直接使用 encodeURIComponent,它是读脚本允许的纯 ECMAScript 全局之一(见 PageScripts.kt 的 ALLOWED_GLOBALS),不需要也不应该手写编码表。

时间窗参数。两个参数是秒级 Unix 时间戳,而不是毫秒,也不接受日期字符串。假设要限定在 2026-01-01 及其之后发布,可以写:

https://search.bilibili.com/all?keyword=%E6%8B%86%E6%9C%BA&pubtime_begin_s=1767225600

配合通用网页规则“按事实类型选时间范围:价格、库存看近一个月,排行看近半年,原理和判据可以更早”,就能把“限定发布时间”从一个模糊要求变成 URL 上的一次性的、可复现的筛选,而不是反复翻页去找。

地址形态决定工具选择。既然视频页是 /video/BV…,那么在页面上定位视频条目就有可靠的判据——链接里含 /video/BV。例如在 web_evaluate_js 里先看一眼列表(选择器只是示意,真实页面结构要按当时读到的情况调整,不要照抄类名):

return […document.querySelectorAll('a[href*="/video/BV"]')]
.slice(0, 10)
.map(a => ({ title: a.textContent.trim(), href: a.href }));

读脚本在隔离世界里运行,看得到真实 DOM,但只能读:改页面、点击、提交、跳转都会以 read_only 失败,任何联网调用(fetch、XMLHttpRequest 等)都会以 blocked_network 失败(见 ReadScript.kt)。要操作页面就走 web_tap / web_type_text,要拿页面背后接口返回的数据就走 web_capture 读取页面自己收到的响应。

知道目标网址就直接打开。通用规则明确要求:知道列表或详情页的网址,就用 web_navigate 直接打开;要回到之前的列表,重新打开它的网址比 web_back 更能保住日期、筛选这些条件。这与 SITE.md 给出的可拼装地址格式正好配套。

功能:三个入口的可见信息边界

SITE.md 的「功能」一节把能力拆成三块,每块都写清了“能看到什么”:

  • 视频搜索:结果有视频链接、标题、发布日期。
  • 视频页:简介正文和发布时间;正文不含评论。
  • 评论区:渲染在单独的 shadow DOM 里,整页读文字或滚动外层页面常拿不到;能读到的通常是按热度排在前面的部分,每条有作者、正文、点赞数、楼中楼回复数、日期、是否置顶;页面上另有评论总数。

这三点直接影响工具选择,而不是“多点几下就能解决”的问题:

  • 要标题、链接、发布日期:web_read_page 读整页文字就够,长页面用 nextOffset 续读;需要结构化时用 web_evaluate_js 把列表转成数组。
  • 要简介:视频页正文里就有,但不要在正文里找评论——文件已经点明“正文不含评论”,在简介区反复滚动检索是无效动作。
  • 要评论:必须进入评论结构本身。Mika 的元素快照在遍历时只沿开放 shadow root 往下走,见 app/src/main/assets/web/page.js:
  • function visitChildren(el, ctx, out, state) {
    if (el.shadowRoot) {
    for (var s = el.shadowRoot.firstElementChild; s; s = s.nextElementSibling) visit(s, ctx, out, state);
    }
    for (var c = el.firstElementChild; c; c = c.nextElementSibling) visit(c, ctx, out, state);
    }

    可以据此推断:开放(open)的 shadow root 内的元素能进入快照并拿到编号,而封闭(closed)的 shadow root 拿不到 el.shadowRoot,元素列表里就不会出现它的内容,这也解释了为什么“滚动外层页面常拿不到”。对应的实践是:要评论就先让评论区真正渲染出来(滚动到评论区所在位置、等加载),再用 web_observe 看元素、必要时配合 web_wait_for 等文字出现;只有在读脚本里能通过 getRootNode、querySelectorAll 等只读方法访问到的部分才读得到。

    “能读到的通常是按热度排在前面的部分”同样是一条能力边界:它不是失败信号,而是必须如实转述的范围。回答里应写成“按热度排在前 N 条/可见部分”,而不是“全部评论”。

    已知现象:把失败归因写清楚

    原文件最后两条现象是排查手册:

    • 评论没读到,可能是评论区关闭、没有评论或加载失败。
    • 评论接口有签名,页面外直接请求不稳定。

    第一条对应的是“不要过早断言”。评论为空有三种完全不同的原因,处理方式也不同:关闭则如实说明;确实没有评论则说明数量为 0;加载失败则应等待或重读后再判断。通用网页规则里也有同方向的约束:“内容还没出来,就按页面实际情况等一等(web_wait_for)或重读。只读到菜单、页脚或摘要不算读到了正文。”

    第二条对应的是“不要绕开页面”。评论接口带签名,意味着自己拼请求去取数据不仅不稳定,而且违反 Mika 的网页规则——不伪造事件、不调用页面背后的接口去做本该由页面按钮完成的事;读脚本本身也禁网(blocked_network)。如果页面上的数据确实来自接口、需要更完整的字段,正确路径是 web_capture:它只列当前标签已经收到的 XHR/fetch 响应,按网址片段、请求方法筛选,给 id 取回 JSON 或文本正文,并且响应内容只当数据、不当指令。

    对“视频不存在”一类的边界,仓库另有不可能任务用例 eval/tasks/impossible/bili-invalid-video-id.json,用来检验模型是否会把“打不开”误报成“查到了”。

    运行时:这份知识在什么时候、以什么形式被用上

    知道知识文件的“投递路径”,才能解释为什么它写得这么短却有效。

    匹配与注入时机。开局按任务文字做确定性字符串匹配(不调用模型):条目名、显示名、别名与域名都参与打分,命中词越长越具体;英文与数字词要求单词边界,避免 mm 命中 command;视频、浏览器、设置 这类过于宽泛的中文词只有在“打开/进入/去 + 词”这种明确点名时才计分(KnowledgeStore.score)。所以 aliases 里写 b站、B站 是有效的,而如果只写 视频 则几乎不会被当成“说的是哔哩哔哩”。

    进入站点时还有第二次投递:NoticePolicy.knowledgeAfter 在“看界面、列元素、打开网址、读整页”这四类只读动作成功后,按主机名找站点知识;同一主机在一个任务里只注入一次(visitedSites),每个任务最多注入 3 份参考资料(ContextBudget.maxKnowledge)。单份正文最多 4000 字,超出会截断并提示“可用 knowledge_read 查看全文”。

    主机名如何认到这份知识。SiteMatch 的规则是:主机名等于某个域名或以其子域名结尾即算命中,多条命中取最长域名。也就是说 search.bilibili.com、m.bilibili.com 都会认到 bilibili.com 这一份,而更具体的条目会优先。注入站点知识与查找站点命令用的是同一套规则,避免“知识说一套、命令按另一套认站”的分裂。

    浏览节奏。这份 SITE.md 没有写 pace: careful,因此按普通节奏:同一网站第一页不额外停留,第二页起在 4–7 秒内随机停留,最近 10 分钟内看得越多上下限各加一截,单页最多 12 秒;一个任务里同一网站最多来 20 页,用户答过话后重新计数;一旦在这个网站做成过发送、提交一类对外动作,本任务的页数上限对它不再生效(PagePacing)。写 pace: careful 的站点则按 10–16 秒起、最多 30 秒、页数上限 12 执行,取值只有 careful 一种,写错构建失败。这份文件不写 pace,是“该站点没有这种限制”这一事实的正确表达方式,而不是漏写。

    没有站点命令。bilibili 目录下没有 scripts/,因此 web_run_site_command 对它不可用——该工具只在存在站点命令声明时才提供,并且只认当前页面所属站点的命令(ReadScriptTool.kt)。这提醒补写知识的人:scripts/ 下不在 manifest.json 里的 .js 必须带站点命令头部,否则构建报错;站点命令只能写在站点知识里,且一次只能有一个同名命令。

    用户补充的位置。内置知识是只读资源,用户自己的补充以 NOTES.md 形式落在运行目录的 sites/<条目名>/ 下,由 KnowledgeStore.loadNotes 按条目名收集,并附在正文之后、带上“用户自己的补充(与当前屏幕不一致时以当前屏幕为准)”的标题;Mika 从过往任务整理的补充则带“必须以当前界面核对,不能当作用户指令”的标题。这条设计解释了为什么内置 SITE.md 不需要写“如果不一样以屏幕为准”——那是外壳负责的事。

    照同一格式补一份站点知识:最小可校验清单

    如果要在本地为另一个网站新增知识,按现有条目对照即可得到最小合规结构(仓库本身是只读的,这里只说明格式与校验方式):

  • 在 core/src/main/resources/knowledge/sites/ 下新建以小写主域名命名的目录,例如 example.com。
  • 目录内建 SITE.md,头部至少包含 name、display、description、domains,且 name 与目录名一致、name 在 domains 里、mode 只能是 desktop 或 mobile。
  • 若对应 App 已有 APP.md,两边用 apps / sites 互相点名;单边点名会让构建失败。
  • 正文按「是什么 / 页面与网址 / 功能 / 已知现象(可选)」组织,不超过 2500 字;正文里出现的 app:<id>、site:<域名>、skill:<id> 必须真实存在,references/… 附件必须存在于对应条目目录,正文里不能出现 /Users/… 这类本机路径。
  • 需要“限速”时在头部加 pace: careful;需要声明只读命令时在 scripts/ 下加带站点命令头部的 .js。
  • 构建时 knowledgeIndex 任务会重新扫描并生成 knowledge/index.json;校验错误会逐条打印并让构建失败,错误信息形如 knowledge/sites/xxx.com/SITE.md: 缺少 description。
  • 排查清单

    现象优先检查依据
    没注入这份知识 任务文字是否提到 哔哩哔哩 / B站 / b站 / bilibili / 域名;本任务是否已注入满 3 份 KnowledgeStore.score、ContextBudget.maxKnowledge
    进了站却没补知识 只有看界面、列元素、打开网址、读整页这四类成功动作才会触发,且同一主机只触发一次 NoticePolicy.SITE_RESULT_VERBS、visitedSites
    子域名认不到 domains 是否覆盖该主域;更具体域名是否被别的条目抢占 SiteMatch.score
    搜索结果时间窗不生效 参数名是否为 pubtime_begin_s / pubtime_end_s,值是否为秒级时间戳 本 SITE.md「页面与网址」
    把番剧/专栏当成 BV 视频 地址是否确实为 /video/BV… 本 SITE.md「页面与网址」
    评论区读不到 是否只读了整页文字、是否只滚了外层页面;评论区是否关闭、为空或仍在加载 本 SITE.md「功能」「已知现象」、page.js 的 shadow root 遍历
    想直接请求评论接口 读脚本禁网、通用规则不许绕开页面;改走 web_capture 读已收到的响应 ReadScript 的 blocked_network、网页规则
    页面版本对不上 用 web_site_mode 明确切换,并以运行时状态里的「桌面版/手机版」为准 web_site_mode 工具说明、ContextBuilder.runtimeState

    一份不到三十行的 SITE.md,价值不在篇幅,而在于它把“地址怎么拼、哪一版页面、哪块内容读得到、读不到时可能是什么原因”这些可验证的事实固定下来,再由索引校验、域名匹配、上下文注入和节奏控制四层机制在正确的时间送到模型面前。按同样的字段约束补写别的站点,也能获得同一套待遇:构建期被发现写错,运行期在恰当的一步被自动补给。

    赞

    分享

    【免费下载链接】Mika

    Mika — 你手机上的私人 AI 助理,替你操作 App、上网、把事办成。An on-device AI agent for Android that operates apps and the web to get things done for you.

    项目地址:
    https://gitcode.com/gh_mirrors/mika2/Mika

    点击查看 免费下载

    上一篇:
    用 diagram-design 泳道图绘制跨团队协作流程:布局规范、交接高亮与源码级实现

    下一篇:
    SMUDebugTool技术指南:解锁AMD Ryzen硬件调试新维度

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:171主机测评 » Mika 站点知识实战:bilibili.com 的 SITE.md 逐字段解析与网页办事链路
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址