先从一个很具体的场景说起。你在 Emacs 里用 EWW 打开一篇技术文章,页面确实能显示,链接也能点,但你的第一反应大概率是:这排版也太朴素了。图片基本缺席,侧边栏被拍平,广告和导航区块混在文本里,光标在一大段没完没了的文字中移动,你得先花几秒钟判断“正文到底从哪里开始”。
过去遇到这种情况,大多数人的反应是:Emacs 的浏览器就是不行,还是老老实实切到 Chrome。但如果把标题里的 “Make the Emacs Web Browser Great Again” 当成一个技术问题来认真看,你会发现它真正要解决的根本不是渲染,而是信息提取。EWW 从来不缺文本,缺的是从杂乱网页里把“值得读的部分”找出来的能力。而 LLM 恰好擅长做这件事。把两者接起来,Emacs 浏览器反而可能成为目前最适合做深度阅读和技术研究的内容消费工具。
这篇文章会从 EWW 的文本化逻辑讲起,给出一个把 LLM 接入 Emacs 浏览器的最小闭环,再展开到批量阅读、知识库沉淀、本地模型和云端 API 的选型,以及落地时最容易踩的坑。整体思路是:先运行通一条链路,再逐步扩展成真正可长期使用的网页工作流。
1. 为什么说 Emacs 浏览器的问题不在“浏览器”,而在“信息提取”
1.1 EWW 的真实身份:一个文本渲染器,而不是浏览器
严格来说,Emacs 内置的 EWW(Emacs Web Wowser)不做完整页面渲染。它把 HTML 拉下来,用 shr 等文本渲染机制把网页转成纯文本 buffer。这个设计在早期看起来像缺陷,但实际上是一种取舍:浏览器要展示视觉复杂、交互丰富的页面,而 Emacs 要提供的是可编辑、可检索、可组合的文本上下文。
所以 EWW 里最常见的体验是:打开一个现代网页,图片变成占位符,CSS 布局基本失效,JS 效果不会执行,剩下的是一大段连续文本。它不像浏览器,更像是一个“网页转文本”的转换器。
这个特性决定了 EWW 的适用边界:
- 适合阅读以文字为主的页面,比如技术博客、文档、论文、新闻。
- 不适合操作复杂前端应用,比如在线表单、拉表格、可视化控制台。
- 适合把网页内容复制进 org-mode 做笔记,不适合做“看起来像原网页”的展示。
换句话说,EWW 本来就不是给“视觉浏览”设计的。它的优势在于,网页内容一旦变成文本 buffer,就可以被 Emacs 里几乎所有工具处理:org-mode、isearch、avy、embark、甚至自定义 Lisp 函数。但这也是问题所在:网页文本里混入了大量噪声,正文、导航、评论、推荐阅读、cookie 弹窗全部挤在一起。
1.2 真正的瓶颈:网页是写给眼睛看的,不是写给编辑器看的
网页设计的核心目标是视觉组织。人类用眼睛扫一眼就能区分标题、正文和侧边栏,但这段文本丢进 EWW 后,所有视觉层次全部丢失,只剩结构扁平的字符串。你可以用正则去匹配标题、链接、段落,但面对一个格式混乱的真实网页,正则很快就会失效。
这也是传统自动化网页抓取的老问题:每换一个网站,几乎都要重新适配一次。如果只是想在自己电脑上读几篇文章,这种适配成本太高了。
LLM 改变的是这一层。它不需要你精确写规则,而是理解“网页正文”这个概念。你给它一段 EWW 产生的杂乱文本,它能够识别出哪些是导航,哪些是广告栏,哪些是真正的内容;它还能进一步做总结、提出问题、抽取关键信息,甚至按固定格式输出。
所以标题里的 “Great Again” 并不是说 EWW 会在渲染上追上 Chrome,而是说:当 LLM 参与进来之后,Emacs 浏览器第一次拥有了把“文本”变成“结构化知识”的能力。这个能力在视觉浏览器里不一定存在,因为浏览器默认把网页当作展示对象,而不是当作知识原料。
1.3 LLM 补上的不是“速度”,而是“语义结构”
传统浏览器解决的是:把 URL 变成可视化页面。LLM 浏览器解决的是:把 URL 变成可理解、可处理、可沉淀的内容。这两者完全是不同的事情。
你可以在 Emacs 里保留 EWW 的极简浏览体验,然后把 LLM 放在中间层:
- 第一步:EWW 拉取页面文本。
- 第二步:清洗掉过长空白、脚本残留、无关区块。
- 第三步:把文本交给 LLM,让它输出正文摘要、关键术语、操作步骤或待办事项。
- 第四步:结果插入到当前 buffer,或追加到 org 文件,作为阅读笔记。
这个链路里,LLM 的角色不是“生成网页”,而是“阅读网页”。它替代的不是浏览器,而是你前 30 秒的扫读和判断。这才是这类项目最大的价值:节省的不是打开网页的时间,而是决定“这篇文章值不值得读、重点是什么、要不要记录”的判断时间。
2. 把 LLM 接入 EWW:从文本清洗到总结输出的最小闭环
2.1 前置准备:确认 EWW 环境和 LLM 服务可用
先说前置条件。EWW 是 Emacs 内置模块,通常不需要额外安装,通过M-x eww输入 URL 即可打开。如果你已经使用 Emacs,建议先确认:
- Emacs 版本不能太老,建议使用比较新的稳定版本。
- 确认
eww命令可以正常打开普通 HTTP/HTTPS 页面。 - 确认本机网络环境允许访问目标页面。
- 确认你有一个可以调用的 LLM 服务,可以是本地模型服务,也可以是一个兼容 OpenAI 协议的 API 服务。
在把 LLM 接入 Emacs 前,先用命令行验证服务是否可用。例如,一个常见的本地模型服务默认监听在 127.0.0.1 的 8000 端口,可以通过 curl 验证:
curl -s -X POST http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "local-model", "messages": [{"role": "user", "content": "这是测试,请回复 OK"}], "max_tokens": 10 }'如果返回结果正常,说明你的 LLM 服务可以继续使用。这一步很重要,因为后续在 Emacs 里调试接口会更麻烦,先在外面把接口跑通,能节省大量时间。
2.2 最小示例:把 EWW 页面文本发送给 LLM
现在进入核心环节:在 Emacs 里写一个简单的 Lisp 函数,把当前 EWW buffer 的文本发送给 LLM,并把返回结果展示出来。
以下是一个示意结构,重点是理解链路,而不是直接照抄。不同 LLM 服务的接口地址、模型名称和请求格式可能不同,使用前要适配。
(defun eww-llm-summarize () "Send current EWW page text to an LLM service and show a summary." (interactive) (let* ((page-text (buffer-substring-no-properties (point-min) (point-max))) ;; 把连续空白压缩一下,减少 token 浪费 (clean-text (replace-regexp-in-string "[ \t]+" " " page-text)) ;; 先截断,避免超出模型最大上下文 (truncated (substring clean-text 0 (min (length clean-text) 4000))) (prompt (concat "这是网页文本,请提取核心要点并输出 Markdown 格式:\n\n" truncated)) (payload (json-encode `(("model" . "local-model") ("messages" . [((role . "user") (content . ,prompt))]) ("max_tokens" . 800) ("temperature" . 0.3)))) (result-buffer "*LLM Summary*")) (with-temp-buffer (shell-command (concat "curl -s -X POST http://127.0.0.1:8000/v1/chat/completions " "-H 'Content-Type: application/json' " "-d '" payload "'") t) (goto-char (point-min)) ;; 这里只做了最粗糙的 content 提取,实际使用需要完整解析 JSON (when (re-search-forward "\"content\":\"\\([^\"]*\\)\"" nil t) (let ((summary (match-string 1))) (with-current-buffer (get-buffer-create result-buffer) (erase-buffer) (insert summary) (goto-char (point-min))) (display-buffer result-buffer))))))这个函数的问题很明显:
- 直接用
substring截断文本,可能把关键内容切掉。 - 没有处理 JSON 转义,如果网页文本里有双引号或反斜杠,payload 会出错。
- 没有解析完整 JSON 响应,只用正则找
"content"字段,不稳定。 - 没有处理请求失败、超时、模型不存在等情况。
所以它只能算最简验证。但它的价值在于:把“EWW 网页文本”和“LLM 输出”之间的管道打通了。一旦这条链路通了,后面的优化都能在上面的基础上逐层做。
2.3 从单页总结到批量阅读:把结果写进 org-mode
跑通单页总结后,下一步不是急着让它变好看,而是考虑怎么把结果沉淀下来。一个很自然的做法是:在阅读多篇文章时,把每页的 LLM 总结追加到同一个 org 文件,形成一份带来源链接的阅读笔记。
可以设计成这样的流程:
- 在 EWW buffer 中执行
eww-llm-summarize,获得当前页面的总结。 - 同时把当前页面的 URL 抓出来,插入到 org 文件条目中。
- 按日期或主题组织多个条目。
伪代码思路:
(defun eww-llm-save-to-org () "Append LLM summary of current page to an org file." (interactive) (let* ((url (plist-get (cdr (assoc "url" (eww-data))) :url)) (title (plist-get (cdr (assoc "title" (eww-data))) :title)) (summary (eww-llm-get-summary)) (org-file "~/reading-notes.org")) (with-current-buffer (find-file org-file) (goto-char (point-max)) (insert (format "\n* [[%s][%s]]\n%s\n" url title summary)) (save-buffer))))在实际项目里,你有两种选择:一是把所有逻辑写在一个函数里,界面保持简单;二是拆成独立模块,比如eww-llm.el专门负责请求和解析,另一个文件负责 org 写入。如果只是个人用,前者就够了;如果想让其他 Emacs 用户使用,更建议拆清楚。
关键点不是代码怎么写,而是流程要确认:页面 → 清洗 → LLM → 验证 → 沉淀。每一步都要能单独调试。
3. 真正拉开差距的不是“总结”,而是“网页工作流”
3.1 让 LLM 成为你的“阅读代理”
很多文章讲 LLM 浏览器,都会把重点放在“总结”。但总结只是最浅的一层。真正有价值的是:把网页变成一个可以执行任务的原料。
我一般会先让 LLM 做三件事,而不只是总结:
- 判断这篇文章值不值得读。让它输出一个“推荐指数”和“适合人群”,避免你在无关文章上浪费时间。
- 抽取关键信息。比如一篇工具发布博文,让 LLM 输出:核心功能、安装方式、适用版本、主要坑点、作者的结论。这样你不用再回头扫读原文。
- 整理链接资源。页面上有一堆参考链接,传统浏览器把它们展示为蓝色下划线文本,你需要一个个点开看。LLM 可以根据链接锚文本和上下文,按类别整理成清单,比如“官方文档”“原文引用”“相关项目”。
这个转变的本质是:你不是在用浏览器“看网页”,而是在用 LLM“替你读网页”。你在 Emacs 里看到的不是一个渲染后的页面,而是几个人的判断和结论。
3.2 从网页提取可操作事项:让内容接入 org-mode
阅读技术博客经常带着一个目的:这篇文章里有没有我能直接用到的东西?比如一个新的命令行参数、一段配置、一个修复 bug 的方法、一个值得跟进的项目。这些内容散落在页面各处,手动提取很慢,而且容易漏。
LLM 可以做结构化提取。假设你面对一篇关于某个新工具的评测文章,你可以让 LLM 输出:
## 核心结论 - 定位:... - 适合场景:... - 不适合场景:... ## 使用方式 - 安装命令:... - 最小配置:... - 常用参数:... ## 潜在风险 - ...然后用一个函数把这段 Markdown 插入到当前 org 文件里。这样你积累的不是“网页收藏夹”,而是一份可检索、可执行的工作笔记。Emacs 最强的地方本就是文本管理,LLM 把网页内容变成高质量文本后,Emacs 的所有工具都能继续发挥作用。
3.3 批量处理一组网页:从“单页消费”到“主题研究”
单页总结仍然偏轻。真正能体现 LLM 价值的是批量场景:你有一组相关页面,想让 LLM 帮你归纳多个来源的异同,或者提取多个页面里的共同要点。
比如你正在调研“当前有哪些本地 LLM 推理方案”,你先用 EWW 或外部脚本收集十几篇相关页面,然后对每一页调用一次 LLM,得到每页的结构化笔记,最后再把多份笔记合成一个对比表格。这个流程手动做会非常累,但把单页链路跑通后,批量只是循环和错误处理的问题。
批量处理需要注意三点:
- 请求之间要控速,不要一上来就把所有页面并发请求。
- 每一页的 LLM 结果要单独保存,避免一次失败导致全部重来。
- 批量任务要先跑一两条样例,确认输出格式稳定后再全量执行。
用 Emacs 做这件事的优势是:你可以把批量脚本和结果文件全部放在同一个环境中,从网页拉取到最终笔记不需要切换应用。
4. 本地模型 vs 云端 API:先选对工具链,再谈效果
4.1 两类方案的核心区别
把 LLM 接入 Emacs 浏览器,你可以走两条完全不同的技术路线:
一个是调用云端 API。优点是省事,不需要准备显卡或推理环境,模型能力通常更强。代价是页面内容要发到外部服务,涉及隐私问题;如果阅读量很大,成本也需要关注。
另一个是在本地跑模型服务。优点是可以处理敏感内容,数量不限,也不依赖网络。代价是硬件要求高,模型能力可能弱一些,部署和升级需要维护。
这里不针对具体供应商,只从通用策略上做判断:
- 如果只是自己阅读技术文章,不涉及隐私,可以先从云端 API 开始,跑通链路再说。
- 如果你读的是内部文档、公司代码库说明、未公开资料,就更适合本地模型。
- 如果你希望在批量任务中长期跑,本地模型往往更划算,但要接受输出质量可能低于云端大模型的事实。
4.2 一个判断表格:从四个维度做选择
| 维度 | 本地模型 | 云端 API |
|---|---|---|
| 部署复杂度 | 较高,需要安装推理框架、下载模型、管理显存 | 较低,注册服务即可调用 |
| 隐私安全 | 数据不出本机,适合敏感内容 | 内容会发送到服务端,需要评估 |
| 长期成本 | 硬件投入为主,跑量大后更可控 | 按调用量计费,长期高频使用成本可能明显 |
| 输出稳定性 | 依赖模型参数和硬件,效果波动更大 | 通常更强,但不同服务差异很大 |
如果你的目标是先快速看到效果,一条更稳妥的路径是:
- 先用一个可用的 API 把 EWW → LLM 链路跑通。
- 确认流程稳定后,再评估要不要迁移到本地模型。
- 迁移时先在一台本地机器上部署小规模服务,用两三条样本对比输出质量。
- 只有本地模型输出达到你能接受的程度,才建议正式切换。
不要一开始就在工具链上纠结太久。这个项目真正的工作量在流程、清洗和长期维护,不在“选哪家模型”。
4.3 面向团队场景:API 和共享工具的边界
如果你的场景不止个人使用,而是想让团队里的其他人也能用同一套“网页 → LLM 笔记”的能力,情况会复杂一些。Emacs 本身有一定使用门槛,直接让团队每个人都配一套 Emacs 环境并不现实。常见的折中方案是:
- 保留 Emacs 侧的脚本,给熟悉 Emacs 的成员使用。
- 同时把整理后的结果放入一个共享的应用层,比如使用支持网页访问的 LLM 工具站,让不熟悉 Emacs 的人也能检索和阅读同样的笔记。
具体工具选型会根据团队已有的技术栈变化,这里不展开某一家产品的搭建细节。重点是:Emacs + LLM 很适合个人钻研,但团队推广时,需要有比 Emacs buffer 更容易访问的出口。
5. 落地时最容易踩的几个坑与排查路径
5.1 输入超长:不是所有页面都能完整塞进模型
EWW 渲染后的页面内容可能很长,尤其是一篇长博客或被拍平的论坛帖子。直接截断到前 4000 个字符虽然简单,但很可能把正文最核心的部分丢掉,LLM 的总结质量会明显下降。
更稳妥的做法是把页面按段落或标题拆成若干块,分多次调用,再把结果合成。实际使用中,我会先做一个“正文提取”步骤,也就是先把可能的导航、评论、页脚去掉,再进入 LLM。这个预处理不一定严格,但能显著降低 token 消耗。
也可以利用 EWW 的 buffer 特点:先通过fill-paragraph或正则把连续空白压缩,再按段落边界切分。判断是否超出上下文,以“字符数约为 4000 到 6000 之间”作为常见经验值,具体要看模型最大上下文。
5.2 请求失败:先定位是哪一层出了问题
把 LLM 接入 Emacs 后,最常见的错误就是请求失败。排查时应按由外到内的顺序:
- 先用 curl 直接调接口,确认服务是否活着。
- 再检查 Emacs 发出的 payload 是否合法,特别是双引号和反斜杠有没有转义。
- 然后看模型名称是否匹配,很多服务对模型名有严格校验。
- 最后看响应内容是否被正确解析,正则提取容易失败,更稳妥的方式是用 JSON 解析库。
整个链路的排查顺序大致是:网络 → 接口 → 参数 → 返回格式 → 展示逻辑。不要第一步就去改 Lisp 代码。
5.3 输出不稳定:用格式约束和温度参数收敛结果
LLM 输出本身具有随机性。做网页总结时,最怕的是返回格式时好时坏:一会儿 Markdown,一会儿纯文本,一会儿还给了一段无关的话。这不是 Emacs 的问题,而是提示词和参数设计不够稳。
可以这样做:
- 在提示词中明确输出格式,甚至给出示例输出模板。
- 把采样温度调低,比如 0.2 到 0.4,降低随机性。
- 在代码中增加格式校验,如果返回内容不是预期结构,就重试一次。
- 对重试次数做上限,避免无限循环。
这里特别想提醒:不要因为一次输出不稳定就否定整个方案。LLM 应用普遍需要调提示词和参数,Emacs 集成只是把你的工作流放到了更适合反复调试的环境里。
5.4 一个可复用的故障排查顺序
如果你要在自己环境里长期跑这套方案,建议把下面这个顺序记下来,作为标准排查链路:
- 先看现象:是完全没有输出,还是输出为空,还是输出格式错误?
- 再看输入:当前 EWW 页面的 buffer 内容是否正常,有没有乱码、截断或过大。
- 再看请求:用 curl 手工构造同样的请求,确认服务端有没有正常返回。
- 再看解析:确认响应 JSON 被正确读出,返回字段名是否匹配。
- 再看流程:如果所有单步都正常,再检查函数之间的变量传递有没有断掉。
这个顺序适用于绝大多数“Emacs 调用外部服务”的场景,不只是 LLM。
6. 把网页浏览从“视觉消费”变成“语义工作”
6.1 从 EWW 到“LLM 原生浏览器”的演进方向
这个项目真正值得关注的,不是某一段 Lisp 代码,而是它代表的一种产品方向:浏览器不再只是让你看网页,而是帮你理解网页。传统浏览器花大量精力做像素级渲染,但在信息过载的时代,用户需要的是“内容优先”的阅读体验。
EWW 原本只做了文本化,但它天然适合承接这一层变化。当你在 EWW 里打开页面时,内容已经是一段可编辑文本;LLM 加进来后,这段文本可以被重排、精简、结构化。未来类似的思路可能扩展到更完整的浏览器生态里,比如在后台自动生成摘要、自动提取操作步骤、自动把文章转换成可问答的知识库。
Emacs 的作用不只是编辑器,也是一个“可编程的信息处理平台”。LLM 本身是一个无状态的文本接口,而 Emacs 可以为它提供上下文、历史记录、任务列表和组织方式。这种组合的价值远大于“在一个网页里弹出一个 AI 对话框”。
6.2 对个人知识库和研究流程的实际影响
如果你平时依赖 org-roam、org-mode 或 Markdown 管理知识,这套方案能改变你的阅读习惯。过去你需要在浏览器和笔记应用之间不断切换:看到一篇文章,复制关键段落,回到编辑器整理。现在你可以在 Emacs 内部完成从浏览到总结再到存储的全过程,而且总结之后的文本可以继续被检索和复用。
长期积累下来,你得到的不再是几十个浏览器书签,而是一批有来源、有摘要、有判断的知识条目。LLM 的总结不一定完美,但它给了你一个初始版本,你只需要校对和补充,而不是从零开始整理。
6.3 现阶段最重要的边界
最后必须说清楚适用边界。Emacs + LLM 的方案非常适合:
- 技术博客、文档、论文、新闻等以文本为主的页面。
- 需要快速判断文章是否值得深读的场景。
- 需要把网页内容转成笔记或任务的研究流程。
- 愿意花时间调试 Emacs 环境和个人工作流的用户。
它不适合:
- 需要完整视觉还原的页面。
- 大量依赖 JavaScript 交互的应用。
- 希望浏览器自己完成所有判断、完全不检查 LLM 输出的场景。
- 追求零配置开箱即用的用户。
换句话说,这套方案并不是要让 Emacs 变成一个现代浏览器。它要做的是让 Emacs 环境里的“阅读”和“知识管理”变得前所未有的顺畅。你仍然可以保留 Chrome 处理复杂页面,但当你认真读一篇文章、做一个调研、沉淀一份笔记的时候,Emacs + LLM 会是一个比传统浏览器更有深度的选择。
真正的 Great Again,不是让 Emacs 浏览器去比拼渲染速度,而是让它回到自己最擅长的事情上:把网页当成文本,把文本变成知识。