一种简易的rss接收方案

背景
需求的功能:①不额外下载软件(洁癖是这样的:tieba_003: )②能够提醒我每天进行阅读,但是不愿意关闭免打扰模式③可以一键部署,但是要保留可diy性
思考
最难解决的问题其实是第二条,我之前尝试过在专门的软件上配制rss,但是死活无法养成“打开手机-进行阅读”的链条(阅读的内容是有关学习的,原生吸引力不强),后来一直在想,有哪个软件是我一打开手机肯定会点开的呢?于是我把目光投向了微信。微信的小红点是我最无法忍受的东西,于是结合微信的这种特性,提出了一种简易的rss方案
方案
需要准备两个邮箱,一个是QQ邮箱,另一个任意。获取另一个邮箱的专用密码(本文以gmail为例),在GitHub新建仓库使用下面的代码

import feedparser
import smtplib
import random
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timezone, timedelta

# --- 1. 配置信息 (请根据需要修改) ---

# 发件人Gmail地址
SENDER_EMAIL = '[email protected]'

# Gmail应用专用密码
SENDER_APP_PASSWORD = 'ndmb eppv ndmb eppv'

# 收件人邮箱
RECIPIENT_EMAIL = '[email protected]'

# SMTP服务器地址
SMTP_SERVER = 'smtp.gmail.com'

# SMTP服务器端口 (SSL)
SMTP_PORT = 465

# --- 2. RSS源列表 ---

# 在这里添加或删除您想订阅的RSS源
# 格式为 {'name': '自定义名称', 'url': 'RSS地址'}
RSS_FEEDS = [
    # 您之前提供的RSS源
    {'name': 'RSS App Feed', 'url': 'https://rss.app/feeds/o6eUCKonflhxeRZO.xml'},
    {'name': 'Sitemap Generator 1', 'url': 'https://cdn.mysitemapgenerator.com/shareapi/rss/25061165417'},
    {'name': 'Sitemap Generator 2', 'url': 'https://cdn.mysitemapgenerator.com/shareapi/rss/25061165418'},
    {'name': 'Sitemap Generator 3', 'url': 'https://cdn.mysitemapgenerator.com/shareapi/rss/25061165450'},
    {'name': 'IBTimes', 'url': 'https://www.ibtimes.com/rss'},
    {'name': 'The Guardian', 'url': 'https://www.theguardian.com/world/rss'},
    {'name': 'NYT HomePage', 'url': 'https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml'},
    {'name': 'RSSHub Finance', 'url': 'https://news.google.com/rss/search?q=when:24h+allinurl:bloomberg.com&hl=en-US&gl=US&ceid=US:en'},
    {'name': 'Sitemap Generator 4', 'url': 'https://cdn.mysitemapgenerator.com/shareapi/rss/25061165479'},
    {'name': 'Sitemap Generator 5', 'url': 'https://cdn.mysitemapgenerator.com/shareapi/rss/25061165480'},
    # 您代码中原有的RSS源
    {'name': 'V2EX', 'url': 'https://www.v2ex.com/index.xml'},
    {'name': 'Telegram - NodeSelect', 'url': 'https://rss.wudifeixue.com/telegram/channel/nodeselect'},
    {'name': '吾爱破解精选', 'url': 'https://www.52pojie.cn/forum.php?mod=guide&view=digest&rss=1'},
    {'name': 'Hack News', 'url': 'https://hn.buzzing.cc/feed.xml'},
    {'name': '少数派', 'url': 'https://rss.app/feeds/1h8et5HcZccKZeDu.xml'},
]

# --- 3. 发送数量控制 ---

# 设置每天要发送的文章总数
TOTAL_POSTS_TO_SEND = 20

# --- 4. 功能实现 (通常无需修改) ---

def fetch_and_select_random_posts():
    """获取所有RSS源的文章,随机选择指定数量"""
    all_posts = []
    print("开始获取所有RSS源的文章...")

    for feed_info in RSS_FEEDS:
        name = feed_info['name']
        url = feed_info['url']
        print(f"正在处理: {name} ({url})")
        try:
            feed = feedparser.parse(url)
            if feed.bozo:
                print(f"警告: RSS源 '{name}' 可能格式不正确: {feed.bozo_exception}")

            # 为每篇文章添加来源名称
            for post in feed.entries:
                post.source_name = name
                all_posts.append(post)

        except Exception as e:
            print(f"获取RSS源 '{name}' 失败: {e}")
            continue

    print(f"所有RSS源获取完毕,共收集到 {len(all_posts)} 篇文章。")

    # 如果收集到的文章少于要发送的数量,直接使用所有文章
    if len(all_posts) < TOTAL_POSTS_TO_SEND:
        print(f"警告: 总文章数 ({len(all_posts)}) 少于目标发送数 ({TOTAL_POSTS_TO_SEND}),将发送所有已获取文章。")
        selected_posts = all_posts
    else:
        # 随机打乱所有文章的顺序
        random.shuffle(all_posts)
        # 选择指定数量的文章
        selected_posts = all_posts[:TOTAL_POSTS_TO_SEND]
        print(f"已随机选择 {len(selected_posts)} 篇文章进行发送。")

    # 对最终选择的文章按发布时间排序,方便阅读
    selected_posts.sort(key=lambda p: p.get('published_parsed'), reverse=True)

    return selected_posts


def format_email_html(posts):
    """将帖子列表格式化为HTML邮件内容"""
    if not posts:
        return "<h3>今天所有RSS源都没有获取到新帖子。</h3>", "每日RSS推送"

    beijing_time = datetime.now(timezone(timedelta(hours=8)))
    title = f"每日RSS精选 - {beijing_time.strftime('%Y-%m-%d')}"

    html_content = f"""
    <html>
    <head>
        <style>
            body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; line-height: 1.6; background-color: #f4f4f4; margin: 0; padding: 0; }}
            .container {{ max-width: 680px; margin: 20px auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background-color: #ffffff; }}
            h2 {{ color: #333; text-align: center; }}
            .post {{ margin-bottom: 18px; padding-bottom: 18px; border-bottom: 1px solid #eee; }}
            .post-title {{ font-size: 18px; margin: 0 0 8px 0; }}
            .post-title a {{ color: #0056b3; text-decoration: none; font-weight: bold;}}
            .post-title a:hover {{ text-decoration: underline; }}
            .post-meta {{ font-size: 13px; color: #555; }}
            .source-tag {{ background-color: #007bff; color: white; padding: 3px 8px; border-radius: 4px; font-weight: bold; font-size: 12px; display: inline-block;}}
        </style>
    </head>
    <body>
        <div class="container">
            <h2>{title}</h2>
    """

    for post in posts:
        post_title = post.title
        post_link = post.link
        source_name = getattr(post, 'source_name', '未知来源')

        pub_time_str = "未知时间"
        if 'published_parsed' in post and post.published_parsed:
            try:
                # 创建UTC时区的datetime对象
                pub_time_utc = datetime(*post.published_parsed[:6], tzinfo=timezone.utc)
                # 转换为北京时间
                pub_time_beijing = pub_time_utc.astimezone(timezone(timedelta(hours=8)))
                pub_time_str = pub_time_beijing.strftime('%Y-%m-%d %H:%M')
            except Exception as e:
                print(f"处理时间戳时出错: {e}")
                pub_time_str = "时间格式错误"

        html_content += f"""
            <div class="post">
                <p class="post-title"><a href="{post_link}" target="_blank">{post_title}</a></p>
                <div class="post-meta">
                    <span class="source-tag">{source_name}</span> | 发布于: {pub_time_str}
                </div>
            </div>
        """

    html_content += """
        </div>
    </body>
    </html>
    """

    return title, html_content


def send_email(subject, html_body):
    """发送邮件"""
    print("准备发送邮件...")
    msg = MIMEMultipart()
    msg['From'] = SENDER_EMAIL
    msg['To'] = RECIPIENT_EMAIL
    msg['Subject'] = subject
    msg.attach(MIMEText(html_body, 'html', 'utf-8'))

    try:
        with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
            server.login(SENDER_EMAIL, SENDER_APP_PASSWORD)
            server.sendmail(SENDER_EMAIL, [RECIPIENT_EMAIL], msg.as_string())
        print(f"邮件已成功发送至 {RECIPIENT_EMAIL}!")
    except Exception as e:
        print(f"邮件发送失败: {e}")


def main():
    """主函数"""
    posts = fetch_and_select_random_posts()
    if posts:
        subject, html_body = format_email_html(posts)
        send_email(subject, html_body)
    else:
        print("没有获取到任何文章,不发送邮件。")


if __name__ == "__main__":
    main()

然后在workflow里面配制

name: V2EX Daily Push

on:
  workflow_dispatch: # 允许手动触发
  schedule:
    # 每天北京时间上午9点运行
    # cron表达式格式为:分钟 小时 日 月 周
    # UTC时间 1:00 对应北京时间 9:00
    - cron: '0 1 * * *'

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install feedparser

    - name: Run script
      env:
        # 引用下面设置的 Secrets
        SENDER_EMAIL: ${{ secrets.SENDER_EMAIL }}
        SENDER_APP_PASSWORD: ${{ secrets.SENDER_APP_PASSWORD }}
        RECIPIENT_EMAIL: '[email protected]'
      run: |

        # 运行Python脚本
        python v2ex_daily.py

效果




我主要用于每日进行外刊推送,另外微信客户端自带了翻译(右上角有翻译功能),可以在微信内部进行每日外刊阅读而不用跳到其它APP
RSS推荐
代码里面已经有一些我自己在用的,下面再推荐一些比较优质的rss源给大家

9 Likes

好东西,谢谢大佬!

感谢分享

感谢分享