Python Codecs包无法解码字节

0 投票
2 回答
1338 浏览
提问于 2025-04-18 17:53

我正在使用 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)

这样我创建的一个叫 RowDataRowHeaders 的以逗号分隔的字符串列表看起来是这样的:

 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 个回答

0

这个 '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

下面是理解这个过程的方法:

  1. 首先,要明白 字符 是给人看的,而 字节 是给电脑用的。电脑其实是在帮我们把字节转换成字符,这样我们才能理解数据。

  2. 所以,每当你需要存储一些东西给电脑用时,你需要把它从字符转换成字节,因为电脑只懂字节。所有的文件(即使是文本文件)都是字节。只不过当你打开文件时,这些字节数据会被转换成字符,这样我们才能理解它的内容。对于“二进制”文件(比如图片或 Word 文档),这个过程稍微有点不同。

  3. 如果我们要写“文本”内容,就需要把字符(也就是字形)转换成字节,这样文件才能被写入。这个过程叫做解码。

  4. 当我们想要“读取”一个文本文件,也就是把字节转换成字符时,我们需要对这些字节进行编码——实际上就是翻译它们。为了知道哪些字符对应存储的字节,我们使用一个查找表,这个表的名字(utf-8)就是你传入的。

1

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

撰写回答