
本文详解为何原始代码中 for 循环未能完整填充数组,并指出核心问题在于正则表达式过于严格导致匹配失败;通过修正正则模式、增强容错处理,实现对 patch-* 类补丁文件(如 .patch.gz、.tar.xz 等)的完整抓取。
本文详解为何原始代码中 `for` 循环未能完整填充数组,并指出核心问题在于正则表达式过于严格导致匹配失败;通过修正正则模式、增强容错处理,实现对 `patch-*` 类补丁文件(如 `.patch.gz`、`.tar.xz` 等)的完整抓取。
在自动化抓取 Linux 实时内核(RT Patch)版本信息的场景中,一个常见目标是:从索引页解析所有可用版本号(如 5.16、6.1),再逐个访问其 sha256sums.asc 文件,从中提取全部补丁归档名称(例如 patch-6.1.12-rt7.patch.xz)。但实践中常出现“只捕获到早期版本(如 2.6)”的假象——实际并非循环中断或数组重置,而是正则匹配完全失效,导致后续所有版本的文件名均未被识别。
根本原因在于原始正则表达式:
patch_name_pattern = re.compile(r'patch-.*?\.tar\.xz')
该模式强制要求文件名必须以 patch- 开头、中间任意字符、并严格以 .tar.xz 结尾。然而,RT 补丁仓库中实际存在的文件命名格式多样,例如:
-
patch-5.16.2-rt19.patch.gz -
patches-6.1.12-rt7.tar.xz -
patch-6.1.12-rt7.patch.xz patches-6.1.12-rt7.tar.gz
以上四者均不满足 r'patch-.*?\.tar\.xz' 的约束(或后缀不符,或含 es,或用 patch 而非 patches),因此全部被跳过。由于低版本目录(如 2.6/)中恰好存在符合该模式的文件(如 patch-2.6.32-rt1.tar.xz),造成“仅返回早期版本”的错觉——实则是其他所有版本均无一匹配成功。
✅ 正确做法是采用更健壮、语义清晰的正则模式。推荐使用:
patch_name_pattern = re.compile(r'patch(?:es)?-[^\s]+?\.(?:patch|tar)\.(?:gz|xz)')
该模式含义明确:
-
patch(?:es)?:匹配patch或patches(?:表示非捕获组,提升性能); -
-:字面量连字符; -
[^\s]+?:非空白字符的最小匹配(避免跨行误捕); -
\.(?:patch|tar)\.(?:gz|xz):精确限定后缀为.patch.gz/.patch.xz/.tar.gz/.tar.xz。
同时,必须添加异常处理,因为部分版本目录(如 2.4/ 或已归档旧版)可能不存在 sha256sums.asc,直接 raise_for_status() 会导致整个循环中断:
import requests
import re
patches_versions = []
patches_full_versions = []
RT_PATCHES_BASE_URL = "https://www.php.cn/link/7cd71a0dd07fcca99870586c4865d084"
# 提取版本目录名(如 '5.16', '6.1')
patches_page_content = requests.get(RT_PATCHES_BASE_URL)
patches_page_content.raise_for_status()
# 使用更简洁的 HTML 清洗(无需预编译 cleaner)
stripped = re.sub(r']+>', '', patches_page_content.text)
for line in stripped.splitlines():
if re.search(r'\d+\.\d+', line): # 更精准匹配 x.y 版本格式
version_dir = line.strip().split('/')[0]
if version_dir and re.fullmatch(r'\d+\.\d+', version_dir):
patches_versions.append(version_dir)
# 定义宽松但安全的补丁文件匹配模式
pattern = re.compile(r'patch(?:es)?-[^\s]+?\.(?:patch|tar)\.(?:gz|xz)')
for ver in patches_versions:
url = f"{RT_PATCHES_BASE_URL}{ver}/sha256sums.asc"
try:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
matches = pattern.findall(resp.text)
patches_full_versions.extend(matches) # 推荐用 extend 替代多次 append
print(f"✓ {ver}: found {len(matches)} patches")
except requests.RequestException as e:
print(f"⚠ Skip {ver}: {type(e).__name__} - {e}")
print(f"\nTotal patches collected: {len(patches_full_versions)}")
print("Sample:", patches_full_versions[:3] if patches_full_versions else "None")
? 关键改进总结:
- ✅ 正则语义化:用
patch(?:es)?覆盖两种前缀,用[^\s]+?避免贪婪跨行,显式限定合法后缀; - ✅ 版本过滤强化:用
re.fullmatch(r'\d+\.\d+', ...)排除非版本噪声(如latest/,older/); - ✅ 异常隔离:单个版本请求失败不影响全局流程;
- ✅ 性能优化:
extend()批量追加比循环append()更高效; - ✅ 可观测性:每轮输出日志,便于定位缺失源。
运行修正后代码,即可稳定获取从 2.6 到最新 6.x 全量补丁文件名,彻底解决“数组填充不全”的表象问题。











