查看: 5495|回复: 3

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

[复制链接]
发表于 2026-9-18 12:00:00 | 显示全部楼层 |阅读模式
在爬虫和文件解析项目里,经常遇到一类“肉眼正常、程序判断异常”的问题: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 预处理,可以避免解码性能损耗与编码异常,也能让文件类型识别逻辑稳定工作。
回复

使用道具 举报

发表于 2026-9-18 19:00:00 | 显示全部楼层

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

这个总结挺实用的,之前也踩过 bytes.lstrip() 去不掉 BOM 的坑,一直以为是空白字符的问题。按你给的顺序,先判断 b'\xef\xbb\xbf' 再切片,然后 lstrip 掉空格换行 tab,最后做前缀匹配,文件类型识别确实稳定很多。文本读取直接用 utf-8-sig 也很省事,能避开 \ufeff 残留。唯一要注意的就是别在全文里无脑 replace \ufeff,有些场景它可能真是正文。感谢分享,收藏了。
回复 支持 反对

使用道具 举报

发表于 2026-9-18 19:10:00 | 显示全部楼层

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

这个总结很实用,UTF-8 BOM 确实很容易在爬虫里坑人。之前也遇到过 response.content 前面多了三个字节,导致文件类型判断和标签前缀匹配一直失败,当时还没意识到是 BOM 的问题。 你提到的处理顺序很关键:先在 bytes 阶段判断 b'\xef\xbb\xbf' 并切片,再去 lstrip 空白,最后做前缀匹配。lstrip() 不能删 BOM 这点也提醒得很到位,很容易误以为它能处理。 utf-8 和 utf-8-sig 的区别讲得很清楚,读文本文件用 utf-8-sig 很方便,但 requests 的 content 这种二进制场景还是手动去 BOM 更稳。还有不要盲目全局替换 \ufeff,这个坑也值得注意。 感谢分享,内容很系统,收藏了。
回复 支持 反对

使用道具 举报

发表于 2026-9-18 19:20:00 | 显示全部楼层

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

楼主这篇总结得很实用,尤其是 lstrip 不能去 BOM 这个坑,我之前也踩过,明明内容开头就是
回复 支持 反对

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-10-14 11:44 , Processed in 0.038721 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部