ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用LLM增强Emacs浏览器:实现网页摘要、问答与翻译

用LLM增强Emacs浏览器:实现网页摘要、问答与翻译 用 LLM 改进 Emacs 里的浏览器这个想法这两年越来越值得认真试一下。Emacs 自带的 eww 浏览器最大的特点是能把网页转成纯文本省去了大量样式、脚本和广告噪声但它的问题也很明显很多现代网页转完文本之后仍然又长又乱你打开一篇文章光滚动就让人头疼。把 LLM 接进来本质上就是让模型帮你做第二步处理也就是从已经转好的文本里再提炼一遍摘要、重点、问答、翻译都可以在 Emacs 里直接完成不用再切换到外部浏览器去复制粘贴。这篇文章适合两类人。一类是平时把 Emacs 当主力环境、希望阅读和资料整理少一点割裂感的用户另一类是刚接触 LLM 接入、想找一个非聊天场景练手的人。文章按实际落地顺序拆先讲 eww 和 LLM 结合到底解决什么问题再讲前置条件和两种接入方式然后给最小可运行的实现最后补参数、排查和边界。不会画一个特别宏大的架构只讲普通机器上能跑、能验证、能长期用的路径。1. 先搞清楚LLM 到底能为 Emacs 浏览器补上什么短板1.1 eww 的强项和明显短板Emacs 自带的 ewwEmacs Web Wowser最大的优点是不需要离开编辑器就能浏览网页。它用 shr 渲染引擎把 HTML 转成文本和基础格式在终端里也能看纯键盘操作和 org-mode、elfeed 这些工具配合起来非常顺手。对文字型内容比如文档、博客、新闻eww 的体验并不差。短板也很明确。第一现代网页的正文往往被导航栏、推荐位、弹窗和评论区脚本包着eww 转文本时会把这一堆东西一并转出来导致页面文本非常长。第二eww 不执行复杂的 JavaScript很多站点在 eww 里只有半个页面甚至打开就是空白。第三长文章缺少提炼能力你进入一个页面之后还是要从头读到尾才知道它到底讲了什么。这些短板恰好是 LLM 擅长的地方。LLM 不负责把网页变成漂亮的渲染结果它负责读文本、找重点、回答你的问题。也就是说eww 先把网页变成机器可读的文本LLM 再在这个文本里帮你读两者是一条天然的流水线eww 负责取LLM 负责读。1.2 LLM 能插手的四个环节从实际使用来看LLM 在 Emacs 浏览器场景里最有用的不是自动打开网页而是四个环节。页面摘要。进入一个长文章页面后先让模型生成一两百字的摘要快速判断这篇值不值得继续读。这一步看起来简单实际是最常用的尤其是打开技术文档和英文博客时。内容提纯。把正文里重复的背景介绍、过渡段、客套话去掉只保留核心观点。适合篇幅特别长、结构松散的页面。和摘要的区别在于摘要偏“浓缩”提纯偏“保留骨架”。针对性问答。不需要通读全文直接问“这篇文章的实验条件是什么”“作者最后的结论是偏乐观还是偏悲观”模型基于当前页面内容回答。这个能力用于查资料特别舒服你带着问题进页面答案直接出。多语言处理。英文页面生成中文要点或者做段落级翻译。对经常读英文技术文章的人来说比单独装翻译插件更灵活因为可以同时保留原文和译文。需要注意这些能力不是 eww 自带的也不是敲一个命令就一定有而是需要你自己把“当前页面文本”和“LLM 接口”串起来。这个串的复杂度并不高下面前置条件讲完就能动手。2. 前置条件把网页内容整理成 LLM 能读的干净文本2.1 环境准备Emacs 版本、网络和模型服务先确认环境。eww 在 Emacs 24 之后就有内置但建议至少用 Emacs 29新版本的 shr 对表格、图片占位和链接处理的体验更好。如果还在用发行版自带的旧 Emacs建议先升级否则后面处理中文乱码和文本截断时会多出很多无关问题。网络方面取决于你选哪种模型服务这里有一个很实际的分支本地模型比如 ollama 或 llama.cpp 启动的服务不需要外网适合隐私要求高、内容敏感的场景但要求机器配置够内存 16GB 以上跑 7B 模型才比较顺。远程 API比如 OpenAI、Anthropic、DeepSeek、智谱、通义等兼容接口需要网络通畅需要 API key按 token 计费但效果和速度通常更好。原始材料没有指定具体模型和版本落地时先确认你手上服务支持的接口格式和模型名称不要照搬别人代码里的模型名。不同服务的请求路径和参数命名会有差异最稳妥的办法是先把官方文档里的 curl 示例跑通。2.2 从 HTML 到文本正文提取决定后续效果LLM 接口接收的是纯文本不是 HTML。eww 展示的 buffer 已经是渲染后的文本对用户友好但对模型来说还不够干净。我一般不会直接把整个 eww buffer 发给模型而是先做一次轻量过滤。在 Emacs 里eww buffer 的内容可以拿到但里面可能包含页面底部的链接列表、作者信息、版权声明等。比较稳妥的做法是把当前页面内容复制到一个临时 buffer压缩连续空白行按段落重新整理。对大多数文章页面来说直接取 eww buffer 文本也能用真正影响效果的是长度而不是格式。比较麻烦的是那些在 eww 里只显示半屏的页面比如使用大量 JavaScript 渲染的站点。这种情况下eww 拿到的初始 HTML 本身就没有正文再怎么喂给 LLM 也没用。遇到这种站要么换用外部工具先抓完整文本要么让 eww 调用外部浏览器打开。这属于输入源的问题不要归到模型头上。注意先确认你拿到的文本里确实有正文再谈摘要和问答。如果页面在 eww 里本身只有一行“此页面需要 JavaScript”后面所有步骤都会失败。2.3 接入方式本地模型和远程 API 怎么选接入方式我建议按隐私和成本分成两类。本地模型适合的场景文档内容涉及未公开信息、客户资料、内部流程或者所在环境访问外部 API 不稳定或者想长期用但不想按 token 付费。缺点是模型能力相对弱尤其是中文长文理解和指令遵循7B 级别的本地模型能出结果但经常需要反复调提示词。远程 API 适合的场景内容主要是公开网页、技术文档、新闻需要稳定且高质量的中文摘要希望换模型成本低。缺点是每页都调接口会产生费用而且注意不要把不该外发的内容发出去。从工程角度我更建议先做一个小的抽象层用一个函数接收“文本”和“指令”内部再区分本地模型和远程 API。这样后面换模型、换服务商只需要改一个函数不会把整个流程推倒重来。3. 实操从验证接口到在 eww 里直接问页面3.1 先用 curl 验证模型接口在不安装任何 Emacs 包的情况下最简单的方式是先用 curl 调一个 OpenAI 兼容格式的接口。假设你的本地服务地址是http://127.0.0.1:11434/v1/chat/completions以 ollama 作为后端可以这样验证curl http://127.0.0.1:11434/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5:7b, messages: [ {role: system, content: 你是一个阅读助手请用中文总结用户提供的网页内容。}, {role: user, content: 这里是网页正文请输出摘要。} ], temperature: 0.3 }这个命令能跑通说明本地模型服务正常。如果是远程 API把 URL 换成服务商提供的地址在请求头里加上认证信息即可。这里把模型名、请求路径、环境变量都写成示例实际以你的服务配置为准。这一步的意义是先把 Emacs 之外的问题排除掉。接口通了后面在 Emacs 里报错时你就能确认问题出在 Emacs 侧而不是模型侧。3.2 在 eww buffer 里取正文并发送给模型curl 验证通过后再把它搬进 Emacs。下面是一个很朴素的实现思路不依赖第三方包代码是示意结构(defun my/eww-ask-llm (question) 基于当前 eww 页面内容向 LLM 提问。 (interactive s问题: ) (unless (eq major-mode eww-mode) (user-error 请先在 eww 页面中执行)) (let* ((page-text (buffer-substring-no-properties (point-min) (point-max))) (truncated (substring page-text 0 (min (length page-text) 6000))) (json-data (json-encode ((model . qwen2.5:7b) (messages . [((role . system) (content . 你是阅读助手基于用户提供的网页内容回答。)) ((role . user) (content . ,(concat truncated \n\n问题 question)))]) (temperature . 0.3))))) ;; 这里把 json-data 写入临时文件再用 curl 发送解析响应 (message 请稍候正在请求模型...)))实际使用时需要补充请求发送和 JSON 解析上面只展示数据组装逻辑。关键点是取当前 buffer 文本、做长度截断、把问题和文本拼在一起、发送给模型。这里有一个更实用的建议不要每次都发送整个 buffer。很多长页面里正文只占中间一段前面是标题和导航后面是评论区。手动用区域选中正文再发送文本短、费用低、结果也准。可以把函数改成先读区域如果当前没有活动区域再退回到发整个 buffer。3.3 把摘要、问答、翻译绑成快捷键跑通单次调用之后再考虑效率。我的建议是把三个功能分别绑定(global-set-key (kbd C-c e s) #my/eww-summarize-page) (global-set-key (kbd C-c e q) #my/eww-ask-page) (global-set-key (kbd C-c e t) #my/eww-translate-region)三个函数分别对应总结当前页面、基于当前页面回答指定问题、翻译选中段落。实现逻辑相同只是提示词和用户输入不同。这里有一个关键点问答功能的记忆问题。如果你每次只把当前页面文本作为消息发送模型没有任何历史记忆。也就是说不能先问“这篇文章讲了什么”再追问“第二个实验的样本量是多少”因为第二次请求时模型已经忘了第一次的内容。需要把前一次对话记录也一起发过去或者每次提问都重新发送完整页面加新问题。我建议单页场景用第二种每次带完整页面问题走追加。虽然 token 消耗大一点但实现简单不会出现上下文串台。只有当你确认页面文本很短、对话轮次很多时才需要维护一个消息列表。4. 关键参数上下文截断、温度、超时和批量节奏4.1 不是所有网页都适合整页发送网页文本长度是第一个要处理的参数。不同模型支持的上下文长度差别很大有的 8K有的 128K。但支持 128K 不等于 128K 效果都好模型在长文本后半段的注意力通常会下降而且费用会明显上升。我一般按这个规则处理文章类页面先取前 3000 到 6000 字发给模型。如果确实需要全文理解把文本分段每段生成一个小摘要最后让小摘要再合并成总摘要。对技术文档优先取标题、段落首句和代码块说明这些部分信息密度最高。截断不一定硬切。有些接口支持按字符数限制有些需要你在发送前用代码截断。中文按字符数截断时注意不要把关键字切成两半不过对摘要场景来说轻微的切碎影响不大。这段内容对实际落地很重要。很多人第一次跑通时报错“上下文超限”其实不是模型不支持而是没做截断。把截断逻辑写进发送函数里后面换更长上下文的模型时再逐步放大阈值就行。4.2 温度和超时设置温度参数控制随机性。摘要和问答建议设 0.3 到 0.5不要用默认的 1.0。温度过高会出现“看起来通顺但事实不在原文里”的情况。提取事实类问题比如“这篇文章里提到的版本号是多少”温度设 0.1 更稳。超时和重试是容易被忽略的部分。远程 API 在高峰期响应可能很慢给请求加上超时限制比如 60 秒避免 Emacs 因为等待接口而假死。异步调用时要处理用户连续按两次快捷键的情况。同一个模型服务并发请求过多低价 API 会直接返回限流错误。本地模型如果内存不够也会出现排队连续按时会特别明显。所以我的习惯是先发一个请求确认响应时间如果单次只要三秒那连续操作没问题如果单次要二三十秒就要在函数里加“正在处理”的提示避免用户以为卡死了。4.3 批量处理和成本判断批量处理建议单独考虑。如果要对整个 RSS 列表里的十篇文章逐篇生成摘要不要写一个循环直接并发打接口。稳妥做法是逐个处理每篇之间停顿几秒输出结构统一为“标题 三行要点”失败的单篇记录地址最后统一重试。不要一上来就开最大并发先处理一篇看耗时就清楚整个批量的节奏了。成本不只看单次价格还要看失败率。一个模型如果经常截断、经常返回空内容实际成本会高于标价。判断一个模型在网页阅读场景是否够用我一般看四个指标指标判断方式摘要完整性摘要是否覆盖核心结论而不是只复述开头事实一致性问答里的数字、名称、结论是否能在原文找到中文流畅度中文是否自然有无明显翻译腔或啰嗦表达返回稳定性连续 10 次调用是否都能返回合法 JSON无空内容、无超时四个指标不需要一次跑完正常使用一周就能判断。如果只是自己读文章本地 7B 模型够用如果要写进工作流、生成报告素材建议用更强的远程模型省下来的整理时间通常比 API 费用值钱。5. 排查链路从输入文本到接口响应再到输出质量5.1 先确认发给模型的是不是正文很多人遇到摘要胡说八道第一反应是换更大的模型其实先应该看发给模型的文本到底是什么。我实测里踩过最多次数的坑是发送的文本里根本没有正文而是 eww 页面底部一排链接或者一段无法显示的提示。排查顺序在 Emacs 里进入 eww buffer重新打开页面确认正文在不在。手动选中一段正文执行发送函数看发送的文本内容。如果发送的是乱码检查 Emacs 的编码设置和页面响应里的字符集。如果发送的是空文本检查是不是取错了 buffer。这一步花两分钟能省掉后续大量无效调参。很多人一看到摘要不对就换模型、改温度实际上是输入文本就不对改什么都没用。5.2 接口报错定位顺序接口报错是最容易定位的但很多人习惯只看 Emacs 的 message 缓冲区。更好的做法是先把 curl 命令单独跑一遍看原始响应。常见情况认证错误API key 错误或没有权限。404接口地址路径不对很多本地服务不止一个版本路径。429限流降低频率或等待重试。500 / 502服务端问题换时间段再试。返回 JSON 但没有内容检查消息结构有些接口要求系统消息必须放在第一条不能为空。如果返回内容里出现转义字符没有被正确解析多半是 Emacs 里读取 JSON 的方式问题而不是模型问题。建议用 Emacs 自带的 JSON 解析函数不要在 shell 层直接截字符串。解析之后把生成的内容显示到一个临时 buffer方便用户复制到 org 文件里。5.3 输出截断和提示词不稳定怎么处理模型能跑通但输出质量不稳定这种情况最耗时间。我的经验是先固定两个变量提示词和文本预处理。提示词要写得具体。比如“请用不超过三点的列表列出文章核心结论不要引用原文长句”比“请总结这篇文章”稳定得多。文本预处理方面把连续的空白行压缩、去掉 eww 的 footer、把超长段落按段落拆分这些操作单独写成函数保证每次发给模型的文本结构一致。还有一个常见问题输出被截断。很多模型的输出 token 有上限长摘要会在中间断掉。处理方式有两种一是把摘要要求字数降低二是给接口加输出长度参数确保生成长度有保障。注意这个参数在不同版本接口里的名字不一样接新服务之前先确认参数名。如果发现连续几次输出都不稳定不要一直试先把发送给模型的完整请求打印出来看是不是提示词和文本拼接出了问题。很多时候问题出在自己代码的字符串拼接上而不是模型本身。6. 边界与扩展长期使用值得保留哪些场景6.1 推荐长期用的工作流我最推荐的场景是把 eww 当阅读预处理工具而不是把 LLM 当浏览器渲染引擎。日常读技术博客、官方文档、长文章时先用 eww 打开页面执行一个函数生成摘要和要点判断要不要细读。细读过程中选中某一段让模型解释或翻译不需要切换到外部浏览器和聊天窗口。这个流程用几周之后会明显减少在浏览器和编辑器之间来回切换的次数。另一个不错的场景是给 org-mode 记笔记。读完页面后把生成的摘要、要点和页面地址一起写入 org 文件形成可检索的资料库。这个场景下LLM 的输出不需要特别华丽只要准确、短、可编辑就行。6.2 不要过度依赖 LLM 的场景以下几种情况建议不要纯靠 LLM。复杂网页交互。需要登录、需要点击按钮才能加载内容的页面eww 本身就拿不到完整内容LLM 再强也补不了前置缺失。实时资讯阅读。LLM 有延迟一篇文章五六千字从发送到拿到摘要通常需要几秒到几十秒。追求秒开体验的人会觉得别扭。需要逐字准确的内容。代码示例、版本号、API 参数表这些内容用摘要模式会丢信息。正确用法是把原文全文保留到本地让 LLM 只做索引和定位真正核对时还是看原文。这些边界不是劝退而是提醒你设计工作流时把 LLM 放在“辅助阅读”的位置而不是“唯一事实来源”的位置。6.3 后续可以做的扩展如果这个方案用顺手了可以再往几个方向扩展把批量摘要接入 elfeedRSS 文章自动生成摘要标题。把提问历史和当前页面地址存入 org 文件形成“今天读了什么”的日志。在函数里加入本地模型和远程模型的自动切换内容敏感时走本地公开内容走远程。对常用站点写针对性的正文提取规则减少不适用的页面。这些扩展不需要一上来就做。我个人更建议先把单页面摘要、问答、翻译这三个基础功能跑稳定确认日常访问的页面里大部分都能拿到干净的正文文本再考虑批量和自动化。工具链越简单越容易坚持用下去。最后留一个经验这类方案真正落地时最该盯住的不是模型选得多大而是输入文本是否干净、接口超时是否处理、失败时是否有日志。这三件事做好以后哪怕换更强的模型迁移成本也极低。
返回列表