Python爬虫实战:深挖GitHub开源库的“进化史”,Releases 抓取(附 CSV 导出)!

㊙️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!

㊗️爬虫难度指数:⭐

🚫声明:本数据&代码仅供学习交流,严禁用于商业用途、倒卖数据或违反目标站点的服务条款等,一切后果皆由使用者本人承担。公开榜单数据一般允许访问,但请务必遵守"君子协议",技术无罪,责任在人。

全文目录:

      • [🌟 开篇语](#🌟 开篇语)
      • [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
      • [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
      • [3️⃣ 合规与注意事项(必写)](#3️⃣ 合规与注意事项(必写))
      • [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
      • [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
      • [6️⃣ 核心实现:请求层(Fetcher)](#6️⃣ 核心实现:请求层(Fetcher))
      • [7️⃣ 核心实现:解析层(Parser)](#7️⃣ 核心实现:解析层(Parser))
      • [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
      • [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
      • [🔟 常见问题与排错(💡 经验之谈)](#🔟 常见问题与排错(💡 经验之谈))
      • [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
      • [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
      • [🌟 文末](#🌟 文末)
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

1️⃣ 摘要(Abstract)

项目名称:GitHubReleaseTracker_v1.0

核心工具:Python 3 + Requests + BeautifulSoup4

产出目标 :指定一个 GitHub 仓库(以 pandas-dev/pandas 为例),自动抓取其所有的发布版本号、发布日期、标题及更新日志摘要,导出为 CSV 时间线文件。

读完这篇文章,你将获得:

  1. 🕵️ 精准定位技巧 :如何在混乱的 HTML 结构中,精准抓取 <relative-time> 这种动态时间标签。
  2. 📑 分页处理模版 :GitHub 的分页是基于 Cursor(游标)的,学会如何处理 Next 按钮。
  3. 📝 非结构化文本处理:如何把长达几千字的 Markdown 更新日志清洗成适合表格存储的"摘要"。

2️⃣ 背景与需求(Why)

为什么要爬?

  • 版本审计:如果你是安全研究员,你需要知道哪个版本修复了 CVE 漏洞。
  • 竞品分析:分析竞争对手的迭代频率。他们是"周更狂魔"还是"年更咸鱼"?
  • 自动化报告:很多公司的内部文档需要自动同步依赖库的最新变更。

🎯 目标站点https://github.com/{owner}/{repo}/releases
📋 目标字段清单

  • Version Tag (版本标签):如 v2.0.3
  • Release Date (发布日期):精确到日
  • Title (发布标题):通常带有版本代号
  • Changelog (日志摘要):截取前 200 个字符

3️⃣ 合规与注意事项(必写)

  • Robots.txt:GitHub 允许抓取公开仓库的 Releases 页面,但禁止用于镜像站点或高频请求。
  • 频率控制这是底线! 抓取历史版本通常需要翻很多页(Pagination)。如果我们不加 sleep,连续快速翻页 10 次以上,GitHub 会直接触发 429 限制,甚至封禁你的 IP 一段时间。
  • 数据量级:有些老牌项目(如 Linux 核心或 React)有上千个版本,建议在测试阶段限制抓取页数(比如只爬前 5 页)。

4️⃣ 技术选型与整体流程(What/How)

技术栈

  • Requests:依然是主力,处理静态 HTML 足够了。
  • BeautifulSoup4 :GitHub Releases 页面的 HTML 结构语义化较好(section, h2),用 BS4 提取非常直观。

🌊 整体流程

初始化 URLFetcher (请求页面)Parser (提取当前页 Release 列表)Pagination (寻找 Next 按钮链接)Loop (递归/循环翻页)Storage (存入 CSV)

5️⃣ 环境准备与依赖安装(可复现)

Python 版本:3.8+

依赖安装

bash 复制代码
pip install requests beautifulsoup4 pandas

📂 项目结构

text 复制代码
github_releases/
├── data/
│   └── pandas_history.csv
├── release_spider.py
└── requirements.txt

6️⃣ 核心实现:请求层(Fetcher)

我们需要一个能够处理"下一页"逻辑的 Fetcher。这里我们把请求封装好,特别是要加上重试延时

python 复制代码
import requests
import time
import random

def get_soup(url):
    """
    发送请求并返回 BeautifulSoup 对象
    """
    headers = {
        # 务必加上 Referer,看起来像是在网页内点击跳转
        'Referer': 'https://github.com/', 
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
    }
    
    try:
        # 随机延时 2~4 秒,模仿人类阅读发布日志的速度
        sleep_time = random.uniform(2, 4)
        time.sleep(sleep_time)
        
        print(f"📡 正在请求: {url} (延时 {sleep_time:.2f}s)")
        resp = requests.get(url, headers=headers, timeout=15)
        resp.raise_for_status()
        
        return resp.text
    except Exception as e:
        print(f"❌ 请求失败: {e}")
        return None

7️⃣ 核心实现:解析层(Parser)

GitHub 的 Releases 页面结构主要由 <section> 标签组成(最新版 UI)。我们需要提取其中的关键信息。

难点

  • 日期 :GitHub 使用 <relative-time datetime="2023-01-01...">,这种标签文本是动态渲染的(如 "2 days ago"),但 datetime 属性是静态且标准的。
  • 日志.markdown-body 里面包含大量的 HTML,我们只需要纯文本。
python 复制代码
from bs4 import BeautifulSoup

def parse_releases(html):
    soup = BeautifulSoup(html, 'html.parser')
    releases_data = []
    next_page = None
    
    # 1. 定位所有发布板块
    # GitHub 的结构经常微调,目前每个 release 通常包裹在 section 里,或者特定的 div
    # 我们找包含 release-header 的区域
    sections = soup.find_all('section', class_='Box') 
    
    # 如果找不到 section,尝试找 div (兼容不同 UI 版本)
    if not sections:
        sections = soup.find_all('div', class_='Box')

    for section in sections:
        try:
            # --- 提取版本号 ---
            # 这里的结构通常是 href="/owner/repo/releases/tag/v1.0"
            tag_link = section.find('a', href=lambda x: x and '/releases/tag/' in x)
            version = tag_link.text.strip() if tag_link else "Unknown"
            
            # --- 提取日期 ---
            # 查找 relative-time 标签的 datetime 属性
            time_tag = section.find('relative-time')
            date_str = time_tag['datetime'] if time_tag else "Unknown"
            # 截取前10位,只要 YYYY-MM-DD
            date_str = date_str[:10]
            
            # --- 提取标题 ---
            # 标题通常是 h2 下的链接文本,或者直接是 h2 文本
            h2 = section.find('h2')
            title = h2.text.strip() if h2 else version
            
            # --- 提取日志摘要 ---
            body = section.find('div', class_='markdown-body')
            if body:
                # get_text() 会移除所有 HTML 标签
                raw_text = body.get_text(separator=' ', strip=True)
                # 截取前 150 个字符作为预览,避免 Excel 爆炸
                summary = raw_text[:150] + "..." if len(raw_text) > 150 else raw_text
            else:
                summary = "No description"

            releases_data.append({
                'Version': version,
                'Date': date_str,
                'Title': title,
                'Summary': summary
            })
            
        except Exception as e:
            # 某个版本解析失败不应影响整体
            continue

    # 2. 寻找下一页链接
    # GitHub 的分页按钮通常在底部,是一个类名为 pagination 的容器
    # 里面的 Next 按钮通常包含 text "Next"
    pagination = soup.find('div', class_='pagination')
    if pagination:
        next_link_tag = pagination.find('a', string=lambda text: text and 'Next' in text)
        if next_link_tag:
            next_page = next_link_tag['href']
            
    return releases_data, next_page

8️⃣ 数据存储与导出(Storage)

python 复制代码
import pandas as pd
import os

def save_history(data_list, repo_name):
    if not data_list:
        return
        
    df = pd.DataFrame(data_list)
    
    # 按照日期降序(虽然网页本身就是降序的,但保险起见)
    df.sort_values(by='Date', ascending=False, inplace=True)
    
    # 构造文件名
    safe_name = repo_name.replace('/', '_')
    filename = f"data/{safe_name}_releases.csv"
    
    if not os.path.exists('data'):
        os.makedirs('data')
        
    df.to_csv(filename, index=False, encoding='utf-8-sig')
    print(f"💾 历史档案已保存: {filename} (共 {len(df)} 个版本)")

9️⃣ 运行方式与结果展示(必写)

这里我们加入翻页逻辑。为了演示,我们设定一个 MAX_PAGES 限制,防止一直爬到天荒地老。

python 复制代码
def main():
    # 这里以 pandas 为例,你可以换成 facebook/react, vuejs/vue 等
    target_repo = "pandas-dev/pandas"
    base_url = f"https://github.com/{target_repo}/releases"
    
    all_releases = []
    current_url = base_url
    page_count = 0
    MAX_PAGES = 5  # ⛔️ 演示用,限制爬取前 5 页
    
    print(f"🚀 开始挖掘 {target_repo} 的发布历史...")
    
    while current_url and page_count < MAX_PAGES:
        page_count += 1
        print(f"📄 正在处理第 {page_count} 页...")
        
        html = get_soup(current_url)
        if not html:
            break
            
        data, next_page = parse_releases(html)
        if data:
            all_releases.extend(data)
            print(f"   ✅ 本页抓取 {len(data)} 个版本")
        
        # 检查是否有下一页
        if next_page:
            current_url = next_page
        else:
            print("🏁 已到达最后一页")
            break
            
    save_history(all_releases, target_repo)

if __name__ == "__main__":
    main()

📊 运行结果预览:

text 复制代码
🚀 开始挖掘 pandas-dev/pandas 的发布历史...
📡 正在请求: https://github.com/pandas-dev/pandas/releases (延时 2.15s)
📄 正在处理第 1 页...
   ✅ 本页抓取 10 个版本
📡 正在请求: https://github.com/pandas-dev/pandas/releases?after=... (延时 3.50s)
📄 正在处理第 2 页...
   ✅ 本页抓取 10 个版本
...
💾 历史档案已保存: data/pandas-dev_pandas_releases.csv (共 50 个版本)

CSV 文件预览:

Version Date Title Summary
v2.1.0 2023-08-30 Pandas 2.1.0 This release includes...
v2.0.3 2023-06-28 Pandas 2.0.3 Fixed regression in...
v2.0.2 2023-05-29 Pandas 2.0.2 Performance improvements...

🔟 常见问题与排错(💡 经验之谈)

  1. 抓不到数据?

    • GitHub 的 HTML 类名有时候是随机生成的(如 class="f1-light"),尽量找语义化标签 (如 relative-time)或属性 (如 href 包含 /tag/)。如果解析失败,请先打印 soup.prettify() 看看结构是不是变了。
  2. 无限循环翻页?

    • 一定要检查 next_page 提取逻辑。如果选择器写得太宽泛,可能会一直抓到同一个链接。
  3. 日期提取为空?

    • 确保你取的是 datetime 属性而不是 .text。因为 .text 可能是 "2 days ago",对数据分析很不友好。

1️⃣1️⃣ 进阶优化(可选但加分)

  • 增量爬取(断点续跑)

    • 在爬取前,先读取本地已有的 CSV,获取最新的版本号(比如 v2.1.0)。
    • 在爬虫 Loop 中,一旦遇到 v2.1.0,直接 break 退出循环。这样每次运行只爬最新的几个版本,速度快且安全。
  • Markdown 转 HTML

    • 如果你想把日志展示在自己的网页上,可以使用 markdown 库将提取的纯文本转回 HTML。

1️⃣2️⃣ 总结与延伸阅读

恭喜你!现在你已经拥有了一把"考古铲",可以随意挖掘任何开源项目的历史地层。

复盘

  • 我们处理了复杂的分页逻辑(Next Link)。
  • 我们学会了从 <relative-time> 这种标准标签中提取清洗好的时间数据。

下一步可以做什么?

  • Issues 爬取 :Releases 只是冰山一角。试试爬取 Issues 页面,统计一下哪个 Label(如 bug vs enhancement)出现的频率最高?
  • 贡献者图谱:爬取 Insights 页面的 Contributors,看看谁是这个项目的核心大佬。

代码跑起来,去看看你最爱的库(Vue? React? Django?)是什么时候诞生的吧!

🌟 文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!

相关推荐
2301_790300964 小时前
Python单元测试(unittest)实战指南
jvm·数据库·python
Data_Journal4 小时前
Scrapy vs. Crawlee —— 哪个更好?!
运维·人工智能·爬虫·媒体·社媒营销
VCR__4 小时前
python第三次作业
开发语言·python
韩立学长4 小时前
【开题答辩实录分享】以《助农信息发布系统设计与实现》为例进行选题答辩实录分享
python·web
深蓝电商API5 小时前
async/await与多进程结合的混合爬虫架构
爬虫·架构
2401_838472515 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
u0109272715 小时前
使用Python进行网络设备自动配置
jvm·数据库·python
工程师老罗5 小时前
优化器、反向传播、损失函数之间是什么关系,Pytorch中如何使用和设置?
人工智能·pytorch·python
Fleshy数模5 小时前
我的第一只Python爬虫:从Requests库到爬取整站新书
开发语言·爬虫·python
CoLiuRs5 小时前
Image-to-3D — 让 2D 图片跃然立体*
python·3d·flask