花拾录
← 返回知识库

解析页面算出"主文件比总大小还大":选择器选错了表格

编程语言导入2026/09/220 阅读0 评论

从详情页解析文件列表,算出一个明显违反常识的结果:主文件体积比整个列表的总大小还大。数字自己会说话——这不可能。这篇文章讲这个"不可能"背后的 HTML 解析陷阱。

现象

从详情页解析文件列表,算出来的主文件体积竟然比总大小还大:

主文件大小: 1.2 GB
列表总大小: 860 MB

主文件是整个列表的一部分,怎么可能比总和还大?这个"局部大于整体"的矛盾,就是解析出了问题的最明确信号。

根因

页面里有两个 class 名相同或相近的表格:

  • 一个是真正的文件表格(主区,列出了这个资源的各个文件);
  • 一个是侧栏的**"相关资源"表格**(推荐其它资源,体积是另一回事)。

而脚本的选择器写得太宽,两个表格都选中了,于是把侧栏"相关资源"的体积也算进了"文件列表"里。侧栏那些"相关资源"跟当前资源没关系,体积自然可以对不上,一叠加就出现了"主文件比总大小还大"的荒谬结果。

关键在于:选择器语法上是正确的——它确实选中了带那个 class 的元素,一个都没选错。问题出在"选中的范围"上,而不是"选中的语法"上。

解决

第一,明确只取第一个表格。 既然主表格在文档流里排在前面,就用索引限定:

from bs4 import BeautifulSoup

soup = BeautifulSoup(html, "html.parser")
tables = soup.select("table.file-list")   # 可能匹配到多个

# 只取第一个(真正的文件表)
file_table = tables[0]

# 显式记录匹配了几个,匹配到多个就告警
if len(tables) > 1:
    print(f"[WARN] 匹配到 {len(tables)} 个同名表格,只取第一个")

rows = file_table.select("tr")

第二,更稳妥的做法是加上更具体的定位。 结合父容器、位置、表头特征来缩小范围:

# 用父容器限定,避免选到侧栏
main = soup.select_one("div.main-content")
file_table = main.select_one("table.file-list")

# 或者用表头特征确认是不是正确的表
headers = [th.get_text(strip=True) for th in file_table.select("th")]
assert "文件名" in headers and "大小" in headers, f"表头不对: {headers}"

第三,不要只看残缺的文件列表就下结论,先确认拿到的是完整列表。 用一个自洽性检查兜底:

main_size = parse_size(main_file["size"])
total = sum(parse_size(r["size"]) for r in rows)
assert main_size <= total, f"主文件({main_size}) > 总和({total}),解析有误"

这条断言能立刻拦下这类错误——因为"主文件 ≤ 总和"是恒成立的物理约束。

延伸与预防

这个坑的核心教训是:解析 HTML 时,"选择器选到了什么"比"选择器写对没有"更容易出错。

选择器语法正确,只保证"它不会报错",不保证"它选的是你要的东西"。页面结构一变(多了个同名 class、多了个侧栏模块),选择器的语义就悄悄变了,而语法依然"正确"。

预防的方法:

  • 给选择器加"数量断言"——预期选 1 个就断言 len == 1,多了少了都报错;
  • 用父容器限定范围,别用全局的宽松选择器;
  • 用内容特征验证——检查表头、字段名是否符合预期,而不只是看元素存不存在;
  • 加自洽性校验——像"主文件 ≤ 总和"这种恒真约束,是最好的 bug 探测器。

最后一条经验值得单独记住:当解析结果违反常识时,先怀疑"选多了",而不是先怀疑数据源错了。 HTML 里藏着的同名元素、隐藏元素、模板元素,远比想象的多。

评论(0)

  • 还没有评论,来抢沙发~

相关文章