
很多人第一次接触 kindle 推送脑子里想的都是「不就是发封邮件带个附件吗」结果折腾一晚上附件发出去了设备上什么都没有。问题几乎从来不出在「发送」这个动作上而是出在发送之前和之后的那几段链路上发件邮箱有没有被信任、收件地址拼没拼对、附件是什么容器、设备这会儿有没有联网同步。而「推送网页到 kindle」这件事难点又往前挪了一步——网页本身不是文件你得先决定用什么办法把它变成 Kindle 认得的东西再谈怎么送过去。我把这套东西拆成两条线来看一条是通道也就是推送权限和地址体系怎么配另一条是内容也就是一篇网页如何变成排版能看的文档。通道配错内容再好也到不了通道通了但内容随便塞读起来就是灾难。下面按我自己反复折腾出来的顺序讲从链路原理、后台设置、四条网页转换路径、一条可以完全自动化的流水线最后落到那些「推送成功了但根本读不下去」的具体坑上。1. 推送链路上的三个角色发件箱、白名单、那串专属地址1.1 一次推送到底经过了谁的手把 Kindle 推送想成寄挂号信会容易很多。你要寄东西得有三个东西同时成立寄件人身份被收件方认可、收件地址准确、包裹本身符合运输规定。对应到 Kindle 上就是三样你用来发信的邮箱、设备或账号的那串专属收件地址、附件本身。专属收件地址的形态通常是「一串字符某后缀」这串字符是系统分配的不是你注册的账号名后缀取决于你的账号在哪个站点可能是 .com、.co.uk、.co.jp 之类。一个账号下可能有多个设备地址也可能有一个「发送到全部设备」的汇总地址。这个设计的意义在于你可以只往平板推不打扰阅读器也可以一次性铺到所有设备。第二样是白名单也就是「已批准的个人文档邮箱列表」。默认情况下任何陌生邮箱往你的专属地址发东西都会被丢掉——这是一个非常必要的防骚扰设计否则你的地址一旦泄露就会被各种订阅内容塞满。代价就是你必须手动把自己的发件邮箱加进去这一步是所有新手卡住的地方。第三样是附件本身。系统对格式、体积、数量都有硬性限制超了会静默失败或者回一封措辞含糊的退信。这三样里任何一样不成立你的推送就会停在半路而且大多数情况下设备端不会有任何提示这是最让人抓狂的部分。1.2 为什么白名单是所有人踩的第一块砖我刚接触这套东西的时候犯的错是把「添加邮箱」和「验证邮箱」当成一回事。实际流程往往是两步先在后台把邮箱地址填进批准列表然后系统可能会往那个邮箱发一封确认信需要点一下链接才算完成。如果你只填了地址没点确认界面看起来是加上了实际推送照样被拒。还有一个更隐蔽的情况你用的是邮箱别名。比如你在邮件客户端里显示的发件人是「昵称 abckindle某域名」但白名单里填的是abc某域名。带加号后缀的别名在部分邮件系统里会被视为不同地址于是匹配失败。我后来的做法是把发出去的那封信的原始邮件头打开看一眼From:字段里的真实地址复制粘贴进白名单一个字都不手改。第三个高频问题是企业邮箱或自建域名的邮箱。这类邮箱发出的信经常在收件方那一侧被判为可疑尤其是你的域名没有配置过发信认证的时候。表现是白名单加对了格式也对了就是收不到。这种时候换一个主流公共邮箱发一次立刻就能验证出问题出在哪边——这也是我排查时最常用的对照组手法。1.3 EPUB、PDF、DOCX 到底该挑哪个容器格式选择不是随手挑的它直接决定你在设备上能不能调字号、能不能重排、图片会不会糊。下面这张表是我这些年用下来的实际感受不是官方文档的复述容器可重排字号可调图片表现适合什么EPUB是是好可自适应宽度网页正文、长文、小说首选DOCX是转换后是一般复杂版式易乱手头只有 Word 稿的临时场景PDF否否只能缩放取决于原文件清晰度表格、公式、扫描件、固定版式HTML是转换后是依赖外链容易丢图快速试验不适合正式归档TXT是是无纯文字草稿、代码片段这里有一个特别容易被忽略的点MOBI 已经不被推荐作为推送格式了早期很多人习惯用 MOBI现在继续用它只会给自己添麻烦。EPUB 是目前最稳的选择它的容器本质是一个压缩包里面有 XHTML、CSS、图片和一个描述清单文件Kindle 侧收到后会做一轮转换再入库。还有一个判断标准如果这篇网页你打算认真读、要划重点、要调字号、晚上躺床上看那就必须走 EPUB。如果只是查一份参数表、一张对照图、一段必须保持原样的排版那就走 PDF。别拿 PDF 去读长篇文字因为固定版式意味着字号放大之后你要左右拖动读三页就想放弃。2. 后台设置实操把通道打通只要四处改动2.1 找到个人文档设置页的位置登录账号之后进「管理我的内容和设备」在「偏好设置」里找「个人文档设置」这一类目。不同站点后台的中文措辞会有差异有的叫「个人文档设置」有的叫「发送至 Kindle」但本质是同一页。这一页里通常集中了三块内容已批准的个人文档邮箱列表、设备专属邮箱地址列表、以及个人文档相关的开关比如是否允许通过邮件发送、是否自动转换。我的建议是进这一页之后先别急着改先把页面完整看一遍把「设备邮箱列表」那一块截图保存。原因很实际——设备邮箱是一串随机字符肉眼抄写极易出错尤其是 l 和 1、O 和 0 这种。我见过不止一个人把地址抄错一个字符然后怀疑是平台问题折腾了两小时。2.2 添加发件邮箱时的三个动作添加这一步我固定做三件事做完基本不会出岔子填完整地址不填别名。不要填「昵称」不要填带加号的分支地址填邮件头里那个最原始的地址。去收件箱找确认邮件并点击。有些站点会发确认信有些不会发了就必须点。这一步不做前面全白费。发一封测试信附件用一个几百 KB 的 TXT。别上来就推一个 20MB 的图文长文来测试失败了你会搞不清是通道问题还是体积问题。测试信的内容我一般写清楚用途比如主题写「测试-通道验证-日期」正文写一句「此信用于验证推送通道无需阅读」。这样做的好处是将来在设备上看到这封信你能立刻判断它是测试残留还是真实内容方便清理。2.3 多个设备地址怎么管如果你手上有阅读器和平板两个设备地址列表里会有两条。我的做法是给每个地址做个备注记在本地文档里格式是「设备名 用途 地址」。别指望后台帮你记得清设备一多必然混乱。有些账号会提供一个汇总地址往它发就等于群发到所有设备。这个地址适合什么场景适合「我自己也不知道今晚会在哪个设备上看」的情况。但它不适合做自动化推送——你每天推十条内容平板和阅读器上就会各留十份重复文件清理起来很烦。提示设备邮箱属于半敏感信息泄露之后别人可以给你推垃圾内容。不要把完整地址贴到公开场合也不要用它当任何网站的注册邮箱。2.4 一封信验证整条链路配置完成后用 SMTP 工具或者普通邮件客户端发一封带 EPUB 附件的信。发完不要马上盯着设备因为同步不是即时的设备需要处于联网且唤醒状态才会去拉取。我的验证顺序是先看发件箱有没有退信几分钟内再看设备上的「文档」分类里有没有新条目几分钟到十几分钟最后看内容排版是否正常。这三步分别对应「发送成功」「投递成功」「转换成功」把三个环节分开看出问题时定位会快很多。如果设备端一直没动静先手动触发一次同步在设备设置里找「同步我的 Kindle」之类的选项点一下。这一步能排除掉「只是还没轮到同步」这种最常见也最冤枉的情况。3. 网页变书四条路径的成本与适用场景对比3.1 路径一浏览器扩展一键直达官方提供的浏览器扩展是成本最低的方案。装好之后在任意网页点一下扩展图标它会把这篇文章的地址和内容提交到你的账号稍后自动出现在设备上。整个过程不需要你配置邮件、不需要下载文件、不需要管格式。它的优点是省事缺点也很明确你没法控制排版。扩展抓的是它认为的正文主体遇到多栏布局、无限滚动、正文被折叠在「展开全文」后面的页面抓出来的东西可能残缺。另外它走的是网页通道不能批量、不能定时、不能做二次处理。我的用法是临时看到一篇想读的短文直接扩展推过去不纠结排版。但凡是我打算认真读、或者要留档的内容一律不走这条路。3.2 路径二手动导出适合图文混排页面遇到排版复杂、图片多、公式多的页面我倾向于手动导出。两种做法打印成 PDF。在浏览器里调用打印功能目标选「另存为 PDF」然后在打印选项里关掉页眉页脚、把纸张设为合适尺寸。这个方法的优点是所见即所得缺点是会把导航栏、侧边栏、评论区一起打进去除非你事先用阅读模式把页面净化一遍。而且在 Kindle 上PDF 的字号是固定的长文读起来很难受。用阅读模式加另存为。大多数现代浏览器都有阅读模式进去之后页面只剩正文和插图这时候再打印成 PDF效果会干净非常多。这是我处理「必须保留原排版」的场景时最常用的手法比如技术文档、带表格的说明页。导出之后优先考虑用转换工具把 PDF 或 HTML 转成 EPUB。命令行工具ebook-convert就够用ebook-convert article.html article.epub \ --title 网页归档-示例标题 \ --authors 归档 \ --language zh \ --level1-toc //h:h1 \ --level2-toc //h:h2--level1-toc和--level2-toc用的是 XPath把标题层级映射成目录结构。这一步做了之后设备上的目录才有内容否则一篇文章只有一个入口长文翻起来很痛苦。3.3 路径三Calibre 抓取与自定义 Recipe如果你有固定的几个栏目想每天看Calibre 的「抓取新闻」功能非常合适。它的本质是你定义一个 Recipe一个 Python 类里面写清楚标题、抓哪些源、每源抓几篇它会定期抓取、拼成一本带目录的电子书。一个最简 Recipe 长这样from calibre.web.feeds.news import BasicNewsRecipe class DailyArchive(BasicNewsRecipe): title 每日网页归档 language zh oldest_article 3 max_articles_per_feed 8 auto_cleanup True feeds [ (栏目甲, https://example.com/feed-a), (栏目乙, https://example.com/feed-b), ]oldest_article是只抓最近几天的内容max_articles_per_feed是每个源的上限auto_cleanup会尝试清理掉页面里的多余元素。把文件放进 Calibre 的 recipe 目录就能在「抓取新闻」的列表里看到它。它的价值在于批量与目录一次抓十几篇拼成一本目录清晰设备上翻起来像一本杂志。它的限制在于Recipe 依赖页面结构站点改版之后经常需要调 CSS 选择器。我自己的经验是Recipe 只适合长期稳定的源不适合一次性抓一篇。3.4 路径四自己写脚本把正文抽出来做成 EPUB这是我最推荐的长期方案因为可控。整条链路的每一步你都知道发生了什么出问题也定位得到。核心就三件事抽正文、内嵌图片、打包成 EPUB。抽正文用readability-lxml它会根据文本密度和标签结构判断哪一块是主体内容把导航、广告、评论区剥掉import requests from readability import Document UA {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} def fetch_article(url: str): resp requests.get(url, headersUA, timeout15) resp.raise_for_status() resp.encoding resp.apparent_encoding doc Document(resp.text) return doc.short_title(), doc.summary(html_partialTrue)short_title()拿的是净化后的标题比title标签干净summary(html_partialTrue)返回的是正文的 HTML 片段而不是完整页面。接下来是打包用ebooklibimport uuid from ebooklib import epub def build_epub(title: str, body_html: str, out_path: str, author归档): book epub.EpubBook() book.set_identifier(str(uuid.uuid4())) book.set_title(title) book.set_language(zh) book.add_author(author) css epub.EpubItem( uidstyle, file_namestyle/main.css, media_typetext/css, contentbody{line-height:1.7;font-size:1em;} img{max-width:100%;height:auto;} h1{font-size:1.3em;line-height:1.4;}, ) book.add_item(css) chap epub.EpubHtml(titletitle, file_namechap_01.xhtml, langzh) chap.content fh1{title}/h1{body_html} chap.add_item(css) book.add_item(chap) book.toc (chap,) book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) book.spine [nav, chap] epub.write_epub(out_path, book)这段代码里我特意加了 CSS。别小看这一点默认行高和图片宽度在 Kindle 上未必舒服max-width:100%能防止宽图把版面撑破行高调大一点阅读疲劳度会明显下降。4. 手写一条流水线从抓正文到自动发信4.1 图片为什么必须内嵌这是最容易被跳过、也最影响观感的一步。readability 抽出来的 HTML 里图片还是原始的外链地址。EPUB 规范里远程资源支持有限Kindle 侧的转换流程通常不会替你去下载这些图结果就是设备上正文有了、图全是空的占位框。解决办法是把图片下载下来作为 EPUB 的资源项加入然后把src改成相对路径import mimetypes from urllib.parse import urljoin from bs4 import BeautifulSoup def inline_images(html: str, base_url: str, book) - str: soup BeautifulSoup(html, html.parser) for i, img in enumerate(soup.find_all(img)): src img.get(src) if not src: img.decompose() continue try: url urljoin(base_url, src) r requests.get(url, headersUA, timeout10) ctype r.headers.get(Content-Type, ).split(;)[0] if r.status_code ! 200 or not ctype.startswith(image): img.decompose() continue ext mimetypes.guess_extension(ctype) or .jpg name fimg_{i:03d}{ext} book.add_item(epub.EpubItem( uidname, file_namefimages/{name}, media_typectype, contentr.content, )) img[src] fimages/{name} except Exception: img.decompose() return str(soup)注意那个img.decompose()。下载失败就把这个img节点整块删掉而不是留一个破图。留破图在设备上会显示成一个奇怪的小方块比没有图还难看。图片体积也要控一下。我一般会把宽度超过 1200 像素的图缩到这个尺寸以内JPEG 质量压到 80 左右。一篇 15 张图的长文这样处理完通常在 2 到 4 MB邮件附件走得很顺如果原图不动动辄十几 MB既慢又容易触到上限。4.2 用 SMTP 把文件送出去发送这一段用标准库的smtplib就够了不需要额外依赖import os, smtplib from email.message import EmailMessage SMTP_HOST smtp.example.com SMTP_PORT 587 # STARTTLS 常用 587SSL 直连常用 465 SENDER meexample.com PASSWORD os.environ[MAIL_PASS] KINDLE_TO your-addresskindle.com def send_to_kindle(path: str, subject: str): msg EmailMessage() msg[From] SENDER msg[To] KINDLE_TO msg[Subject] subject msg.set_content(附件为网页归档正文见文件。) with open(path, rb) as f: msg.add_attachment( f.read(), maintypeapplication, subtypeepubzip, filenameos.path.basename(path), ) with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout30) as s: s.starttls() s.login(SENDER, PASSWORD) s.send_message(msg)几个细节值得说清楚。subtype用epubzip是 EPUB 的标准 MIME 类型用octet-stream有时候也能过但标准类型更稳妥。密码不要写死在脚本里走环境变量。timeout一定要设否则网络抖动时脚本会挂在那里。还有一个老话题早年流行在邮件主题里写convert来触发转换现在这个做法已经不需要了别再把它当成必须的步骤。主题行真正有用的地方是给你自己做标识比如「归档-2024-05-11-三篇」。这一点后面还会讲。4.3 定时任务与失败重试脚本跑通之后接一个定时任务就完成了自动化。Linux 上写 cronWindows 上用任务计划程序本质都是定时执行一条命令。# 每天早上 7 点抓取并推送 0 7 * * * /usr/bin/python3 /opt/kindle_push/daily.py /var/log/kindle_push.log 21我把日志留下来是有原因的推送失败往往是静默的没有日志你根本不知道昨天那篇到底发没发。日志里至少记录三件事抓取的 URL、生成的 EPUB 体积、发送是否抛异常。重试策略我一般设成两次间隔五分钟。因为失败原因大多是临时的网络问题重试一次就过了如果是配置类问题地址错、密码过期重试一百次也没用反而会触发发件方的频率限制。4.4 一个我踩过的文件名坑附件文件名用中文在部分邮件链路里会被重新编码到设备上就变成一串乱码或者问号。表现是文件明明在但标题栏显示得像乱码而且排序全乱。我的做法固定下来了文件名只用 ASCII中文标题写进 EPUB 的元数据里。文件名用日期加序号比如digest_20240511_01.epub标题在set_title()里写中文。设备上显示的是元数据里的标题文件名只在你本地管理时有用两者分开互不干扰。5. 推送成功却读不下去十二个坑的现场排查5.1 先分清是「没送到」还是「送到了不好读」这两类问题的排查路径完全不同混在一起查只会浪费时间。判断方法很简单去设备的「文档」分类里看有没有这条记录。没有记录说明问题在通道侧地址、白名单、格式、体积、同步。有记录但排版难看、图片丢失、目录为空说明问题在内容侧EPUB 结构、图片内嵌、CSS、元数据。我按这个二分法整理了常见情况现象大概率原因处理方向完全没收到也没退信发件邮箱不在批准列表或未点确认回后台核对白名单与确认邮件完全没收到有退信附件超限、格式不支持、地址拼错看退信原文缩体积换格式收到但标题乱码文件名用了非 ASCII文件名改英文标题写元数据收到但图片全是空框图片是外链未内嵌下载图片作为 EPUB 资源加入收到但目录为空没有生成 nav/ncx 或未设 toc补EpubNcx、EpubNav和toc字号调不了推的是 PDF改推 EPUB排版挤成一团缺 CSS行高与图片宽度未约束加基础样式表段落全被合并原文用 div 排版、无 p 标签抽取后做一次标签规整同步很慢设备未联网或未唤醒手动触发同步收到重复多份用了汇总地址群发改成指定单设备地址推送后设备卡顿单文件体积过大压缩图片、拆分长文隔天再看没了被归到其他分类或被清理检查筛选条件与保留策略5.2 体积与数量的边界在哪官方的具体数值会调整我不建议你把某一个数字当成永久真理但可以记住量级邮件附件这一侧单封的附件数量上限在几十个以内总大小在几十 MB 这个量级网页端直接上传的通道单文件上限会宽一些能到百 MB 级别。真正要养成的习惯是「主动控制」而不是「贴着上限走」。我的经验阈值是单个 EPUB 控制在 5 MB 以内一次推送不超过 3 个文件。这个范围内几乎不会遇到任何限制问题而且设备上打开、翻页都很流畅。接近上限的文件即使推送成功在设备上打开也会有明显的等待感尤其是老设备。5.3 内容侧的三个高频问题正文抽多了。readability 不是万能的遇到把正文和评论区放在同一个容器里的页面它会把评论一起抽进来。处理办法是在抽取之后做一次节点裁剪把class或id里含 comment、related、footer 这类关键词的块删掉。粗暴但有效。正文抽少了。有些页面把正文拆成多个分页或者内容靠脚本渲染。前者需要你手动拼页后者在纯静态抓取下拿不到内容。这种情况我一般退回「浏览器阅读模式 打印成 PDF」这条路径比死磕脚本划算。代码块和表格被压平。网页里的pre和table在转换成 XHTML 后如果没有对应样式会失去等宽和边框。加两条 CSS 就能救回来pre{white-space:pre-wrap;font-family:monospace;}和table{border-collapse:collapse;}td,th{border:1px solid #ccc;padding:4px;}。5.4 我的固定排查顺序遇到问题我按这个顺序走基本三轮内能定位打开发件邮箱确认有没有退信读退信原文——它通常直接告诉你原因。核对设备地址逐字符比对重点看 l/1、O/0。核对白名单里的地址与邮件头From:是否完全一致。换一个已知可用的小 TXT 附件测试确认通道本身没问题。通道没问题再查内容侧用本地阅读器打开生成的 EPUB看结构是否正常。最后才怀疑同步手动触发一次同步。这个顺序的逻辑是「从简单到复杂、从外部到内部」。很多人一上来就怀疑自己的脚本写错了改半天代码结果发现是地址里少了一个字符。6. 把推送用成习惯命名、归档与额度6.1 用元数据给阅读队列编码推送用久了设备上的文档会越堆越多找东西变成新问题。我的做法是在元数据里做编码而不是靠文件名。具体是标题写成「来源简称 - 文章标题」作者字段固定写一个统一的值比如「网页归档」。这样在设备上按作者排序所有推送来的内容会聚在一起不会和你买的书混在一块。标题前缀用来源简称同一来源的内容自然排在一起扫一眼就知道哪些还没读。更进一步可以在标题前面加一个状态标记比如未读用一个符号前缀读完改掉。这需要你在推送之后手动维护稍麻烦但比在一堆同名文档里翻找强得多。6.2 定期清理比配置更重要我见过太多人通道配得很漂亮几个月后设备里塞了几百份文档同步变慢、存储告急、找东西靠翻。推送这件事真正的维护成本不在配置在清理。我的节奏是每月一次在后台的「管理我的内容和设备」里按时间排序把已经读完的、过期的批量删掉。删的时候注意区分「个人文档」和「购买的書」别手滑。另外删掉之后设备端需要再同步一次才会消失别以为没删成功又删一遍。6.3 几条不太写在文档里的经验第一先用小文件打通再上真实内容。这条前面说过但值得重复因为它能省掉大量误判。第二发送端和接收端要分开验证。发出去的脚本跑通不代表内容能读内容在本地能读不代表转换后还能读。中间那一轮格式转换是黑盒必须用设备实测。第三别迷信「一键」。所有一键方案都在替你做一个取舍而它做的取舍未必适合你要读的那篇文章。真正稳定的做法是手里同时有「快」和「可控」两条路径日常走快的重要的走可控的。第四给自动化留一条人工通道。我的定时任务只负责常规栏目遇到单篇想读的内容还是手动走一次。全自动听起来很酷但一旦某天抓错了内容推过来一堆无关的东西你会宁愿当初没做自动化。第五留一份本地归档。推送过去的文件在云端有但排版是转换后的。原始 EPUB 留在本地将来想重新推送、想改样式、想合并成合辑都有原料可用。我自己是按月份建目录文件名带日期攒够一批就合并成一本比零散推送好管理得多。这套东西说到底就两层把通道配对上把内容做干净。前者是一次性工作配好之后基本不用再碰后者是长期活儿每篇文章的页面结构都不一样你得准备好随时退回手动路径。我自己的习惯是常规栏目交给脚本遇到结构复杂的页面直接走阅读模式加手动导出不为了省那两分钟去调一个可能只用一次的抓取规则。