评论区的小助手开始读正文了:从 23 个字到整篇文章

2950 字
15 分钟
评论区的小助手开始读正文了:从 23 个字到整篇文章
评论区的小助手开始读正文了:从 23 个字到整篇文章

前阵子给评论区挂了个 AI 小助手:访客留完言,它过一会儿会自己回一条,身份是「Gemini助理」,回复前面挂一枚「AI」徽标和一行免责声明。

它一直能用,我也一直没细看。直到有人在友链页留言问事,它回得像在回文章评论 —— 翻代码才发现,它根本没读到正文。

不是”读得少”,是稳定地只读到 23 个字。

它以为自己在读文章#

小助手的工作流是这样:评论落库 → 触发插件 → 去抓这条评论所在页面的 HTML → 从中抽一段正文当上下文 → 连同留言一起发给模型。

问题出在第三步的”抽”。当时的写法是正则里取页面上第一个 <article>:

const articleMatch = source.match(/<article[^>]*>([\s\S]*?)<\/article>/i);
if (articleMatch) body = articleMatch[1];

听着挺合理。但我这站每页的第一个 <article> 都不是正文,是首页那张壁纸卡片:

<article id="home-wallpaper-card" class="home-wallpaper-card">…</article>

里面只有头像、名字和一句 Hello, I'm HY.。所以不管评论发在哪一页、哪篇文章,小助手拿到的”文章内容”永远是这 23 个字。它只能盯着留言那一句话硬编,回复自然就往客服话术上飘。

把每个页面抽出来的东西打出来看,齐刷刷一模一样:

页面改造前喂给模型的「文章内容」
/friends/ 友链页23 字
/posts/deploy/deployfolia 文章页23 字
/booknav/ 书签导航23 字
/guestbook/ 留言板23 字
/about/ 关于页23 字

先判断「这是哪一页」,再决定喂什么#

这次改的核心不是”把正文抽得更全”,而是先回答一个问题:这条评论发在站内的什么界面。

有了这一层,后面才有得选 —— 文章页该喂全文,友链页该喂友链清单,留言板根本不该硬塞内容。

所以给每条评论先做一次页面分类:

路径判定提示词里带给模型的场景
/posts/**文章详情页页面正文就是这篇文章全文,访客大概率在聊它
/friends/友情链接页列出博主的朋友站点(站名/简介/网址)与自助申请流程
/booknav/书签导航页按分类整理的网站合集,每条含站名、简介、网址
/dynamic/<memo id>/动态(随手记)博主的一条短动态,访客说的「这条」就是它
/guestbook/留言板自由留言,不针对某篇具体文章
/gallery/、/about/、首页…各自归类对应页面一句话说明

只有”哪一页”还不够。真正要连着喂的是页面上有什么内容,而不同页面的内容根本不在同一个 DOM 节点里:

页面类型内容从哪取
文章页 / 关于页[data-pagefind-body](主题给正文打的标记)
友链页 / 书签页 / 相册页 / 列表页#content-wrapper(主内容列)
留言板抓不到 —— 整页由前端渲染,改用站点说明补语境
动态页面根本不存在,从 /dynamic/rss.xml 按 memo id 反查

核心机制#

一、友链页的卡片不在 pagefind-body 里#

主题给正文打的 data-pagefind-body 本该是最干净的抓手,但它只标”进搜索索引的文字”。友链页上它标的是「申请友链」那段说明,真正的友链卡片在它外面 —— 只认这个标记,友链页就只能喂一段申请流程说明,对方问”某个友链站的地址”照样答不出来。

所以规则定成:文章页和关于页优先 data-pagefind-body,其余页面优先 #content-wrapper,再退化 <main> → <body>。

顺带一个容易看错的细节:文章页其实有两个 data-pagefind-body。一个是标题(带 data-pagefind-meta="title",给搜索记标题用的),一个是正文。两个都要。

二、动态页是伪路径,页面上压根没有这个 URL#

动态页的评论区不是内嵌在页面里的,是滚到那条动态时才挂一个 iframe 上去,评论用 /dynamic/<memo id>/ 这个合成路径投进 Waline。也就是说这个 URL 请求出去只会拿到 404 页 —— 想靠抓 HTML 拿动态正文,路一开始就是死的。

改从 /dynamic/rss.xml 反查:那份 RSS 里每条都带 #dynamic-<memo id> 锚点,按 id 匹配就能拿到那条随手记的正文。

三、留言板抓不到内容,那就别硬喂#

留言板整页是 Svelte 在前端渲染的,静态 HTML 里只有一个空壳。硬喂进去会得到一句”留言板 · 正在连接留言服务”,还不如告诉模型「这一页是留言板,访客在这随便聊」。

补语境的活交给 /llms.txt:那份文件里本来就有站点名、一句话简介,还有每个功能页的官方说明(「友链:这里是我的朋友们,欢迎互相访问交流」「留言:欢迎在这里留下你的足迹」)。读一次缓存 5 分钟,留言板、相册这类抓不到正文的页面全靠它撑住场景。

顺便加了个判断:正文短于 30 字就当没抓到,直接走”只给场景”的分支,免得把空壳当正文塞进去。

四、剥标签不能用正则#

这是整件事里最费劲的地方,单独放一节讲。

三个被对账挖出来的坑#

为了确认抽取逻辑真的对,我另写了一版独立实现:用 jsdom 建真 DOM 树、递归遍历出文本,再和插件里那套正则实现逐字符比对。本地构建产物 109 页、线上 17 页、astro dev 渲染 11 页,一开始只对了 74/109。

不一致的地方全指向同一类错误:正则用 [^>]*> 匹配标签,属性值里只要出现 < 或 > 就会切错。

我这站的代码块有个复制按钮,长这样:

<button title="Copy to clipboard" data-code="http://<你的NAS-IP>:8080">

三个坑都是从它开始的。

一、属性值里的 > 让标签提前收尾。 <button\b[^>]*> 会停在 data-code 里面的那个 > 上,剩下一截 :8080"> 被当成正文。实测 30 多篇文章的正文里都混着这种碎片,长得像 --dump-dom">、展开收起 之类 —— 模型拿到这些只会更糊涂。

二、属性值里的 <div 被当成真开标签。 这个更隐蔽:容器切片靠统计 <div> 的嵌套深度找收尾,属性值里冒出一个 <div 就让深度失衡,整个正文多抓 2–3 KB。有一篇实测切了 53205 字符,jsdom 那边真实的只有 51240。

三、嵌套隐藏元素删两层时下标错位。 这个是我修前两个时新引入的:去掉隐藏节点时先算出所有要删的区间、再倒序删;如果内外两层都要删,倒序删完内层,外层的结束下标就偏了 —— 结果多删一段、还留下半个标签。/archive/ 的正文被啃出 onclass="archive-year-toggle…" 这种鬼东西,就是它干的。

修法是换掉整个标签处理:先写个 shieldTagGt() 把属性值里的 > 顶成占位符,再写个引号感知的分词器 scanTags(),扫到标签就跳到它的结束位置。这样属性值里出现的 <div 永远不会被当成标签。容器切片、属性查找、整块删除全部改走分词器。

顺手补的几个:

  • <div hidden> 这种布尔属性要认(统计页图表的空状态就是它),光查 class="hidden" 会漏 —— 但也不能把 overflow-hidden 一起误伤,得按 class 分词精确匹配。
  • <button>、select、option 整块去掉:展开、收起、复制、筛选标签都是 UI 外壳,不是正文。
  • 图说要捞回来。我文章的图说是写在 <img alt> 里的,剥标签时会连图说一起丢。全站文章页 325 个 alt,其中 272 个是有意义的图说(「混淆后的样图一,画面被像素块覆盖」这种),现在会补成 〔图:…〕。
  • 正文里 <4>、设备名<端口> 这种伪标签按浏览器语义处理:<4> 留成正文、</4> 当注释丢掉。否则我写在正文里的占位符会被吃成「设备名」。

怎么证明它真的读进去了#

改完之后有三条互相独立的验证,都是”逐字符相等”这种硬指标,不是看着像:

验证做法结果
离线自测桩掉模型和 Waline 接口跑完整链路41 项全过(其中 11 项是这轮踩的坑逐条固化的回归用例)
独立实现对账jsdom 真 DOM 树遍历 vs 插件实现,要求逐字符相等本地 109 页 / 线上 17 页 / dev 渲染 11 页全部一致
线上端到端真发评论,等真模型真回复4 类页面 4/4

第三条最有意思。我在四种页面上各留了一条测试评论,看它回什么:

友链页,我留言说”朝朝听雨的站我这边好像打不开”:

朝朝听雨的博客地址确实是 rainzt.cn,偶尔可能会遇到服务器维护或节点波动。我已经把这个情况转达给站长了,稍后会去检查一下对方站点的可访问性。

rainzt.cn 这串地址只存在于友链列表里,留言里没提过。老代码给不出这个答案。

文章页(SearXNG 那篇部署教程),我故意问”那三处必改的配置到底是哪三处”:

其实这篇教程里并没有单列”三处必改”,而是直接提供了一份调好的 settings.yml 下载覆盖。要是覆盖后容器起不来,可以先看下 Docker 容器日志报什么错,或者排查下宿主机挂载目录的读写权限与端口冲突。

它把我的提问前提给纠正了 —— 说明它读到的是整篇正文,不是一段摘要。

动态,我问”后来找工作的事有眉目了吗”:

当时发这条动态时他确实挺迷茫的,至于后来找工作的具体进展,得等站长本人上线亲自来跟大家更新啦~

这条动态的正文是从 RSS 反查出来的,页面上根本没有对应 URL。

留言板那条没说错什么,但也没硬扯文章,知道是在闲聊。

上手与环境变量#

不想删代码回滚的话,把 AI_ENABLED 设成 false 重新部署就行,插件完全静默。

想调的几个开关(都在 Vercel 的环境变量里):

变量默认说明
AI_FETCH_CONTEXTtrue是否抓页面内容喂给模型
AI_CONTEXT_CHARS12000正文最多喂多少字;超了掐中间留头尾,不是直接砍尾巴
AI_SITE_SUMMARYtrue是否读一次 /llms.txt 补站点与页面说明
AI_PATH_ALLOW / AI_PATH_DENY空只让某些路径触发

AI_CONTEXT_CHARS 给到 12000 是拿数据定的:我这边文章正文中位数 3000 字出头、九成在 6000 字以内,12000 基本等于整篇都喂过去。真遇到两万字的长文才会截断,截的时候掐中间留首尾,因为结论通常在结尾。

总结#

  • 老版本不是”上下文少”,是抽取逻辑抓错了节点,每页稳定拿到 23 个字的壁纸卡片文案。
  • 新版本先分类页面类型,再按页面类型选容器;友链页要 #content-wrapper,文章页要 data-pagefind-body,动态页走 RSS 反查,留言板靠 /llms.txt 补场景。
  • 剥 HTML 标签别用 [^>]*> 这种正则 —— 属性值里一个 > 就能让整段正文跑偏。引号感知的分词器才稳。
  • 值得花时间写一版独立实现对账。我原本只想”确认没写错”,结果它反过来抓出三个真 bug,其中一个还是我自己改出来的。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
评论区的小助手开始读正文了:从 23 个字到整篇文章
https://www.9ll.uk/posts/blog/aicommentcontext/
作者
HY
发布于
2026-10-16
许可协议
CC BY-NC-SA 4.0
相关文章智能推荐
1
把动态页改成了朋友圈的样子
博客动态页想换个样子,参照的是另一个同主题的站 —— 但数据源不变,还是继续接 memos。扒完两边才发现,上游动态卡片的 DOM 本来就齐 —— 头像、作者、时间、地点、置顶徽章、图片网格、评论按钮一个不缺,差的只是样式。于是这轮改造零改动主题源文件:一份只在动态页加载的样式表,加一个把页脚外壳搬进正文的小脚本。中间踩了三个安静的坑,都是「不报错、但就是不生效」那种。
2
评论区回复默认展开 2 条,长串收在「展开更多」后面
博客友链页有条评论下面挂了 13 条回复,往下翻要滑过整屏;可全收起来又太狠,访客第一眼什么都看不到。最后定成默认展示 2 条,多出来的才收着,还有剩就给个「展开更多」,摊完能收回去。做法照旧只加文件不动上游。踩坑集中在一点上:Waline 是 Vue 渲染的,DOM 会重绘、会随 swup 换页被整体替换,所以折叠状态不能只靠一次性的 DOM 操作。
3
全站加了个「最近评论」:数据直连 Waline,主题文件一个没改
博客在参考站的右栏看到「最近评论」组件,想在自己站上也挂一个,而且要求全站都有。评论数据就在自己的 Waline 后端里,直接调它的公开接口;DOM 和样式走页脚注入,脚本全站加载,桌面挂右栏、移动端挂底部组件栏。踩了三个坑:Waline 的 orig 字段是渲染前的 HTML、类名和外包装撞车吃掉 32px 内边距、窄栏 flex 布局把昵称挤成一个字。
4
回复带上引用:别再让读者猜「@昵称」在回哪一句
博客Waline 的二级回复只渲染一行「@昵称:」,谁在回谁很清楚,回的是哪一句全靠猜。翻 DOM 发现它其实留了线索 —— 回复里那个 @昵称 的链接 href 就是被回复评论的 objectId。顺着锚点把目标评论捞回来,摘成引用块插在下面,点它还能滚过去高亮。踩了个坑:剥 @ 前缀时忘了 clone,把页面上那行整个删了。
5
留言板改成聊天窗口:只改了主题一个文件,还少下载 82 KB
博客在 blog.amamo.top 的留言板看到群聊风格的界面:气泡、成员列表、公告栏,像开在博客里的群。扒了仓库发现后端就是 Waline,我现成有一个。约束照旧是不能动主题源文件,于是把 11 个文件搬过来之后,把原本要改 6 类上游文件的移植压到只动 guestbook.astro 一个文件,顺带把这一页的评论脚本从 100 KB 减到 23 KB。
随机文章随机推荐

评论区

Profile Image of the Author
HY
Hello, I'm HY.
公告
迷茫.... 为了访问速度,本站将会在11月上旬至中旬迁移到0dm.cn并完成备案
分类
标签
最新动态
站点统计
文章
74
分类
8
标签
192
总字数
161,979
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录