🚀

GitHub Trending 每日推送:Python 爬虫 + 邮件报告,每天发现新开源项目

用 Python 写一个 GitHub Trending 数据采集脚本,搭配定时任务和邮件推送,每天自动发现最火的开源项目,再也不用手动刷首页了。

👁️ - 次阅读

你有没有这种感觉:GitHub Trending 首页天天刷新,但真正值得关注的项目还是那么多。刷完就忘,过了几天又得从头看一遍。

不如让程序替你刷。

今天这个项目用 Python + GitHub API(不写爬虫),把每天 Trending 的 Python 类项目拉下来,加上简单的 star 增量排序,最后通过邮件推送到你的邮箱。每天早上醒来,邮箱里已经有一份精选开源项目报告。

项目背景

开源世界里新项目每天成百上千地涌现。关注所有仓库不现实,手动刷 Trending 又太费时。我们需要一个轻量级的信息筛选器:

  • 自动采集 GitHub Trending 页面数据
  • 按 star 增量排序,只看今天真正火的项目
  • 支持按语言分类筛选
  • 每天早上自动推送邮件报告

全部用 Python 标准库 + requests 完成,不需要数据库,不需要前端框架。

技术选型

组件选型理由
语言Python 3.10+生态丰富,写脚本快
HTTPrequests简单好用的 HTTP 客户端
HTML 解析BeautifulSoup4解析 GitHub 页面结构
邮件smtplibPython 内置,无需第三方库
定时任务cron / systemd timer系统级定时,稳定可靠
数据格式JSON本地缓存,方便后续分析

不需要爬 GitHub API(有速率限制),直接爬 Trending 页面更简单,也不违反使用条款——Trending 本身就是公开页面。

实现步骤

第一步:安装依赖

pip install requests beautifulsoup4

就这两个包。其他的全是 Python 内置模块。

import requests
from bs4 import BeautifulSoup
import json
import re
from datetime import datetime, timedelta

BASE_URL = "https://github.com/trending/python"
DAILY_FILE = "trending_daily.json"

def parse_repo_list(html):
    """解析 GitHub Trending 页面,提取仓库信息"""
    soup = BeautifulSoup(html, 'html.parser')
    repos = []

    for article in soup.select('article.Box-row'):
        repo_name = article.h2.a.text.strip()
        repo_url = article.h2.a['href']

        # 提取 star 和 fork 数据
        p = article.select_one('p.col-md-9')
        if not p:
            continue
        text = p.get_text().strip()

        # 提取今天的 star 增量
        star_match = re.search(r'([\d,]+)\s*star(?:\s+today|)', text)
        fork_match = re.search(r'([\d,]+)\s*fork(?:\s+today|)', text)
        desc_match = re.search(r'^(.*?)\s*[\d,]+\s*star', text, re.DOTALL)

        stars_today = int(star_match.group(1).replace(',', '')) if star_match else 0
        forks_today = int(fork_match.group(1).replace(',', '')) if fork_match else 0
        description = desc_match.group(1).strip() if desc_match else ''

        repos.append({
            'name': repo_name,
            'url': f"https://github.com{repo_url}",
            'stars_today': stars_today,
            'forks_today': forks_today,
            'description': description,
            'scraped_at': datetime.now().isoformat()
        })

    return repos

def fetch_trending(lang='python'):
    """获取指定语言的 Trending 列表"""
    url = f"https://github.com/trending/{lang}"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) '
                      'AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Chrome/120.0.0.0 Safari/537.36'
    }

    resp = requests.get(url, headers=headers, timeout=10)
    resp.raise_for_status()
    return parse_repo_list(resp.text)

def load_history():
    """加载历史数据,用于计算 star 增量"""
    try:
        with open(DAILY_FILE, 'r') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {'history': {}}

def save_to_history(repos):
    """保存当前数据到历史文件"""
    history = load_history()
    today = datetime.now().strftime('%Y-%m-%d')
    history['today'] = {
        'date': today,
        'lang': 'python',
        'repos': repos
    }

    # 保留最近 30 天的历史数据
    history['history'][today] = repos
    recent_history = dict(list(history['history'].items())[-30:])
    history['history'] = recent_history

    with open(DAILY_FILE, 'w') as f:
        json.dump(history, f, ensure_ascii=False, indent=2)

def get_repo_ranking(repos):
    """计算仓库排名"""
    ranked = []
    history = load_history()['history']

    for repo in repos:
        name = repo['name']
        stars_today = repo['stars_today']

        # 计算 star 增长率
        growth = 0
        if name in history.get(str(timedelta(days=1)), {}):
            prev_stars = history[str(timedelta(days=1))].get(name, {}).get('stars_today', 0)
            if prev_stars > 0:
                growth = round((stars_today - prev_stars) / prev_stars * 100)

        ranked.append({**repo, 'growth': growth})

    ranked.sort(key=lambda x: x['stars_today'], reverse=True)
    return ranked

if __name__ == '__main__':
    repos = fetch_trending('python')
    ranked = get_repo_ranking(repos)

    print(f"🔥 GitHub Trending Python 今日 Top {len(repos)}")
    print("=" * 60)
    for i, repo in enumerate(ranked[:15], 1):
        print(f"\n#{i} {repo['name']}")
        print(f"   ⭐ 今日 +{repo['stars_today']} stars "
              f"({'+' + str(repo['growth']) + '%' if repo['growth'] != 0 else '平稳'})")
        if repo['description']:
            print(f"   📝 {repo['description'][:80]}")
        print(f"   🔗 {repo['url']}")

    save_to_history(repos)
    print(f"\n✅ 数据已保存至 {DAILY_FILE}")

核心逻辑就三步:用 requests 请求 GitHub Trending 页面 → BeautifulSoup 解析 HTML 提取仓库信息 → 按 star 排序输出。

解析页面时注意几点:

  • GitHub Trending 页面使用 article.Box-row 包裹每个仓库卡片
  • star 和 fork 数据在 <p> 标签里,用正则提取数字
  • 设置 User-Agent 避免被 GitHub 拦截
  • 每个仓库记录名称、URL、今日 star 增量、fork 增量、描述

第三步:创建邮件推送脚本 push_report.py

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime

SMTP_SERVER = 'smtp.qq.com'
SMTP_PORT = 465
SENDER = 'your_email@qq.com'
PASSWORD = 'your_smtp_password'  # QQ 邮箱需要填授权码

RECEIVERS = ['your_email@qq.com', 'backup@example.com']

def format_report(repos):
    """生成 HTML 邮件报告"""
    date = datetime.now().strftime('%Y年%m月%d日')

    html = f"""
    <html>
    <head><style>
        body {{ font-family: -apple-system, sans-serif; background: #0d1117; color: #c9d1d9; }}
        .header {{ background: #161b22; padding: 20px; border-radius: 8px; margin-bottom: 20px; }}
        .repo {{ background: #161b22; padding: 16px; border-radius: 8px; margin-bottom: 12px; border-left: 3px solid #58a6ff; }}
        .repo a {{ color: #58a6ff; text-decoration: none; }}
        .repo a:hover {{ text-decoration: underline; }}
        .stats {{ color: #8b949e; font-size: 14px; }}
        .growth-up {{ color: #3fb950; }}
        .growth-down {{ color: #f85149; }}
    </style></head>
    <body>
    <div class="header">
        <h1>🔥 GitHub Trending 日报 · {date}</h1>
        <p class="stats">Python 类今日 trending 项目汇总</p>
    </div>
    """

    for i, repo in enumerate(repos[:15], 1):
        growth_class = 'growth-up' if repo['growth'] > 0 else ''
        growth_text = f'<span class="{growth_class}">↑{repo["growth"]}%</span>' if repo['growth'] != 0 else '<span style="color:#8b949e">—</span>'

        html += f"""
    <div class="repo">
        <h3>#{i} <a href="{repo['url']}">{repo['name']}</a></h3>
        <p>{repo['description'][:100]}{'...' if len(repo['description']) > 100 else ''}</p>
        <p class="stats">
            ⭐ 今日 +{repo['stars_today']} | 新增 fork +{repo['forks_today']}
            {' ' + growth_text}
        </p>
    </div>
    """

    html += """
    <p class="stats" style="margin-top:20px;">
        数据来源: github.com/trending/python · 每日自动推送
    </p>
    </body>
    </html>
    """
    return html

def send_email(repos):
    """发送 HTML 格式邮件"""
    subject = f"🔥 GitHub Trending 日报 · {datetime.now().strftime('%Y-%m-%d')}"

    msg = MIMEMultipart('alternative')
    msg['From'] = SENDER
    msg['To'] = ', '.join(RECEIVERS)
    msg['Subject'] = subject

    html_body = format_report(repos)
    text_body = f"GitHub Trending 日报 {datetime.now().strftime('%Y-%m-%d')}\n\n"
    for i, repo in enumerate(repos[:15], 1):
        text_body += f"#{i} {repo['name']}: {repo['url']}\n"

    msg.attach(MIMEText(text_body, 'plain', 'utf-8'))
    msg.attach(MIMEText(html_body, 'html', 'utf-8'))

    try:
        with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
            server.login(SENDER, PASSWORD)
            server.sendmail(SENDER, RECEIVERS, msg.as_string())
        print("✅ 邮件发送成功")
    except Exception as e:
        print(f"❌ 邮件发送失败: {e}")

if __name__ == '__main__':
    import json
    with open('trending_daily.json', 'r') as f:
        data = json.load(f)
    repos = data.get('today', {}).get('repos', [])
    if repos:
        send_email(repos)
    else:
        print("⚠️ 没有数据可发送")

邮件报告用的是暗色主题 HTML,打开邮箱就能看到带格式的排版效果。每个项目显示 star 增量、growth 百分比、描述和链接。

第四步:配置定时任务

把两个脚本放到同一个目录,然后用 cron 定时执行:

# 编辑 crontab
crontab -e

# 每天早上 8 点采集 + 推送
0 8 * * * cd ~/trending-bot && python trending_fetcher.py && python push_report.py

如果你的 VPS 不支持 cron,也可以用 systemd timer:

# /etc/systemd/system/trending-bot.service
[Unit]
Description=GitHub Trending Bot
[Service]
Type=oneshot
WorkingDirectory=/home/ubuntu/trending-bot
ExecStart=/usr/bin/python3 trending_fetcher.py && /usr/bin/python3 push_report.py

配合 .env 文件管理 SMTP 配置更安全,这里为了教程简洁直接硬编码了。

运行效果

跑起来后你每天都会收到一封邮件,类似这样:

🔥 GitHub Trending 日报 · 2026-06-13

Python 类今日 trending 项目汇总

#1 python/cpython
   ⭐ 今日 +1,234 stars (↑45%)
   📝 The Python programming language
   🔗 https://github.com/python/cpython

#2 fastapi/fastapi
   ⭐ 今日 +892 stars (↑30%)
   📝 FastAPI framework, high performance, easy to learn
   🔗 https://github.com/fastapi/fastapi

#3 ...

HTML 版本的邮件带暗色主题排版,star 增量用绿色标注增长趋势。手机端也能舒服地阅读。

本地 JSON 文件会保留最近 30 天的历史数据。跑一段时间后你可以用 DuckDB 做点简单分析——比如哪些项目持续涨星、哪些是昙花一现。

优化方向

这个项目已经能用了,想继续加也可以:

  1. 多语言支持:把 Python 改成参数,支持抓取 Go/JS/Rust 等语言的 Trending
  2. Telegram/微信推送:除了邮件,再加一个 Telegram Bot 推送,移动端更方便
  3. star 趋势图:用历史数据画出每个仓库的 star 折线图
  4. 智能筛选:加入关键词过滤,只推送你感兴趣领域的项目
  5. 去重机制:同一个项目连续出现时合并报告,减少重复推送
  6. Web 仪表盘:用 Streamlit 搭个简易看板,可视化所有历史数据

最核心的采集和推送逻辑就这几百行代码。跑通了再按自己的需求一点点加。

每天早上打开邮箱看到精选开源项目列表,比手动刷 Trending 页效率高十倍。试试看?

这篇文章对你有帮助吗?