python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python处理UTF-8 BOM

Python如何处理UTF-8 BOM编码标记

作者:detayun

UTF-8 BOM(b'\xef\xbb\xbf')是Windows记事本等工具添加的编码标记,非标准UTF-8组成部分,常文件解析中类型判断失败,下面我们就来看看如何正确处理它吧

前言

在爬虫、文件解析、HTTP响应处理场景里,经常会遇到一种诡异问题:肉眼看文本完全正常,但程序判断逻辑莫名失效。典型案例:HTML页面明明是标准文档,却因为开头多出3个字节,被误判为非HTML类型。罪魁祸首就是 UTF-8 BOM。

很多开发者有误区:UTF-8本身不需要BOM。UTF-8 BOM只是微软体系(Windows记事本等)为了标记编码加入的约定,并不是UTF-8标准强制要求。它的二进制为 b'\xef\xbb\xbf',占3字节,放在文件最前面。

什么是 UTF-8 BOM

举个例子:

# 带BOM的HTML原始二进制
data = b'\xef\xbb\xbf<!DOCTYPE html><html>...</html>'
print(data.lstrip()) 
# 输出还是 b'\xef\xbb\xbf<!DOCTYPE html>...'
# lstrip() 只能删除空白字符,不会移除BOM字节!

这就是我在HTTP文件类型检测函数踩的坑:Content-Type 返回text/html,但正文头部带BOM,前缀匹配直接失败,识别成txt。

Python 处理 UTF-8 BOM 的几种方案

方案1:二进制预处理(推荐,爬虫场景首选)

先判断是否以BOM字节开头,如果是直接切片去掉前3字节。适合直接读取bytes的场景,比如requests响应原始内容。

def strip_utf8_bom(data: bytes) -> bytes:
    if data.startswith(b'\xef\xbb\xbf'):
        return data[3:]
    return data

raw = b'\xef\xbb\xbfhello world'
res = strip_utf8_bom(raw)
print(res) # b'hello world'

适合我们之前写的detect_file_type文件类型判断逻辑,二进制阶段清除BOM,不需要解码,性能更好,也避免解码异常。

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

Python内置编码 utf-8-sig,会自动识别并丢弃UTF-8 BOM,不需要手动写切片逻辑。

# 读取本地文件
with open("test.html", "r", encoding="utf-8-sig") as f:
    text = f.read()

# bytes转字符串时使用
raw_bytes = b'\xef\xbb\xbf<!DOCTYPE html>'
text = raw_bytes.decode("utf-8-sig")
print(text)

注意区分:

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

如果已经解码成字符串,BOM会变成Unicode零宽字符 \ufeff,可以直接替换:

text_with_bom = "\ufeff<!DOCTYPE html>"
clean_text = text_with_bom.replace("\ufeff", "")
print(clean_text)

缺点:会扫描全文替换,只适合小文本;大文件/二进制场景不推荐。

实战场景:爬虫HTTP响应中的BOM

很多网站返回HTML时会带上UTF-8 BOM。用requests拿到response.content是原始bytes。
如果直接用原始字节做魔数、标签前缀判断,BOM会干扰匹配。

就像前面文件类型检测代码,处理顺序建议:二进制先剔除BOM,再去除空白,再做前缀匹配:

def strip_bom_and_whitespace(data: bytes) -> bytes:
    # 第一步移除UTF8 BOM
    if data.startswith(b'\xef\xbb\xbf'):
        data = data[3:]
    # 第二步去除前置空白:空格、换行、回车、tab
    return data.lstrip(b' \r\n\t')

# 测试BOM+换行场景
html_bin = b'\xef\xbb\xbf\n  <html><body></body></html>'
clean_bin = strip_bom_and_whitespace(html_bin)
print(clean_bin.startswith(b'<html')) # True

常见踩坑点总结

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

什么时候保留BOM,什么时候去掉?

写入带BOM的UTF8文件示例:

content = "测试文本"
with open("out.txt", "wb") as fp:
    fp.write(b'\xef\xbb\xbf')
    fp.write(content.encode("utf-8"))

小结

UTF-8 BOM只是历史遗留标记,不是UTF-8标准一部分。在Python里分两种场景处理:

  1. 操作bytes原始二进制:判断前缀 b'\xef\xbb\xbf' 切片去除;
  2. 读取文本字符串:直接使用utf-8-sig编码自动处理。

在爬虫、文件检测这类底层二进制判断场景,推荐bytes预处理,可以避免解码带来的性能损耗与编码异常,也是我在HTTP文件类型识别函数里最终采用的方案。

到此这篇关于Python如何处理UTF-8 BOM编码标记的文章就介绍到这了,更多相关Python处理UTF-8 BOM内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文