用 Python 写一个 GitHub Trending 数据采集脚本,搭配定时任务和邮件推送,每天自动发现最火的开源项目,再也不用手动刷首页了。
你有没有这种感觉:GitHub Trending 首页天天刷新,但真正值得关注的项目还是那么多。刷完就忘,过了几天又得从头看一遍。
不如让程序替你刷。
今天这个项目用 Python + GitHub API(不写爬虫),把每天 Trending 的 Python 类项目拉下来,加上简单的 star 增量排序,最后通过邮件推送到你的邮箱。每天早上醒来,邮箱里已经有一份精选开源项目报告。
项目背景
开源世界里新项目每天成百上千地涌现。关注所有仓库不现实,手动刷 Trending 又太费时。我们需要一个轻量级的信息筛选器:
- 自动采集 GitHub Trending 页面数据
- 按 star 增量排序,只看今天真正火的项目
- 支持按语言分类筛选
- 每天早上自动推送邮件报告
全部用 Python 标准库 + requests 完成,不需要数据库,不需要前端框架。
技术选型
| 组件 | 选型 | 理由 |
|---|---|---|
| 语言 | Python 3.10+ | 生态丰富,写脚本快 |
| HTTP | requests | 简单好用的 HTTP 客户端 |
| HTML 解析 | BeautifulSoup4 | 解析 GitHub 页面结构 |
| 邮件 | smtplib | Python 内置,无需第三方库 |
| 定时任务 | cron / systemd timer | 系统级定时,稳定可靠 |
| 数据格式 | JSON | 本地缓存,方便后续分析 |
不需要爬 GitHub API(有速率限制),直接爬 Trending 页面更简单,也不违反使用条款——Trending 本身就是公开页面。
实现步骤
第一步:安装依赖
pip install requests beautifulsoup4
就这两个包。其他的全是 Python 内置模块。
第二步:创建采集脚本 trending_fetcher.py
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 做点简单分析——比如哪些项目持续涨星、哪些是昙花一现。
优化方向
这个项目已经能用了,想继续加也可以:
- 多语言支持:把 Python 改成参数,支持抓取 Go/JS/Rust 等语言的 Trending
- Telegram/微信推送:除了邮件,再加一个 Telegram Bot 推送,移动端更方便
- star 趋势图:用历史数据画出每个仓库的 star 折线图
- 智能筛选:加入关键词过滤,只推送你感兴趣领域的项目
- 去重机制:同一个项目连续出现时合并报告,减少重复推送
- Web 仪表盘:用 Streamlit 搭个简易看板,可视化所有历史数据
最核心的采集和推送逻辑就这几百行代码。跑通了再按自己的需求一点点加。
每天早上打开邮箱看到精选开源项目列表,比手动刷 Trending 页效率高十倍。试试看?
这篇文章对你有帮助吗?