㊙️本期内容已收录至专栏《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 时间线文件。
读完这篇文章,你将获得:
- 🕵️ 精准定位技巧 :如何在混乱的 HTML 结构中,精准抓取
<relative-time>这种动态时间标签。 - 📑 分页处理模版 :GitHub 的分页是基于 Cursor(游标)的,学会如何处理
Next按钮。 - 📝 非结构化文本处理:如何把长达几千字的 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 提取非常直观。
🌊 整体流程:
初始化 URL ➔ Fetcher (请求页面) ➔ 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... |
🔟 常见问题与排错(💡 经验之谈)
-
抓不到数据?
- GitHub 的 HTML 类名有时候是随机生成的(如
class="f1-light"),尽量找语义化标签 (如relative-time)或属性 (如href包含/tag/)。如果解析失败,请先打印soup.prettify()看看结构是不是变了。
- GitHub 的 HTML 类名有时候是随机生成的(如
-
无限循环翻页?
- 一定要检查
next_page提取逻辑。如果选择器写得太宽泛,可能会一直抓到同一个链接。
- 一定要检查
-
日期提取为空?
- 确保你取的是
datetime属性而不是.text。因为.text可能是 "2 days ago",对数据分析很不友好。
- 确保你取的是
1️⃣1️⃣ 进阶优化(可选但加分)
-
增量爬取(断点续跑):
- 在爬取前,先读取本地已有的 CSV,获取最新的版本号(比如
v2.1.0)。 - 在爬虫 Loop 中,一旦遇到
v2.1.0,直接break退出循环。这样每次运行只爬最新的几个版本,速度快且安全。
- 在爬取前,先读取本地已有的 CSV,获取最新的版本号(比如
-
Markdown 转 HTML:
- 如果你想把日志展示在自己的网页上,可以使用
markdown库将提取的纯文本转回 HTML。
- 如果你想把日志展示在自己的网页上,可以使用
1️⃣2️⃣ 总结与延伸阅读
恭喜你!现在你已经拥有了一把"考古铲",可以随意挖掘任何开源项目的历史地层。
复盘:
- 我们处理了复杂的分页逻辑(Next Link)。
- 我们学会了从
<relative-time>这种标准标签中提取清洗好的时间数据。
下一步可以做什么?
- Issues 爬取 :Releases 只是冰山一角。试试爬取 Issues 页面,统计一下哪个 Label(如
bugvsenhancement)出现的频率最高? - 贡献者图谱:爬取 Insights 页面的 Contributors,看看谁是这个项目的核心大佬。
代码跑起来,去看看你最爱的库(Vue? React? Django?)是什么时候诞生的吧!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。技术无罪,责任在人!!