
本文详解为何 for 循环未能将所有匹配的补丁文件名(如 patch-5.16.2-rt19.patch.xz)追加到列表中,并提供健壮、可扩展的正则表达式与异常处理方案。
本文详解为何 `for` 循环未能将所有匹配的补丁文件名(如 `patch-5.16.2-rt19.patch.xz`)追加到列表中,并提供健壮、可扩展的正则表达式与异常处理方案。
在使用 Python 爬取 Linux 实时内核(RT)补丁版本页面时,一个常见误区是:误以为循环逻辑或列表追加机制出错,实则根源在于正则表达式过于严格,导致绝大多数目标字符串无法匹配。
回顾原始代码中的关键问题:
patch_name_pattern = re.compile(r'patch-.*?\.tar\.xz')
该正则仅匹配形如 patch- 的字符串(例如 patch-2.6.32.tar.xz),但实际 sha256sums.asc 文件中包含的是更丰富的命名变体,例如:
-
patch-5.16.2-rt19.patch.gz -
patch-5.16.2-rt19.patch.xz -
patches-5.16.2-rt19.tar.gz patches-5.16.2-rt19.tar.xz
这些均*不满足 `patch-.?.tar.xz模式**——要么后缀是.patch.gz,要么前缀是patches-,要么中间含-rt修饰符。因此,尽管for x in patches_versions:循环正常遍历了全部版本(2.6 至 6.9),但绝大多数re.findall()调用返回空列表,patches_full_versions.append()` 实际上几乎未执行,造成“只看到早期版本”的假象(实为早期目录中恰好存在符合旧模式的文件)。
✅ 正确做法是:放宽正则约束,聚焦语义特征而非字面格式。Linux RT 补丁文件名普遍以 patch 或 patches 开头,后跟版本号、可选修饰符(如 -rtN)及扩展名。推荐使用以下更鲁棒的模式:
patch_name_pattern = re.compile(r'(?:patch|patches)[\w\-\.]+(?:\.patch|\.tar)?\.(?:gz|xz)')
但为兼顾简洁性与兼容性(避免过度匹配非补丁行),实践中更稳妥的写法是:
patch_name_pattern = re.compile(r'(?:patch|patches)-[\d\.\-rt]+(?:\.patch|\.tar)?\.(?:gz|xz)')
同时,必须加入异常处理——部分版本目录(如 6.9/)可能尚未发布 sha256sums.asc,直接 requests.get(...).raise_for_status() 会中断整个流程。改进后的完整代码如下:
import re
import requests
patches_versions = []
patches_full_versions = []
RT_PATCHES_BASE_URL = "https://cdn.kernel.org/pub/linux/kernel/projects/rt/"
# 清理 HTML 标签(用于解析索引页)
cleaner = re.compile(r"<.>")
# 步骤 1:提取所有可用版本目录(如 '2.6/', '5.16/', '6.9/')
try:
patches_page_content = requests.get(RT_PATCHES_BASE_URL, timeout=10)
patches_page_content.raise_for_status()
stripped_content = re.sub(cleaner, "", patches_page_content.text)
for line in stripped_content.splitlines():
# 匹配含数字的非空行,并提取首段(即版本目录名)
if line.strip() and re.search(r"\d", line):
version_dir = line.split("/")[0].strip()
if version_dir: # 避免空字符串
patches_versions.append(version_dir)
except Exception as e:
print(f"❌ 获取版本列表失败: {e}")
exit(1)
# 步骤 2:对每个版本,抓取 sha256sums.asc 并提取补丁文件名
patch_name_pattern = re.compile(r'(?:patch|patches)[\w\-\.]+?\.(?:gz|xz|patch)') # 宽松匹配核心命名
for version in patches_versions:
url = f"{RT_PATCHES_BASE_URL}{version}/sha256sums.asc"
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
matches = re.findall(patch_name_pattern, resp.text)
patches_full_versions.extend(matches) # 使用 extend 更高效
print(f"✅ {version}: 找到 {len(matches)} 个补丁文件")
except requests.exceptions.HTTPError as e:
print(f"⚠️ {version} 页面不存在或无 sha256sums.asc ({e.response.status_code})")
except requests.exceptions.RequestException as e:
print(f"⚠️ 请求 {url} 失败: {e}")
except Exception as e:
print(f"⚠️ 解析 {version} 时发生未知错误: {e}")
print(f"\n? 总计收集到 {len(patches_full_versions)} 个补丁文件名")
print("示例:", patches_full_versions[:3] if patches_full_versions else "无匹配项")</.>
? 关键改进总结:
- ✅ 正则升级:从硬编码
patch-.*?\.tar\.xz改为语义化(?:patch|patches)[\w\-\.]+?\.(?:gz|xz|patch),覆盖主流命名; - ✅ 异常隔离:单个版本请求失败不再阻断全局流程,程序继续处理后续版本;
- ✅ 日志反馈:每步输出明确状态,便于快速定位缺失数据源(如
6.9/尚未上线); - ✅ 性能优化:用
list.extend()替代多次append(),减少对象操作开销。
运行此代码后,patches_full_versions 将真实反映各版本下所有可用补丁文件,彻底解决“只显示早期版本”的表象问题。











