Python以utf-8编码逐行读取大文件

10 投票
6 回答
22813 浏览
提问于 2025-04-16 14:46

我想读取一些非常大的文件(具体来说是谷歌的ngram单词数据集),并计算某个字符出现的次数。于是我写了这个脚本:

import fileinput
files = ['../../datasets/googlebooks-eng-all-1gram-20090715-%i.csv' % value for value in range(0,9)]
charcounts = {}
lastfile = ''
for line in fileinput.input(files):
    line = line.strip()
    data = line.split('\t')
    for character in list(data[0]):
        if (not character in charcounts):
            charcounts[character] = 0
        charcounts[character] += int(data[1])
    if (fileinput.filename() is not lastfile):
        print(fileinput.filename())
        lastfile = fileinput.filename()
    if(fileinput.filelineno() % 100000 == 0):
        print(fileinput.filelineno())
print(charcounts)

这个脚本运行得很好,直到它处理到第一个文件的大约第700,000行时,我就遇到了这个错误:

../../datasets/googlebooks-eng-all-1gram-20090715-0.csv
100000
200000
300000
400000
500000
600000
700000
Traceback (most recent call last):
  File "charactercounter.py", line 5, in <module>
    for line in fileinput.input(files):
  File "C:\Python31\lib\fileinput.py", line 254, in __next__
    line = self.readline()
  File "C:\Python31\lib\fileinput.py", line 349, in readline
    self._buffer = self._file.readlines(self._bufsize)
  File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8d in position 7771: cha
racter maps to <undefined>

为了修复这个问题,我在网上查了一下,找到了这段代码:

import fileinput
files = ['../../datasets/googlebooks-eng-all-1gram-20090715-%i.csv' % value for value in range(0,9)]
charcounts = {}
lastfile = ''
for line in fileinput.input(files,False,'',0,'r',fileinput.hook_encoded('utf-8')):
    line = line.strip()
    data = line.split('\t')
    for character in list(data[0]):
        if (not character in charcounts):
            charcounts[character] = 0
        charcounts[character] += int(data[1])
    if (fileinput.filename() is not lastfile):
        print(fileinput.filename())
        lastfile = fileinput.filename()
    if(fileinput.filelineno() % 100000 == 0):
        print(fileinput.filelineno())
print(charcounts)

但是我现在用的这个方法试图一次性把整个990MB的文件都读到内存里,这样我的电脑就崩溃了。有没有人知道怎么改写这段代码,让它真正能工作?

顺便说一下:这段代码还没有完全运行,所以我甚至不知道它是否能完成我想要的功能,但在这之前,我得先解决这个错误。

哦,对了,我使用的是Python 3.2。

6 个回答

1

这个方法对我有效:你可以在钩子定义中使用“utf-8”。我在一个50GB、200万行的文件上使用这个方法,没有遇到任何问题。

fi = fileinput.FileInput(openhook=fileinput.hook_encoded("iso-8859-1"))
2

问题在于,fileinput 并不是逐行读取文件,而是一次性读取一定字节数的内容。具体来说,它使用的是 file.readline(bufsize),这个方法会一次读取 bufsize 字节的数据,然后把这些数据变成一行一行的列表。你在调用 fileinput.input() 时,把 bufsize 参数设置为 0(这也是默认值),这意味着整个文件都会被一次性加载到内存中。

解决办法是:给 bufsize 设置一个合适的值。

8

我不知道为什么fileinput没有按预期工作。

我建议你使用open这个函数。它的返回值可以被逐行读取,和fileinput的效果一样。

那么代码大概会是这样的:

for filename in files:
    print(filename)
    for filelineno, line in enumerate(open(filename, encoding="utf-8")):
        line = line.strip()
        data = line.split('\t')
        # ...

这里有一些文档链接:enumerateopenio.TextIOWrapper(open会返回一个TextIOWrapper的实例)。

撰写回答