如何从SourceForge不同命名规范的软件包中提取版本号
SourceForge 软件版本号提取方案
直接用多规则加权打分+干扰项过滤的思路实现,仅依赖Python标准库即可覆盖绝大多数项目的命名场景,识别准确率远高于单纯正则匹配。
核心逻辑
- 路径拆分预处理:将输入的文件路径按
/切分为独立的目录段、文件名段逐段匹配,避免整串正则匹配引入无关干扰 - 干扰项前置过滤:提前排除架构、系统、语言、日期类无效数字串,包括
x86/x64/amd64/arm64等硬件架构标识、win/linux/macos等系统标识、en-US/zh-CN等语言标识、8位纯数字组成的发布日期串,以及zip/exe/iso等后缀名相关字段 - 候选版本匹配:用宽松正则抓取所有符合版本号特征的候选串,规则为匹配至少包含2段数字分隔的字符串(如
22.00/2.51.1/3.3.0均符合,单段数字如86/64直接排除),同时兼容v1.2.3/ver2.4/3.0.0-beta这类带前缀、预发布标识的版本格式 - 可信度加权排序:对所有候选按规则打分,得分最高的即为最终版本号,打分规则如下:
- 候选出现在目录段:+30分(SourceForge多数项目会将版本号设为独立目录,可信度最高)
- 候选在同一路径中重复出现:每多出现1次+20分
- 候选与项目名称在同一段中相邻(如
KeePass-2.51.1.zip中版本号紧跟项目名):+15分 - 候选前带
v/ver/version等版本标识前缀:+10分 - 候选嵌在无分隔的长字符串中(如
7z2200中的数字段):-20分
可直接运行的实现代码
import re from collections import defaultdict # 非版本号排除关键词,可根据实际遇到的场景持续补充 EXCLUDE_WORDS = { 'x86', 'x64', 'amd64', 'arm64', 'armv7', 'armv8', 'i386', 'i686', 'win', 'win32', 'win64', 'linux', 'darwin', 'mac', 'macos', 'osx', 'en', 'zh', 'cn', 'us', 'de', 'fr', 'jp', 'ko', 'install', 'setup', 'tar', 'xz', 'zip', 'gz', 'bz2', 'exe', 'msi', 'iso', 'src', 'bin' } # 版本号匹配正则 VERSION_PATTERN = re.compile( r'(?:v|V|ver|Ver|version|Version)?[._-]?(\d+(?:[._-]\d+){1,4})(?:[._-]?(?:alpha|beta|rc|RC|dev|stable|final))?' ) # 8位纯日期格式正则(如20110219) DATE_PATTERN = re.compile(r'^\d{8}$') def extract_version(file_path: str, proj_name: str = "") -> str: candidate_scores = defaultdict(int) path_segments = [seg for seg in file_path.split('/') if seg] total_seg = len(path_segments) for seg_idx, segment in enumerate(path_segments): matches = VERSION_PATTERN.finditer(segment) for match in matches: ver_candidate = match.group(1) # 过滤无效候选 clean_ver = ver_candidate.lower().replace('.', '').replace('-', '').replace('_', '') if ver_candidate.lower() in EXCLUDE_WORDS: continue if DATE_PATTERN.match(clean_ver): continue # 按规则打分 # 目录段加权 if seg_idx != total_seg - 1: candidate_scores[ver_candidate] += 30 # 出现次数加权 candidate_scores[ver_candidate] += 20 # 紧邻项目名加权 if proj_name and proj_name.lower().split('.')[0] in segment.lower(): proj_pos = segment.lower().find(proj_name.lower().split('.')[0]) if abs(match.start() - proj_pos) < len(proj_name) + 3: candidate_scores[ver_candidate] += 15 # 带版本前缀加权 prefix = match.group(0).lower() if prefix.startswith(('v', 'ver', 'version')): candidate_scores[ver_candidate] += 10 # 嵌在无分隔长串中减分 if match.start() > 0: prev_char = segment[match.start()-1] if not (prev_char in '._-' or prev_char.isalpha() and prev_char.lower() in 'ver'): candidate_scores[ver_candidate] -= 20 if not candidate_scores: return "" # 取得分最高的候选,统一分隔符为点 best_ver = max(candidate_scores.items(), key=lambda x: x[1])[0] return best_ver.replace('_', '.').replace('-', '.') # 测试验证 if __name__ == "__main__": test_cases = [ ("/7-Zip/22.00/7z2200-linux-x86.tar.xz", "sevenzip"), ("/KeePass 2.x/2.51.1/KeePass-2.51.1.zip", "keepass"), ("/extended/iso/en/OOo_3.3.0_Win_x86_install_en-US_20110219.iso", "openofficeorg.mirror") ] for path, proj in test_cases: print(f"{proj} 提取版本: {extract_version(path, proj)}")
测试结果
- 7-Zip 提取结果:
22.00 - KeePass 提取结果:
2.51.1 - OpenOffice.org 提取结果:
3.3.0
后续如果遇到识别偏差的项目,只需要把对应特有的干扰关键词补充到EXCLUDE_WORDS列表中即可,不需要修改核心逻辑,覆盖的项目越多识别准确率越高。
内容的提问来源于stack exchange,提问作者Alureon
相关产品推荐
相关产品推荐

