Python中如何爬取GitHub上所有merged的PR并展示在README中

面试的时候经常被问到参与过什么开源项目,每次都是找几个然后发链接过去,虽然面试官直接可以从 pull request 页面直接搜,但是还是比较麻烦。所以自己写了个工具,能爬取所有被 merged 的 PR,然后写 README.md 并自动 Push 到 Github 上的仓库。

很久没有用 python 了,语法都快忘的差不多了,代码写的不是很好,后面自己慢慢改,觉得有用的同学可以 fork 过去直接使用,可以提提意见,主要是展示 PR 信息那一栏,没有想出好看布局。

https://github.com/Leviathan1995/MyContribution

欢迎大家提 Issue, star


Python中如何爬取GitHub上所有merged的PR并展示在README中

7 回复

https://github.com/CodeFalling/MyContribution

晒一个,不知不觉蹭了好多 PR,这个小工具挺有用的,给你点个赞


import requests
import base64
from datetime import datetime

def fetch_merged_prs(repo_owner, repo_name, token=None):
    """
    获取GitHub仓库所有merged的PR
    """
    prs = []
    page = 1
    headers = {'Accept': 'application/vnd.github.v3+json'}
    if token:
        headers['Authorization'] = f'token {token}'
    
    while True:
        url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/pulls'
        params = {
            'state': 'closed',
            'per_page': 100,
            'page': page
        }
        
        response = requests.get(url, headers=headers, params=params)
        if response.status_code != 200:
            raise Exception(f"API请求失败: {response.status_code}")
        
        data = response.json()
        if not data:
            break
            
        for pr in data:
            if pr['merged_at']:  # 只收集已合并的PR
                prs.append({
                    'number': pr['number'],
                    'title': pr['title'],
                    'user': pr['user']['login'],
                    'merged_at': pr['merged_at'],
                    'html_url': pr['html_url']
                })
        
        # 检查是否还有下一页
        if 'next' not in response.links:
            break
        page += 1
    
    # 按合并时间倒序排列
    return sorted(prs, key=lambda x: x['merged_at'], reverse=True)

def generate_pr_markdown(prs):
    """
    生成PR列表的Markdown格式
    """
    markdown = "## 📌 已合并的Pull Requests\n\n"
    markdown += "| PR编号 | 标题 | 贡献者 | 合并时间 |\n"
    markdown += "|--------|------|--------|----------|\n"
    
    for pr in prs[:50]:  # 只显示最近的50个
        date = datetime.strptime(pr['merged_at'], '%Y-%m-%dT%H:%M:%SZ')
        formatted_date = date.strftime('%Y-%m-%d %H:%M')
        markdown += f"| [#{pr['number']}]({pr['html_url']}) | {pr['title']} | @{pr['user']} | {formatted_date} |\n"
    
    if len(prs) > 50:
        markdown += f"\n> 显示最近50个PR,共{len(prs)}个已合并PR\n"
    
    return markdown

def update_readme(repo_owner, repo_name, token, pr_markdown):
    """
    更新README文件
    """
    # 1. 获取当前README内容
    readme_url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/contents/README.md'
    headers = {'Accept': 'application/vnd.github.v3+json'}
    if token:
        headers['Authorization'] = f'token {token}'
    
    response = requests.get(readme_url, headers=headers)
    
    if response.status_code == 404:
        # README不存在,创建新文件
        content = ""
        sha = None
    elif response.status_code == 200:
        # 解码现有内容
        data = response.json()
        content = base64.b64decode(data['content']).decode('utf-8')
        sha = data['sha']
    else:
        raise Exception(f"获取README失败: {response.status_code}")
    
    # 2. 替换或添加PR部分
    start_marker = "## 📌 已合并的Pull Requests"
    end_marker = "## "  # 下一个标题开始
    
    if start_marker in content:
        # 找到现有PR部分的位置
        start_idx = content.find(start_marker)
        next_section = content.find(end_marker, start_idx + len(start_marker))
        
        if next_section != -1:
            # 替换现有内容
            new_content = content[:start_idx] + pr_markdown + content[next_section:]
        else:
            # PR部分是最后一部分
            new_content = content[:start_idx] + pr_markdown
    else:
        # 在文件末尾添加PR部分
        new_content = content.rstrip() + "\n\n" + pr_markdown
    
    # 3. 更新README
    update_data = {
        'message': '更新已合并PR列表',
        'content': base64.b64encode(new_content.encode('utf-8')).decode('utf-8')
    }
    if sha:
        update_data['sha'] = sha
    
    update_response = requests.put(readme_url, headers=headers, json=update_data)
    
    if update_response.status_code in [200, 201]:
        print("README更新成功!")
    else:
        print(f"更新失败: {update_response.status_code}")

def main():
    # 配置信息
    REPO_OWNER = "your_username"
    REPO_NAME = "your_repository"
    GITHUB_TOKEN = "your_github_token"  # 需要repo权限
    
    try:
        # 1. 获取已合并的PR
        print("正在获取已合并的PR...")
        prs = fetch_merged_prs(REPO_OWNER, REPO_NAME, GITHUB_TOKEN)
        print(f"找到 {len(prs)} 个已合并的PR")
        
        # 2. 生成Markdown
        markdown = generate_pr_markdown(prs)
        
        # 3. 更新README
        update_readme(REPO_OWNER, REPO_NAME, GITHUB_TOKEN, markdown)
        
    except Exception as e:
        print(f"执行出错: {e}")

if __name__ == "__main__":
    main()

使用说明:

  1. 获取GitHub Token

  2. 修改配置

    • 替换REPO_OWNER为仓库所有者
    • 替换REPO_NAME为仓库名
    • 替换GITHUB_TOKEN为你的token
  3. 运行脚本

    python github_pr_tracker.py
    

脚本功能:

  • 自动获取所有已合并的PR
  • 按合并时间倒序排列
  • 生成美观的Markdown表格
  • 自动更新README文件
  • 处理分页(支持大量PR)

注意事项:

  • 首次运行会在README末尾添加PR部分
  • 后续运行会自动更新现有PR列表
  • 默认显示最近50个PR,可修改generate_pr_markdown中的数量

总结: 用GitHub API获取PR数据,然后自动更新README。

诶我是不是在群里见过你,有没有兴趣来阿里

使用有什么问题吗? 我看你好像先改了一次 README.md,使用不需要任何改动的,它自动重写了 README.md . 现在校招不是都过了吗,还能进阿里啊?

#3 第一次 push 的时候我把密码输错了(你永远不知道用户会干什么系列)

我不知道你是哪届的,2018 届的秋招应该还没开始

我是 17 届的,和你一届吧。我也记得你

邮箱联系吧,github 上有我邮箱。

回到顶部