查看: 155|回复: 0

Python处理UTF-8 BOM:爬虫文件类型识别失败解决

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在爬虫和文件解析项目里,经常遇到一类“肉眼正常、程序判断异常”的问题:HTML 内容看起来是标准文档,文件类型识别却失败,被当成 txt 处理。排查后发现,正文最前面多了 3 个字节。这正是 UTF-8 BOM 造成的。

一、UTF-8 BOM 是什么

BOM 全称 Byte Order Mark,字节序标记,最初用于 UTF-16、UTF-32 区分大小端。UTF-8 本身不存在字节序问题,标准也没有强制要求 BOM。但 Windows 记事本保存 UTF-8 文件时默认写入 0xEF 0xBB 0xBF,即 b'\xef\xbb\xbf',占 3 字节,位于文件最前面。

这 3 字节不是正文,只是编码标记。二进制读取时会被原样读入内存,干扰后续解析。例如:
  1. data = b'\xef\xbb\xbf<!DOCTYPE html><html>...</html>'
  2. print(data.lstrip())
  3. # 输出还是 b'\xef\xbb\xbf<!DOCTYPE html>...'
  4. # lstrip() 只能删除空白字符,不会移除BOM字节!
复制代码

注意 lstrip() 只删除空白字符,BOM 不属于空白,所以不会被移除。在 HTTP 文件类型检测中,如果拿原始 bytes 做前缀匹配,就会直接失败,最终识别为 txt。

二、Python 处理 UTF-8 BOM 的三种方案

方案1:二进制预处理(推荐用于爬虫)

适合直接读取 bytes 的场景,比如 requests 的 response.content。先判断是否以 BOM 字节开头,是则切片去掉前 3 字节:
  1. def strip_utf8_bom(data: bytes) -> bytes:
  2.     if data.startswith(b'\xef\xbb\xbf'):
  3.         return data[3:]
  4.     return data
  5. raw = b'\xef\xbb\xbfhello world'
  6. res = strip_utf8_bom(raw)
  7. print(res)  # b'hello world'
复制代码

在文件类型判断逻辑里,二进制阶段清除 BOM 不需要解码,性能更好,也能避免解码异常。

方案2:使用 utf-8-sig 编码(文本读取首选)

Python 内置的 utf-8-sig 编码会自动识别并丢弃 UTF-8 BOM,不必手写切片逻辑:
  1. with open("test.html", "r", encoding="utf-8-sig") as f:
  2.     text = f.read()
  3. raw_bytes = b'\xef\xbb\xbf<!DOCTYPE html>'
  4. text = raw_bytes.decode("utf-8-sig")
  5. print(text)
复制代码

需要区分两个编码:utf-8 不会自动移除 BOM,解码后 BOM 会变成 \ufeff 字符残留在文本中;utf-8-sig 会自动移除 BOM,得到干净文本。

方案3:字符串层面清除 \ufeff

如果已经解码成字符串,BOM 会表现为 Unicode 零宽字符 \ufeff,可以直接替换:
  1. text_with_bom = "\ufeff<!DOCTYPE html>"
  2. clean_text = text_with_bom.replace("\ufeff", "")
  3. print(clean_text)
复制代码

这种方式会扫描全文替换,只适合小文本;大文件或二进制场景不推荐。

三、爬虫 HTTP 响应中的 BOM 处理顺序

很多网站返回 HTML 时会带 UTF-8 BOM。requests 的 response.content 是原始 bytes,直接拿它做魔数判断或标签前缀匹配,BOM 会干扰匹配。建议顺序是:先剔除 BOM,再去除前置空白,最后做前缀匹配:
  1. def strip_bom_and_whitespace(data: bytes) -> bytes:
  2.     # 第一步移除UTF8 BOM
  3.     if data.startswith(b'\xef\xbb\xbf'):
  4.         data = data[3:]
  5.     # 第二步去除前置空白:空格、换行、回车、tab
  6.     return data.lstrip(b' \r\n\t')
  7. html_bin = b'\xef\xbb\xbf\n <html><body></body></html>'
  8. clean_bin = strip_bom_and_whitespace(html_bin)
  9. print(clean_bin.startswith(b'<html'))  # True
复制代码

四、常见踩坑点

- bytes.lstrip() 不能自动删除 BOM,BOM 不属于空白字符。
- utf-8 与 utf-8-sig 是两个不同编码,打开文件选错就会残留 \ufeff。
- 不要盲目全局替换 \ufeff,极少数业务场景下该字符是正文内容,直接替换会破坏数据。
- 爬虫文件类型识别优先在 bytes 阶段处理 BOM,不要等到解码后再处理,避免不必要的解码开销。
- UTF-8 BOM 在 Linux 服务端极少出现,多由 Windows 生成的文件或 Windows 服务器输出。

五、什么时候保留 BOM,什么时候去掉

需要移除 BOM 的场景:网页解析、JSON 解析、XML 解析、文件类型识别、文本对比。

需要保留 BOM 的场景:兼容 Windows 记事本,输出文件希望记事本默认识别为 UTF-8 时,可以手动写入 BOM:
  1. content = "测试文本"
  2. with open("out.txt", "wb") as fp:
  3.     fp.write(b'\xef\xbb\xbf')
  4.     fp.write(content.encode("utf-8"))
复制代码

六、小结

UTF-8 BOM 是历史遗留标记,不是 UTF-8 标准的一部分。Python 中按操作对象分两类处理:操作 bytes 原始二进制时,判断前缀 b'\xef\xbb\xbf' 并切片去除;读取文本字符串时,直接用 utf-8-sig 编码自动处理。在爬虫、文件检测这类底层二进制判断场景,推荐 bytes 预处理,可以避免解码性能损耗与编码异常,也能让文件类型识别逻辑稳定工作。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-18 13:34 , Processed in 0.019551 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部