问题是在用 Python 抓取网站图片时发现的。代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib2
img = "https://www.baidu.com/img/bdlogo.png"
imgdata = urllib2.urlopen(img).read()
png = open("logo.png", "w")
png.write(imgdata)
png.close()
代码看起来没有什么问题,但下载下来的图片却已经损坏,无法打开。
对原始图片和下载后的图片进行 hash 对比,发现两者的内容确实发生了变化:原文件大小为 5331 字节,保存后的文件变成了 5345 字节。进一步用二进制查看器分析,发现原文件中的部分 0x0a 前面被自动增加了一个 0x0d。
熟悉 ASCII 控制字符的可能知道:
- 0x0a:换行(LF)
- 0x0d 0x0a:回车换行(CRLF)
Windows 和 Unix/Linux 对文本换行符的处理方式不同:
Windows:CRLF → 0x0d 0x0a
Unix/Linux:LF → 0x0a
因此推测,问题并不是图片下载过程中发生的,而是在保存文件时,Python 按照文本模式处理了数据。在 Windows 下,文本模式会自动进行换行符转换,把 0x0a 转换为 0x0d 0x0a。
对于普通文本来说,这种转换通常没有问题;但 PNG、JPEG、EXE 等二进制文件中的 0x0a 本身就是正常的数据内容,一旦被自动修改,文件自然就可能损坏。
那么,对于二进制数据应该如何处理呢?
Python 的文件打开模式提供了 b 参数,用于表示以二进制模式打开文件,例如:
png = open("logo.png", "wb")
png.write(imgdata)
png.close()常见的二进制文件模式包括:
rb 以二进制方式读取
wb 以二进制方式写入
ab 以二进制方式追加
rb+ 以二进制方式读写
wb+ 以二进制方式读写并清空原文件
Python 官方文档对 b 模式的解释大意是:在 Windows 上,文本文件和二进制文件存在区别,文本模式下读取或写入文件时,行结束符可能会被自动转换。这种转换对于普通 ASCII 文本通常没有问题,但可能会破坏 JPEG、EXE 等二进制数据。因此,读写二进制文件时应明确使用二进制模式。
上面的例子是写入文件时的问题。如果读取文件,也存在类似情况。
例如,一个使用 Windows 换行符的文本文件:
0d 0a
在 Windows 下使用普通的 "r" 文本模式读取时,运行环境可能会将其转换为:
0a
而在二进制模式下读取:
f=open("test.txt", "rb")则可以直接获得文件原始的字节数据,不进行换行符转换。
因此可以简单理解为:
文本模式处理的是“文本内容”,运行环境可能会参与编码或换行符转换;二进制模式处理的是“原始字节”,文件中的数据会尽可能保持原样。
对于图片、音频、视频、压缩包、可执行文件等二进制数据,应始终使用 b 模式打开。
虽然在 Unix/Linux 平台上,文本模式和二进制模式通常不会产生 Windows 下这种换行符转换问题,但为了保证程序的跨平台兼容性,处理二进制文件时仍然应该明确使用 rb、wb 等模式。
评论0
欢迎分享你的看法,也欢迎补充不同的实践经验。