Python Codecs包无法解码字节
我正在使用 Python 2.7.3 和 BeautifulSoup 从一个网站的表格中抓取数据,然后用 codecs
把内容写入文件。我收集的一个变量,有时候会出现乱码。例如,如果网站的表格看起来是这样的:
Year Name City State
2000 John D’Iberville MS
2001 Steve Arlington VA
所以当我生成我的 City
变量时,我总是把它编码为 utf-8
:
Year = foo.text
Name = foo1.text
City = foo3.text.encode('utf-8').strip()
State = foo4.text
RowsData = ("{0},{1},{2},{3}").format(Year, Name, City, State)
这样我创建的一个叫 RowData
和 RowHeaders
的以逗号分隔的字符串列表看起来是这样的:
RowHeaders = ['Year,Name,City,State']
RowsData = ['2000, John, D\xc3\xa2\xe2\x82\xac\xe2\x84\xa2Iberville, MS',
'2001, Steve, Arlington, VA']
然后我尝试用以下代码把这些写入文件:
file1 = codecs.open(Outfile.csv,"wb","utf8")
file1.write(RowHeaders + u'\n')
line = "\n".join(RowsData)
file1.write(line + u'\r\n')
file1.close()
结果我遇到了以下错误:
Traceback (most recent call last):
File "HSRecruitsFBByPosition.py", line 141, in <module>
file1.write(line + u'\r\n')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 6879: ordinal not in range(128)
我可以使用 csv 写入包处理 RowsData
,效果很好。但由于一些我不想详细说明的原因,我需要用 codecs 来输出 csv 文件。我搞不清楚发生了什么。有没有人能帮我解决这个问题?非常感谢。
2 个回答
这个 'D\xc3\xa2\xe2\x82\xac\xe2\x84\xa2Iberville'
是一个普通的字符串,但里面有一些特殊的字符被转义了。
所以,要正确显示它,你需要先解码。因为你没有提供解码方式,Python 默认尝试用 ASCII 编码,但失败了。
>>> s
'D\xc3\xa2\xe2\x82\xac\xe2\x84\xa2Iberville'
>>> type(s)
<type 'str'>
>>> type(s.decode('utf-8'))
<type 'unicode'>
>>> print(s.decode('utf-8'))
D’Iberville
下面是理解这个过程的方法:
首先,要明白 字符 是给人看的,而 字节 是给电脑用的。电脑其实是在帮我们把字节转换成字符,这样我们才能理解数据。
所以,每当你需要存储一些东西给电脑用时,你需要把它从字符转换成字节,因为电脑只懂字节。所有的文件(即使是文本文件)都是字节。只不过当你打开文件时,这些字节数据会被转换成字符,这样我们才能理解它的内容。对于“二进制”文件(比如图片或 Word 文档),这个过程稍微有点不同。
如果我们要写“文本”内容,就需要把字符(也就是字形)转换成字节,这样文件才能被写入。这个过程叫做解码。
当我们想要“读取”一个文本文件,也就是把字节转换成字符时,我们需要对这些字节进行编码——实际上就是翻译它们。为了知道哪些字符对应存储的字节,我们使用一个查找表,这个表的名字(utf-8)就是你传入的。
codecs.open()
会帮你进行编码。不要把已经编码的数据交给它,因为这样 Python 会试图再把数据 解码 一遍,然后再编码成 UTF-8。这个隐式的解码使用的是 ASCII 编码,但如果你的数据中有非 ASCII 字符,这样就会出错:
>>> u'D’Iberville'.encode('utf8')
'D\xc3\xa2\xe2\x82\xac\xe2\x84\xa2Iberville'
>>> u'D’Iberville'.encode('utf8').encode('utf8')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128)
解决办法是*不要手动编码:
Year = foo.text
Name = foo1.text
City = foo3.text.strip()
State = foo4.text
需要注意的是,codecs.open()
不是处理文件流的最高效方法。在 Python 2.7 中,我会使用 io.open()
来代替;它提供了相同的功能,但实现得更稳健。io
模块是 Python 3 的默认输入输出实现,但在 Python 2 中也可以使用,以便向后兼容。
不过,你似乎在重新发明 CSV 的处理方式;Python 有一个很棒的 csv
模块,可以帮你生成 CSV 文件。不过在 Python 2 中,它无法处理 Unicode,因此你 确实 需要手动编码:
import csv
# ...
year = foo.text
name = foo1.text
city = foo3.text.strip()
state = foo4.text
row = [year, name, city, state]
with open(Outfile.csv, "wb") as outf:
writer = csv.writer(outf)
writer.writerow(['Year', 'Name', 'City', 'State'])
writer.writerow([c.encode('utf8') for c in row])
最后,如果你的 HTML 页面显示的文本是 D’Iberville
,那么你就遇到了 乱码问题;这是因为你把 UTF-8 错误地当成了 CP-1252 来解读:
>>> u'D’Iberville'.encode('cp1252').decode('utf8')
u'D\u2019Iberville'
>>> print u'D’Iberville'.encode('cp1252').decode('utf8')
D’Iberville
这通常是因为绕过了 BeautifulSoup 的编码检测(传入的是字节字符串,而不是 Unicode)。
你可以尝试用以下方法来“修复”这些问题:
try:
City = City.encode('cp1252').decode('utf8')
except UnicodeError:
# Not a value that could be de-mojibaked, so probably
# not a Mojibake in the first place.
pass