基于RSS与电子墨水屏打造个人专属信息终端:从原理到实践
2026/8/21 6:45:20 网站建设 项目流程

之前为了获取信息,每天都要在手机和电脑上打开一堆网站和App,不仅信息流杂乱,还容易被算法推荐和无关推送干扰。后来尝试用RSS订阅来聚合内容源,虽然信息获取效率提升了,但依然需要盯着屏幕,长时间下来眼睛容易疲劳。于是,我萌生了一个想法:能不能把RSS订阅的内容,以一种更护眼、更专注的方式呈现出来?

经过一番折腾,我成功地将RSS订阅源的内容,自动抓取、格式化,并推送到了一个电子墨水屏设备上,做成了一份可以随时翻阅的“电子墨水报”。现在,我只需要像看报纸一样,拿起这个墨水屏设备,就能浏览我订阅的所有新闻和技术博客,彻底摆脱了手机和电脑屏幕的束缚,阅读体验大幅提升。

本文将完整分享这个项目的实现过程,从RSS订阅的原理、电子墨水屏的选择,到核心的API调用、内容抓取与解析,再到最终的排版与自动推送。无论你是想复刻一个类似的个人设备,还是想学习RSS、API调用和自动化脚本的实战应用,都能从本文中找到清晰的步骤和可运行的代码。

1. 项目核心:RSS与电子墨水屏的结合

1.1 为什么选择RSS?

RSS(Really Simple Syndication,简易信息聚合)是一种基于XML格式的Web内容订阅规范。它允许你将多个网站的最新内容聚合到一个阅读器中,实现信息的主动获取,而非被动接受平台算法的推荐。

核心优势:

  • 信息主权回归:你订阅什么,就看到什么,内容源完全由自己控制。
  • 高效聚合:无需逐个打开网站,在一个地方即可浏览所有更新。
  • 无干扰:通常只有标题、摘要和正文,没有广告、无关推荐和复杂排版。

在项目中,RSS是我们获取内容的“源头”。我们会编写程序,定期抓取这些RSS源(也称为Feed)的最新内容。

1.2 为什么选择电子墨水屏?

电子墨水屏(E-ink)通过反射环境光显示内容,其显示效果接近纸质印刷,且仅在刷新时耗电,显示静态内容时不耗电。

作为“报纸”载体的优势:

  • 护眼:无背光、无频闪,长时间阅读不易疲劳。
  • 专注:单色显示、功能单一,能有效避免数字设备的多任务干扰。
  • 低功耗:内容刷新后即可断电,非常适合作为信息展示终端。
  • 常显特性:像真正的报纸一样,信息可以一直停留在屏幕上。

本项目就是将RSS的“内容聚合”优势与电子墨水屏的“显示体验”优势相结合,打造一个专注、护眼的个人信息终端。

1.3 系统架构概览

整个项目的运行流程可以概括为以下几个步骤:

  1. 内容获取:程序通过HTTP请求,定期从你配置的RSS订阅源地址抓取XML格式的数据。
  2. 内容解析:解析XML,提取出文章的标题、发布时间、摘要或全文、链接等关键信息。
  3. 内容处理:对提取的文本进行清洗(如去除HTML标签)、格式化、排序(如按时间倒序),并可能进行摘要生成或翻译。
  4. 排版渲染:将处理好的文本、图片链接等信息,按照报纸的版式(如分栏、标题加粗、添加日期头等)进行排版,生成一张可在电子墨水屏上显示的图片。
  5. 推送显示:将生成的图片通过API、USB或网络等方式,发送到电子墨水屏设备上进行刷新显示。

接下来,我们将从环境准备开始,一步步实现这个系统。

2. 环境与工具准备

本项目主要涉及后端数据处理和硬件驱动,不限定于某一特定编程语言,但为了教程的通用性和易实现性,我们选择使用Python作为主要开发语言。Python拥有丰富的库来支持RSS解析、HTTP请求和图像处理。

2.1 硬件选择

电子墨水屏设备的选择很多,从DIY的开发板到成品设备均可。这里列举几种常见方案:

设备类型代表型号优点缺点适用人群
开发板+屏幕Raspberry Pi + Waveshare e-Paper完全可控,可玩性高,成本相对较低。需要一定的动手能力,需自行组装和保护外壳。极客、硬件爱好者、学生。
墨水屏终端小米生态链“墨案”电纸书、大上科技显示器等即买即用,显示效果好,系统相对开放。成本较高,可能需要破解或利用其开放接口。追求省心、显示效果的用户。
二手电纸书旧的Kindle(需越狱)、文石阅读器等成本极低,屏幕素质好。系统封闭,驱动和开发难度最大,稳定性存疑。喜欢挑战的极客,预算极其有限。

建议:对于大多数开发者,从树莓派(Raspberry Pi)搭配微雪(Waveshare)的电子墨水屏开始是最佳选择。教程将以树莓派+微雪屏幕为例进行说明。请根据你的屏幕型号,前往微雪官方Wiki查看对应的驱动库和接线说明。

2.2 软件与开发环境

  1. 操作系统:树莓派上推荐使用Raspberry Pi OS (Legacy),这是一个稳定的Debian系统。如果你的处理程序运行在云端或另一台电脑上,则可以是任何支持Python的系统(Windows/macOS/Linux)。
  2. Python版本:建议使用Python 3.8 或更高版本
  3. 包管理工具:使用pip安装Python库。
  4. 代码编辑器:任选,如VS Code、PyCharm等。

2.3 核心Python库安装

我们将使用以下几个关键的Python库,请在你的运行环境中安装它们:

# 安装所需的Python库 pip install feedparser # 用于解析RSS/Atom Feed,非常强大且简单 pip install requests # 用于发起HTTP请求,获取Feed原始数据或文章详情 pip install Pillow # Python图像处理库,用于生成最终显示的图片 pip install pytz # 处理时区 pip install schedule # 用于定时任务(可选,也可用cron)

如果你的树莓派需要直接驱动墨水屏,还需要安装屏幕厂商提供的驱动库。以微雪为例,通常需要克隆其GitHub仓库:

# 示例:安装微雪墨水屏的Python驱动库(具体库名请查阅对应屏幕的Wiki) git clone https://github.com/waveshare/e-Paper.git cd e-Paper/RaspberryPi_JetsonNano/python sudo pip3 install .

3. 核心实现步骤详解

3.1 第一步:管理与解析RSS订阅源

首先,我们需要一个地方来管理我们的RSS源列表。创建一个feeds.txtconfig.yaml文件。

示例 (feeds.txt):

# 技术类 https://hnrss.org/frontpage https://blog.codinghorror.com/rss/ https://overreacted.io/rss.xml # 新闻类 http://rss.cnn.com/rss/edition.rss https://rsshub.app/zhihu/daily # 个人博客 https://your-favorite-blog.com/feed

编写RSS抓取与解析脚本 (rss_fetcher.py):

import feedparser import requests from datetime import datetime, timedelta import pytz from typing import List, Dict def fetch_feed(feed_url: str) -> List[Dict]: """ 抓取并解析单个RSS源,返回文章列表 """ articles = [] try: # 使用feedparser解析 feed = feedparser.parse(feed_url) # 检查解析是否成功 if feed.bozo: # bozo标志位表示解析可能出错 print(f"警告:解析Feed时可能出错 - {feed_url}") # 可以记录日志,这里继续尝试处理已解析的内容 for entry in feed.entries: # 提取关键信息,不同Feed结构可能不同,这里做兼容处理 title = entry.get('title', '无标题') # 优先获取摘要,没有则尝试获取内容 summary = entry.get('summary', entry.get('description', '')) link = entry.get('link', '#') # 处理发布时间,格式可能多样 published_time = None if hasattr(entry, 'published_parsed') and entry.published_parsed: published_time = datetime.fromtimestamp( time.mktime(entry.published_parsed) ).replace(tzinfo=pytz.UTC) elif hasattr(entry, 'updated_parsed') and entry.updated_parsed: published_time = datetime.fromtimestamp( time.mktime(entry.updated_parsed) ).replace(tzinfo=pytz.UTC) else: published_time = datetime.now(pytz.UTC) - timedelta(days=365) # 给一个很老的日期 # 只获取最近24小时的文章,避免内容过多 time_threshold = datetime.now(pytz.UTC) - timedelta(hours=24) if published_time > time_threshold: articles.append({ 'title': title, 'summary': clean_html(summary), # 清洗HTML标签的函数 'link': link, 'published': published_time, 'source': feed.feed.get('title', feed_url) # 来源名称 }) except Exception as e: print(f"抓取Feed失败 {feed_url}: {e}") return articles def clean_html(raw_html: str) -> str: """ 一个简单的HTML标签清洗函数,实际项目可使用`beautifulsoup4` """ import re cleanr = re.compile('<.*?>') cleantext = re.sub(cleanr, '', raw_html) # 替换一些HTML实体 cleantext = cleantext.replace('&nbsp;', ' ').replace('&amp;', '&').replace('&lt;', '<').replace('&gt;', '>') return cleantext.strip() def get_all_articles(feed_urls: List[str]) -> List[Dict]: """ 获取所有订阅源的文章 """ all_articles = [] for url in feed_urls: if url.strip() and not url.startswith('#'): # 跳过空行和注释 print(f"正在抓取: {url}") articles = fetch_feed(url.strip()) all_articles.extend(articles) print(f" 找到 {len(articles)} 篇新文章") # 按发布时间倒序排列 all_articles.sort(key=lambda x: x['published'], reverse=True) return all_articles if __name__ == '__main__': # 从文件读取订阅源 with open('feeds.txt', 'r', encoding='utf-8') as f: urls = [line.strip() for line in f.readlines()] articles = get_all_articles(urls) print(f"总共获取到 {len(articles)} 篇文章") for article in articles[:3]: # 打印前3篇看看 print(f"- {article['source']}: {article['title']}")

3.2 第二步:内容处理与格式化

获取到原始文章列表后,我们需要对其进行处理,以便于在有限的墨水屏空间内展示。

  1. 内容裁剪:墨水屏分辨率有限,文章摘要可能过长。需要设定一个最大字符数进行裁剪。
  2. 日期格式化:将UTC时间转换为本地时间,并格式化为易读的字符串。
  3. 生成报纸版面文本:将所有信息拼接成一份连贯的“报纸”文本。

编写内容处理脚本 (content_formatter.py):

from datetime import datetime import pytz def format_article_for_display(article: Dict, max_summary_len: int = 300) -> str: """ 将单篇文章格式化为用于显示的文本块 """ # 处理时间 local_tz = pytz.timezone('Asia/Shanghai') # 根据你的时区修改 local_time = article['published'].astimezone(local_tz) time_str = local_time.strftime('%m-%d %H:%M') # 处理标题和摘要 title = article['title'][:50] + (article['title'][50:] and '...') # 标题限长 summary = article['summary'] if len(summary) > max_summary_len: summary = summary[:max_summary_len] + '...' # 组合成文本块 # 使用等宽字体风格的排版,用`-`和`=`做简单装饰 text_block = f""" {article['source']} {time_str} {‘=’ * 30} {title} {‘-’ * 20} {summary} """ return text_block def generate_newspaper_text(articles: List[Dict]) -> str: """ 生成整份报纸的文本内容 """ newspaper_lines = [] newspaper_lines.append("=" * 40) newspaper_lines.append(" 我的每日墨水报") newspaper_lines.append(f" {datetime.now().strftime('%Y-%m-%d')}") newspaper_lines.append("=" * 40) newspaper_lines.append("") # 空行 for i, article in enumerate(articles[:15]): # 限制最多显示15篇,避免过长 newspaper_lines.append(format_article_for_display(article)) if i < len(articles[:15]) - 1: newspaper_lines.append("\n" + "-" * 40 + "\n") # 文章分隔线 newspaper_lines.append("\n" + "=" * 40) newspaper_lines.append("(End of Today‘s Digest)") newspaper_lines.append("=" * 40) return "\n".join(newspaper_lines)

3.3 第三步:文本转图片与排版

这是将纯文本渲染成墨水屏可显示图像的关键步骤。我们使用Pillow库来绘制文本。

编写图片生成脚本 (text_to_image.py):

from PIL import Image, ImageDraw, ImageFont import textwrap def text_to_image(text: str, output_path: str = ‘newspaper.png‘, screen_width: int = 800, screen_height: int = 480, font_path: str = ‘/usr/share/fonts/truetype/wqy/wqy-microhei.ttc‘): """ 将文本渲染成图片,适配电子墨水屏的尺寸和特性(黑白、高对比度)。 """ # 1. 创建一张黑白图片(模式‘1‘表示1位像素,黑白) image = Image.new(‘1‘, (screen_width, screen_height), 255) # 255为白色背景 draw = ImageDraw.Draw(image) # 2. 加载字体,墨水屏显示通常使用点阵字体或等宽字体更清晰 # 树莓派上可以安装文泉驿字体,或者使用PIL的默认字体 try: font = ImageFont.truetype(font_path, 16) font_bold = ImageFont.truetype(font_path, 18) # 用于标题 except IOError: print(f"警告:字体文件 {font_path} 未找到,使用默认字体。") font = ImageFont.load_default() font_bold = ImageFont.load_default() # 注意:默认字体可能不支持加粗 # 3. 设置边距和行高 margin = 20 line_spacing = 5 y = margin max_text_width = screen_width - 2 * margin # 4. 按行绘制文本 for line in text.split(‘\n‘): # 处理过长的行,自动换行 if draw.textlength(line, font=font) > max_text_width: # 使用textwrap进行单词级别的换行(英文友好) wrapped_lines = textwrap.wrap(line, width=int(max_text_width / 8)) # 估算字符数 for wrapped_line in wrapped_lines: if y < screen_height - margin - 20: # 留出底部边距 draw.text((margin, y), wrapped_line, font=font, fill=0) # fill=0为黑色 y += font.size + line_spacing else: # 判断是否为标题行(简单的启发式规则) current_font = font_bold if line.strip().startswith(‘我的每日墨水报‘) or ‘===‘ in line else font if y < screen_height - margin - 20: draw.text((margin, y), line, font=current_font, fill=0) # 标题行后多空一点 y += current_font.size + (line_spacing * 2 if current_font == font_bold else line_spacing) # 5. 保存图片 image.save(output_path, ‘PNG‘) print(f"报纸图片已生成: {output_path}") return output_path

3.4 第四步:推送图片到电子墨水屏

最后一步是将生成的图片发送到墨水屏设备并刷新显示。这部分代码高度依赖于你使用的硬件和驱动库。

以微雪(Waveshare)墨水屏为例 (epaper_display.py):

import sys import os sys.path.append(‘/home/pi/e-Paper/RaspberryPi_JetsonNano/python‘) # 添加驱动库路径 from waveshare_epd import epd7in5_V2 # 请根据你的具体屏幕型号导入对应的驱动类 from PIL import Image import time def display_image(image_path: str): """ 在连接的墨水屏上显示图片 """ try: print(‘初始化墨水屏...‘) # 初始化屏幕对象 epd = epd7in5_V2.EPD() epd.init() # 清屏,避免残影 epd.Clear() print(‘加载图片...‘) # 打开图片并转换为屏幕支持的格式(通常是1位黑白) image = Image.open(image_path) # 有些屏幕需要将图片旋转到正确的方向 # image = image.rotate(180) # 确保图片尺寸与屏幕分辨率匹配 # image = image.resize((epd.width, epd.height)) print(‘显示图片...‘) # 将图片缓冲到屏幕内存 epd.display(epd.getbuffer(image)) time.sleep(2) print(‘进入睡眠模式...‘) # 显示完成后,让屏幕进入睡眠模式以省电 epd.sleep() except IOError as e: print(f‘IO错误: {e}‘) except KeyboardInterrupt: print(‘程序被中断‘) epd7in5_V2.epdconfig.module_exit() exit()

3.5 第五步:整合与自动化

创建一个主程序 (main.py),将以上所有步骤串联起来,并设置定时任务。

import schedule import time from rss_fetcher import get_all_articles from content_formatter import generate_newspaper_text from text_to_image import text_to_image from epaper_display import display_image import logging logging.basicConfig(level=logging.INFO, format=‘%(asctime)s - %(levelname)s - %(message)s‘) def job(): """定时执行的任务""" logging.info(‘开始生成今日墨水报...‘) try: # 1. 获取文章 with open(‘feeds.txt‘, ‘r‘) as f: feed_urls = [line.strip() for line in f if line.strip() and not line.startswith(‘#‘)] articles = get_all_articles(feed_urls) if not articles: logging.warning(‘未获取到新文章。‘) return # 2. 生成报纸文本 newspaper_text = generate_newspaper_text(articles) # 3. 文本转图片 image_path = text_to_image(newspaper_text, ‘daily_newspaper.png‘) # 4. 推送到墨水屏显示 display_image(image_path) logging.info(‘今日墨水报已更新并显示!‘) except Exception as e: logging.error(f‘生成或显示报纸时出错: {e}‘, exc_info=True) if __name__ == ‘__main__‘: logging.info(‘墨水报服务启动。‘) # 立即运行一次 job() # 设置定时任务,例如每天早8点和晚8点各更新一次 schedule.every().day.at(‘08:00‘).do(job) schedule.every().day.at(‘20:00‘).do(job) # 也可以每小时运行一次用于测试 # schedule.every().hour.do(job) while True: schedule.run_pending() time.sleep(60) # 每分钟检查一次

为了让程序在树莓派开机时自动运行,可以将其设置为一个系统服务。

创建系统服务文件 (/etc/systemd/system/ink-newspaper.service):

[Unit] Description=Ink Newspaper Service After=network.target [Service] Type=simple User=pi WorkingDirectory=/home/pi/ink_newspaper ExecStart=/usr/bin/python3 /home/pi/ink_newspaper/main.py Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

然后启用并启动服务:

sudo systemctl daemon-reload sudo systemctl enable ink-newspaper.service sudo systemctl start ink-newspaper.service # 查看状态 sudo systemctl status ink-newspaper.service

4. 进阶优化与功能扩展

基础版本完成后,你可以根据需求进行以下优化:

4.1 内容增强

  • 全文抓取:许多RSS只提供摘要。可以使用requestsBeautifulSoup库,根据文章链接抓取并提取正文。
  • 图片处理:简单的文本转图片无法显示网络图片。可以尝试抓取文章首图,并将其转换为墨水屏可用的黑白抖动图,然后与文本图片拼接。
  • 内容过滤:根据关键词、来源或分类过滤掉不感兴趣的文章。
  • 摘要生成:对于过长的文章,可以使用本地NLP库(如jieba中文分词,sumy摘要提取)或调用大语言模型的API(需注意网络和成本)来生成更精炼的摘要。

4.2 显示优化

  • 更美观的排版:使用更复杂的Pillow绘图功能,实现多栏排版、字体混排、添加简单图标或边框。
  • 局部刷新:如果屏幕支持局部刷新,可以只更新变化的区域(如日期、头条),而不是全屏刷新,速度更快且减少残影。
  • 多页显示:如果内容太多,可以生成多张图片,并通过屏幕上的物理按钮或定时自动翻页。

4.3 架构优化

  • 云端部署:将RSS抓取、内容处理和图片生成的服务部署到云服务器(如腾讯云轻量应用服务器),树莓派只作为纯粹的显示终端,定期从云端拉取图片并刷新。这样内容源管理和更新会更灵活。
  • 使用现成API:如果不想自己解析RSS,可以使用像FeedbinInoreader等服务的API来获取已聚合和标准化后的文章列表。这需要订阅其付费服务。
  • 添加Web管理界面:使用Flask或FastAPI搭建一个简单的Web界面,方便在浏览器中添加/删除订阅源、手动触发更新等。

5. 常见问题与排查思路

在实现过程中,你可能会遇到以下问题:

问题现象可能原因排查与解决思路
程序报错ModuleNotFoundErrorPython依赖库未安装。使用pip list检查feedparser,Pillow等是否安装。在树莓派上注意区分pippip3
抓取RSS失败,返回403或超时目标网站有反爬机制;网络连接问题。1. 在请求头中添加User-Agent模拟浏览器。
2. 尝试使用代理(需合法合规)。
3. 检查网络连通性ping rss源域名
feedparser解析时间出错RSS源的时间格式非标准。fetch_feed函数中添加更健壮的时间解析逻辑,或直接使用entry.get(‘published‘)字符串,或忽略时间过滤。
生成的图片中文显示为方框系统缺少中文字体。在树莓派上安装中文字体:sudo apt install fonts-wqy-microhei。在text_to_image函数中指定正确的字体路径。
墨水屏不显示或花屏接线错误;驱动不匹配;初始化失败。1. 对照屏幕Wiki再三检查接线。
2. 确认导入的驱动类与屏幕型号完全一致。
3. 检查树莓派SPI/I2C接口是否已启用 (sudo raspi-config)。
4. 尝试运行厂商提供的示例程序,先确保硬件和基础驱动正常。
图片显示有严重残影刷新方式不对。全屏刷新时,在display前后先调用epd.Clear()。如果支持,尝试使用局部刷新API。遵循厂商推荐的刷新流程。
定时任务不执行schedule库在后台运行异常;系统时间问题。1. 使用更稳定的cron代替schedule
2. 检查系统时区设置timedatectl
3. 查看程序日志sudo journalctl -u ink-newspaper.service
内容过多,显示不全屏幕分辨率有限,文章数量太多。generate_newspaper_text函数中限制显示的文章数量(如15篇)。对摘要进行更严格的长度裁剪。

6. 最佳实践与工程建议

  1. 订阅源管理

    • 将订阅源列表放在独立的配置文件(如config.yaml)中,与代码分离。
    • 对订阅源进行分类(科技、新闻、博客),并在生成报纸时按类别分块显示。
    • 定期审查和更新订阅源,移除已失效或不再感兴趣的源。
  2. 错误处理与日志

    • 对网络请求、文件IO、屏幕操作等可能失败的环节进行try-except捕获。
    • 使用Python的logging模块记录信息、警告和错误日志,便于后期排查。
    • 可以考虑添加邮件或通知服务,当连续多次抓取失败时发送警报。
  3. 资源与性能

    • 树莓派内存有限,避免一次性加载过多文章或过大的图片到内存。
    • 图片生成是比较耗CPU的操作,合理安排定时任务间隔,避免过于频繁。
    • 考虑将生成的图片缓存起来,如果内容没有更新,则直接使用缓存,减少不必要的处理。
  4. 代码可维护性

    • 将不同功能的代码分离到不同的模块文件中(如抓取、解析、渲染、驱动)。
    • 使用函数和类来组织代码,提高可读性和复用性。
    • 在关键函数和配置项旁添加清晰的注释。
  5. 硬件保护

    • 电子墨水屏非常脆弱,避免物理挤压。
    • 严格按照数据手册操作,避免在刷新过程中断电。
    • 为你的树莓派和屏幕制作或购买一个合适的外壳。

通过这个项目,你不仅得到了一个独一无二的护眼阅读设备,更实践了从数据获取、处理到硬件交互的完整物联网(IoT)应用流程。你可以在此基础上继续扩展,例如加入天气预报、日程提醒、乃至简单的智能家居控制面板,让它成为一个真正的桌面信息中心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询