构建RSS转电子墨水报系统:从信息聚合到自动化推送的完整实践

这类项目最值得先看的不是功能列表,而是它到底解决了什么实际问题。很多人收藏了一堆RSS订阅,但最后还是用手机刷信息流,因为打开阅读器这个动作本身就有门槛。把RSS内容做成电子墨水报,核心价值在于 被动接收 低干扰阅读 ——信息像一份真正的报纸一样,定时、自动地呈现在一个不伤眼、不弹通知的设备上,帮你从主动“刷”信息,回归到被动“收”信息。

它适合两类人:一是信息过载,想减少手机使用时间的人;二是喜欢RSS这种自主订阅模式,但希望阅读体验更专注、更护眼的极客或文字工作者。最关键的能力不是显示技术,而是 自动化流程 :如何把分散的订阅源聚合、格式化,并推送到一个常显的电子墨水屏设备上。

下面我会按实际把一个想法落地成可运行系统的顺序,拆解整个过程。我会假设你有一台基础的Linux服务器(树莓派或云主机)和一个支持刷机的电子墨水屏设备(如Kindle、文石,或专门的墨水屏显示器)。

1. 先想清楚:你的“墨水报”需要哪些核心环节

在动手写代码或配置工具前,得先把数据流理清楚。一个完整的“RSS转墨水报”系统,通常包含四个环节,缺一不可:

  1. 信息获取与聚合 :从多个RSS订阅源抓取最新内容。
  2. 内容处理与格式化 :清洗HTML、提取正文、统一排版,转换成适合墨水屏显示的格式(如纯文本、简单HTML或PDF)。
  3. 推送与同步 :将处理好的“报纸”文件发送到墨水屏设备。
  4. 设备展示 :墨水屏设备接收文件并刷新显示。

每个环节都有多种技术选型,你的选择决定了系统的复杂度和维护成本。

1.1 环节一:选对信息聚合器,别从零开始写爬虫

自己写RSS爬虫解析各种网站格式是个无底洞。直接用成熟的RSS阅读器后端或库,是更稳妥的选择。

  • 方案A:使用在线RSS服务(如Feedbin、Inoreader)的API

    • 优点 :稳定、省心。这些服务已经帮你处理了订阅源抓取、去重、内容缓存和API输出。
    • 缺点 :通常是付费服务,且你的数据在第三方。
    • 关键点 :调用它们的API获取文章列表和内容。你需要一个API Key。
  • 方案B:自建RSS聚合服务(如FreshRSS、Miniflux)

    • 优点 :数据完全自主,免费,可定制性强。
    • 缺点 :需要自己维护一个服务,处理订阅源的更新和解析。
    • 关键点 :在服务器上通过Docker或包管理工具安装。之后,你的“墨水报”程序就从这个自建服务的数据库或API里取数据。
  • 方案C:使用本地RSS库(如Python的 feedparser

    • 优点 :最轻量,不依赖外部服务,适合订阅源不多、变化不频繁的场景。
    • 缺点 :需要自己处理网络请求、错误重试、内容缓存,稳定性需要更多代码保障。
    • 关键点 :写一个定时脚本,循环遍历你的订阅源URL列表,用 feedparser 解析。

我的建议 :如果只是个人使用,订阅源在几十个以内,从 方案C 开始最简单。如果想一劳永逸,并且不介意付费, 方案A 的Feedbin是非常好的选择,它的API清晰稳定。 方案B 适合技术控和隐私要求极高的用户。

1.2 环节二与三:格式转换与推送,决定最终体验

墨水屏设备千差万别,推送方式决定了你每天要花多少时间在维护上。

  • 格式选择

    • 纯文本 (.txt) :兼容性最强,任何能显示文本的设备都支持。但会丢失所有格式和图片。
    • HTML单文件 :可以保留简单的排版(标题、段落、列表)。需要墨水屏设备的内置浏览器支持。
    • PDF :排版固定,跨设备显示效果一致。但生成PDF对服务器资源要求稍高,且文件体积大。
    • 图片 :将排版好的内容渲染成图片(如PNG)。兼容性极好,但无法选中文字,且文件体积最大。
  • 推送方式

    • 邮件推送 :最通用。让墨水屏设备(如Kindle)绑定一个专属邮箱,服务器端将生成的“报纸”作为附件发送到这个邮箱,设备会自动收取并显示。这是与Kindle配合的经典方案。
    • Web服务器同步 :在服务器上运行一个简单的HTTP服务,提供“报纸”文件的下载链接。在墨水屏设备上写一个脚本,定时去拉取这个文件。这需要你的墨水屏设备能运行脚本(如刷了Kobo系统的设备)。
    • 物理传输 :通过USB连接设备,用脚本自动拷贝文件。这要求设备一直连着服务器,不适合远程场景。

我的建议 邮件推送 + PDF格式 是平衡效果和便利性的最佳组合。PDF能保证排版,邮件推送几乎支持所有带3G/4G或Wi-Fi的电子书阅读器,实现真正的“云端推送,自动接收”。

2. 环境准备:搭建一个能7x24小时运行的核心服务器

你的“编辑部”需要一台不关机的服务器。树莓派放在家里,或者一台最基础的云服务器(如腾讯云/阿里云的轻量应用服务器)都可以。

2.1 基础系统与包管理

以Ubuntu/Debian系统为例。首先确保系统更新,并安装必要的工具。

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip git curl wget

如果你在macOS上开发,可能会用到Homebrew来安装一些依赖。但在生产服务器上,通常直接用系统包管理器( apt )或Python的 pip 就够了。Homebrew主要用于macOS桌面环境,不要在生产Linux服务器上强行安装。

2.2 核心Python环境与依赖

我们的处理脚本主要用Python写。建议使用虚拟环境隔离依赖。

# 安装虚拟环境工具
sudo apt install -y python3-venv

# 创建项目目录并进入
mkdir ~/rss2paper && cd ~/rss2paper

# 创建Python虚拟环境
python3 -m venv venv

# 激活虚拟环境
source venv/bin/activate

激活后,命令行提示符前会出现 (venv) 字样。接下来安装核心Python库:

pip install feedparser beautifulsoup4 requests reportlab markdown
  • feedparser : 用于解析RSS/Atom订阅源。
  • beautifulsoup4 : 用于从HTML内容中清洗和提取纯文本或特定内容。
  • requests : 用于网络请求(如果某些订阅源需要)。
  • reportlab : 一个强大的PDF生成库。
  • markdown : 如果你希望支持Markdown格式的订阅源,可以用它来转换。

2.3 邮件服务配置(如果选择邮件推送)

你需要一个发件邮箱。 强烈建议使用专门的邮件发送服务(如SendGrid、Mailgun)或你的域名邮箱服务商提供的SMTP ,而不是直接用个人QQ、163邮箱的SMTP,后者容易被封且有限制。

以配置SendGrid为例(它有免费额度):

  1. 注册SendGrid账号,创建一个API Key。
  2. 在服务器上,不需要额外安装软件,Python的 smtplib email 库是内置的。你只需要在脚本里配置好SMTP服务器地址、端口、账号和API Key(或密码)即可。

3. 核心实现:从RSS到PDF报纸的生成脚本

这是整个系统的“排版车间”。我们采用 方案C(本地 feedparser ) + PDF格式 + 邮件推送 的路径来演示。

3.1 第一步:编写RSS抓取与内容提取脚本

创建一个文件 fetch_rss.py

#!/usr/bin/env python3
import feedparser
from bs4 import BeautifulSoup
import html2text
from datetime import datetime
import time

# 配置你的RSS订阅源列表
FEED_URLS = [
    'https://rsshub.app/zhihu/daily',
    'https://sspai.com/feed',
    'https://feeds.feedburner.com/ruanyifeng',
    # 添加更多你喜欢的订阅源
]

def fetch_feeds():
    all_articles = []
    h = html2text.HTML2Text()
    h.ignore_links = False
    h.body_width = 0  # 不自动换行

    for url in FEED_URLS:
        print(f"正在抓取: {url}")
        try:
            feed = feedparser.parse(url)
            if feed.entries:
                for entry in feed.entries[:5]:  # 每个源只取最新5条,避免报纸过长
                    # 提取标题、链接、发布时间和内容
                    title = entry.get('title', '无标题')
                    link = entry.get('link', '#')
                    published = entry.get('published_parsed', entry.get('updated_parsed', None))
                    if published:
                        pub_date = time.strftime('%Y-%m-%d %H:%M', published)
                    else:
                        pub_date = '日期未知'

                    # 处理内容,优先使用summary或description
                    content_html = entry.get('summary', entry.get('description', ''))
                    # 使用BeautifulSoup清理一些不必要的标签
                    soup = BeautifulSoup(content_html, 'html.parser')
                    for tag in soup(['script', 'style', 'iframe', 'ins', 'ads']):
                        tag.decompose()
                    cleaned_html = str(soup)
                    # 将HTML转换为纯文本,保留基本格式
                    content_text = h.handle(cleaned_html)

                    article = {
                        'title': title,
                        'link': link,
                        'date': pub_date,
                        'source': feed.feed.get('title', url),
                        'content': content_text[:1000]  # 限制内容长度,避免PDF过大
                    }
                    all_articles.append(article)
        except Exception as e:
            print(f"抓取 {url} 时出错: {e}")
        time.sleep(1)  # 礼貌性延迟,避免请求过快

    # 按发布时间排序(最新的在前)
    all_articles.sort(key=lambda x: x['date'], reverse=True)
    return all_articles

if __name__ == '__main__':
    articles = fetch_feeds()
    print(f"共抓取到 {len(articles)} 篇文章。")
    # 这里可以先打印几篇看看效果
    for a in articles[:2]:
        print(f"\n---\n来源: {a['source']}\n标题: {a['title']}\n日期: {a['date']}\n")

运行这个脚本 python fetch_rss.py ,确认能正常抓取到文章标题和部分内容。 这是第一个验证点 :确保你的订阅源地址有效,网络通畅,并且 feedparser 能正确解析。

3.2 第二步:编写PDF生成脚本

创建一个文件 generate_pdf.py

#!/usr/bin/env python3
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER
from reportlab.lib import colors
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from datetime import datetime
import fetch_rss  # 导入我们刚才写的抓取模块

# 注册中文字体(关键!否则中文显示为空白)
# 你需要准备一个中文字体文件,如`simsun.ttc`(宋体),放到项目目录下
try:
    pdfmetrics.registerFont(TTFont('SimSun', 'SimSun.ttf'))
    CHINESE_FONT = 'SimSun'
except:
    print("警告:未找到中文字体,将使用默认字体,中文可能显示异常。")
    CHINESE_FONT = 'Helvetica'

def create_paper(articles, filename='daily_paper.pdf'):
    doc = SimpleDocTemplate(filename, pagesize=A4,
                            rightMargin=72, leftMargin=72,
                            topMargin=72, bottomMargin=72)

    styles = getSampleStyleSheet()
    # 创建自定义样式
    styles.add(ParagraphStyle(name='ChineseTitle',
                               parent=styles['Heading1'],
                               fontName=CHINESE_FONT,
                               fontSize=16,
                               spaceAfter=12,
                               alignment=TA_CENTER))
    styles.add(ParagraphStyle(name='ChineseSource',
                               parent=styles['Heading2'],
                               fontName=CHINESE_FONT,
                               fontSize=10,
                               textColor=colors.grey,
                               spaceAfter=6))
    styles.add(ParagraphStyle(name='ChineseBody',
                               parent=styles['BodyText'],
                               fontName=CHINESE_FONT,
                               fontSize=10,
                               leading=14,  # 行距
                               spaceAfter=12,
                               alignment=TA_LEFT))

    story = []
    # 报纸头版标题
    today = datetime.now().strftime('%Y年%m月%d日')
    story.append(Paragraph(f"每日墨水报<br/>{today}", styles['ChineseTitle']))
    story.append(Spacer(1, 24))

    for i, article in enumerate(articles):
        # 文章标题
        story.append(Paragraph(article['title'], styles['ChineseTitle']))
        # 来源和日期
        source_line = f"来源: {article['source']} | 发布时间: {article['date']}"
        story.append(Paragraph(source_line, styles['ChineseSource']))
        # 文章内容
        # 将纯文本中的换行符替换为HTML的<br/>,以便Paragraph识别
        content_with_br = article['content'].replace('\n', '<br/>')
        story.append(Paragraph(content_with_br, styles['ChineseBody']))
        story.append(Spacer(1, 12))
        # 每5篇文章后分页,避免单篇文章跨页太多
        if (i + 1) % 5 == 0:
            story.append(PageBreak())

    doc.build(story)
    print(f"PDF报纸已生成: {filename}")
    return filename

if __name__ == '__main__':
    articles = fetch_rss.fetch_feeds()
    if articles:
        pdf_file = create_paper(articles)
        print(f"成功生成报纸,包含 {len(articles)} 篇文章。")
    else:
        print("未抓取到任何文章,无法生成报纸。")

关键点

  1. 中文字体 :这是PDF生成最大的坑。ReportLab默认不支持中文。你必须将一个中文字体文件(如 .ttf .ttc )放在项目目录,并在代码中注册。你可以从系统字体目录(如 /usr/share/fonts )复制一个,或使用开源字体。
  2. 内容长度 :在 fetch_rss.py 中我们对文章内容做了截断( content_text[:1000] ),这是为了防止某篇文章过长导致PDF体积爆炸。你可以根据需求调整。
  3. 样式调整 leading (行距)、 spaceAfter (段后间距)等参数需要根据你的墨水屏尺寸和分辨率微调,以达到最佳阅读效果。

运行 python generate_pdf.py ,如果一切正常,会在当前目录生成一个 daily_paper.pdf 文件。 这是第二个验证点 :用PDF阅读器打开,检查中文是否正常显示,排版是否清晰。

3.3 第三步:编写邮件推送脚本

创建一个文件 send_email.py

#!/usr/bin/env python3
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
import os
from datetime import datetime

# ====== 配置区 ======
SMTP_SERVER = 'smtp.sendgrid.net'  # 以SendGrid为例
SMTP_PORT = 587
SENDER_EMAIL = 'your_sender_email@yourdomain.com'  # 发件人邮箱
SENDER_PASSWORD = 'your_sendgrid_api_key'  # 这里是API Key,不是邮箱密码
RECEIVER_EMAIL = 'your_kindle_email@kindle.com'  # 你的Kindle接收邮箱
# ====================

def send_paper_via_email(pdf_file_path):
    if not os.path.exists(pdf_file_path):
        print(f"错误:PDF文件不存在 {pdf_file_path}")
        return False

    msg = MIMEMultipart()
    msg['From'] = SENDER_EMAIL
    msg['To'] = RECEIVER_EMAIL
    today = datetime.now().strftime('%Y-%m-%d')
    msg['Subject'] = f'每日墨水报 {today}'

    # 邮件正文
    body = f"""
    您好,

    这是您订阅的每日墨水报 ({today}),已生成PDF附件。

    此邮件由自动化系统发送,请勿直接回复。
    """
    msg.attach(MIMEText(body, 'plain'))

    # 添加PDF附件
    with open(pdf_file_path, 'rb') as f:
        pdf_attachment = MIMEApplication(f.read(), _subtype='pdf')
        pdf_attachment.add_header('Content-Disposition', 'attachment',
                                   filename=os.path.basename(pdf_file_path))
        msg.attach(pdf_attachment)

    try:
        server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        server.starttls()  # 启用TLS加密
        server.login(SENDER_EMAIL, SENDER_PASSWORD)
        server.send_message(msg)
        server.quit()
        print(f"邮件已成功发送至 {RECEIVER_EMAIL}")
        return True
    except Exception as e:
        print(f"邮件发送失败: {e}")
        return False

if __name__ == '__main__':
    # 假设PDF文件名为 daily_paper.pdf
    pdf_file = 'daily_paper.pdf'
    if os.path.exists(pdf_file):
        send_paper_via_email(pdf_file)
    else:
        print("请先运行 generate_pdf.py 生成PDF文件。")

关键配置

  1. SENDER_EMAIL , SENDER_PASSWORD , RECEIVER_EMAIL 替换成你自己的信息。
  2. SENDER_PASSWORD 对于SendGrid是API Key,对于其他服务商可能是专用密码或授权码。
  3. Kindle用户注意 :你需要将发件邮箱( SENDER_EMAIL )添加到亚马逊Kindle的“已认可的发件人电子邮箱列表”中,否则Kindle不会接收附件。这个设置在亚马逊官网的“管理我的内容和设备” -> “首选项” -> “个人文档设置”里。

运行 python send_email.py 测试邮件发送。 这是第三个验证点 :检查你的邮箱(或Kindle)是否成功收到带附件的邮件。

4. 自动化与部署:让系统自己运转起来

手动运行三个脚本太麻烦。我们需要一个总控脚本,并用系统的定时任务让它每天自动执行。

4.1 创建总控脚本

创建一个文件 main.py ,按顺序调用所有功能:

#!/usr/bin/env python3
import subprocess
import sys
import os
from datetime import datetime

def run_script(script_name):
    """运行指定的Python脚本,并检查其返回值"""
    try:
        result = subprocess.run([sys.executable, script_name], check=True, capture_output=True, text=True)
        print(f"[OK] {script_name} 执行成功。")
        print(result.stdout)
        if result.stderr:
            print(f"[STDERR] {result.stderr}")
        return True
    except subprocess.CalledProcessError as e:
        print(f"[FAILED] {script_name} 执行失败,返回码: {e.returncode}")
        print(f"标准输出:\n{e.stdout}")
        print(f"标准错误:\n{e.stderr}")
        return False

def main():
    print(f"=== 开始生成每日墨水报 {datetime.now().strftime('%Y-%m-%d %H:%M')} ===")

    # 1. 抓取RSS
    if not run_script('fetch_rss.py'):
        print("RSS抓取失败,终止流程。")
        return

    # 2. 生成PDF
    if not run_script('generate_pdf.py'):
        print("PDF生成失败,终止流程。")
        return

    # 3. 发送邮件
    pdf_file = 'daily_paper.pdf'
    if os.path.exists(pdf_file):
        # 这里直接导入发送模块的功能
        from send_email import send_paper_via_email
        if send_paper_via_email(pdf_file):
            print("=== 流程执行完毕 ===")
        else:
            print("邮件发送失败。")
    else:
        print(f"错误:未找到PDF文件 {pdf_file}")

if __name__ == '__main__':
    main()

4.2 设置Linux定时任务(Cron)

我们希望每天早晨7点自动生成并发送报纸。

  1. 首先,确保你的总控脚本有执行权限: chmod +x main.py

  2. 编辑当前用户的cron任务: crontab -e

  3. 在打开的编辑器中添加一行(假设你的项目在 /home/pi/rss2paper , 且使用虚拟环境):

    0 7 * * * cd /home/pi/rss2paper && /home/pi/rss2paper/venv/bin/python /home/pi/rss2paper/main.py >> /home/pi/rss2paper/cron.log 2>&1
    
    • 0 7 * * * 表示每天7:00执行。
    • cd /home/pi/rss2paper 切换到项目目录。
    • && /home/pi/rss2paper/venv/bin/python 使用虚拟环境中的Python解释器。
    • >> /home/pi/rss2paper/cron.log 2>&1 将脚本的所有输出(包括错误)追加到日志文件,方便排查。
  4. 保存并退出。Cron会自动加载新任务。

验证定时任务 :你可以将时间改为几分钟后,或者手动运行 python main.py 来测试整个流程。查看生成的 cron.log 文件,确认每一步都执行成功。

5. 进阶优化与问题排查

系统跑起来只是第一步,长期稳定运行需要处理更多细节。

5.1 内容优化:让“报纸”更易读

  • 去重 :不同订阅源可能转载同一篇文章。可以在 fetch_rss.py 中根据文章标题或链接进行简单的去重。
  • 摘要生成 :对于过长的文章,可以尝试用简单的算法(如提取前N句)或调用摘要API(注意API调用成本和稳定性)生成摘要,而不是粗暴截断。
  • 分类排版 :在生成PDF前,将文章按来源或你定义的分类(科技、生活、财经)分组,在报纸中分栏或分章节显示。
  • 添加目录 :如果文章很多,可以在PDF开头生成一个带页码的目录。

5.2 稳定性保障:应对网络波动和失败

  • 重试机制 :在 fetch_rss.py 的抓取环节,对每个订阅源添加重试逻辑(如 try...except 包裹,失败后重试2次)。
  • 超时设置 :使用 requests 库时,为请求设置超时(如 timeout=10 ),避免某个源卡住整个流程。
  • 失败通知 :如果整个流程失败,可以发送一封报警邮件到你的个人邮箱,而不是Kindle。可以在 main.py try...except 最外层捕获异常,并调用一个发送错误邮件的函数。
  • 日志记录 :除了cron的日志,在关键步骤(开始抓取、抓取成功/失败、生成PDF、发送邮件)添加更详细的日志记录到单独的文件,方便追溯。

5.3 设备端适配:不只是Kindle

  • 其他电子书阅读器 :如果设备不支持邮件推送,但支持Wi-Fi和脚本(如刷了Kobo系统的设备),可以在设备上写一个简单的Shell脚本,定时从你的服务器Web服务(可以用Python的 http.server 快速搭建)下载PDF文件,并用内置阅读器打开。
  • 墨水屏显示器/相框 :这类设备通常运行完整Linux系统。你可以将生成PDF的步骤直接放在设备上,或者让设备从服务器拉取渲染好的图片。需要根据设备的具体API或显示方式进行适配。
  • 纯文本显示 :对于只能显示文本的极简设备,可以跳过PDF生成,直接生成一个 .txt 文件,通过 scp rsync 同步到设备上。

5.4 常见问题排查清单

当你的“墨水报”没有按时出现时,按这个顺序检查:

  1. 看日志 :首先检查 cron.log 文件。错误信息通常就在这里。
  2. 查权限 :确认cron任务使用的用户有权限执行脚本、写入当前目录、访问网络。
  3. 验环境 :在cron任务命令中,Python路径和虚拟环境激活是关键。最稳妥的方法是,在cron中直接使用虚拟环境Python的绝对路径(如我们上面做的),而不是先 source activate
  4. 测网络 :手动在服务器上运行 python fetch_rss.py ,看是否能正常抓取订阅源。可能是服务器网络问题,或某个订阅源地址失效。
  5. 查邮件 :登录你的发件邮箱SMTP服务商后台(如SendGrid控制台),查看邮件发送日志,是否有被拒绝、退信等情况。
  6. 查订阅源 :个别订阅源结构变化可能导致 feedparser 解析失败。尝试单独解析那个源的URL,检查返回的数据结构。
  7. 查存储 :服务器磁盘是否满了?导致无法生成PDF。

这个方案的优势在于,所有组件都是可控、可修改的。你可以从最简单的文本PDF开始,逐步增加分类、摘要、更好的排版,甚至加入天气预报、待办事项等内容,打造一份真正属于你自己的个性化数字报纸。它最大的回报不是省下了看手机的几分钟,而是重新找回了对信息流的主动权。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值