读取包含某些行编码错误的 csv 文件,并将不正确的行返回给用户



我有一些用户上传要摄取的csv文件。在 Python 2 中,我能够以二进制打开文件,将其传递给unicodecsv.DictReader,如果某些行存在编码问题,例如由于客户使用 CP1251 或其他原因而导致无效的 Unicode 字符,我可以记录这些行并准确返回哪些行有问题。

使用 py3.7,我似乎无法做到这一点——csv模块需要对文件进行解码,如果我像(line.decode('utf8') for line in my_binary_file)这样的生成器传递给它,我不能让它只为坏行抛出异常并继续追逐。我尝试使用unicodecsv,即使它四年多没有提交并且在技术上不支持 py> 3.5,而且它似乎也不起作用——迭代器只是在坏行后停止。

我可以看到两种解决方法,这两种方法都不吸引人:
1(事先逐行解码文件并找到坏行,这是浪费,或者
2(编写我自己的CSV解析器,允许跳过坏行,这似乎是自找麻烦。

我可以用另一种方式做到这一点吗?

作为参考,下面是在 py2 中工作的示例代码:

def unicode_safe_iterator(reader):
while True:
try:
yield True, next(reader)
except UnicodeDecodeError as exc:
yield False, 'UnicodeDecodeError: %s' % str(exc)
# uncomment for py3:
# except StopIteration:
#     return
def get_data_iter_from_csv(csv_file, ...):
reader = unicodecsv.DictReader(csv_file)
error_messages = []
line_num = 1
for valid, row in unicode_safe_iterator(reader):
line_num += 1
if not valid:
error_messages.append(dict(line_number=line_num, error=row))
else:
row_data = validate_row_data(row)  # check for errors other than encoding, etc.
if not error_messages:
# stop yielding in case of errors, but keep iterating to find all errors.
yield row_data
if error_messages:
raise ValidationError(Errors.CSV_FILE_ERRORS, error_items=error_messages)

data_iter = get_data_iter_from_csv(open(path_to_csv, 'rb'), ...)

这是一个解决方法。我们将文件读取为字节流,在新行处拆分它,并尝试将行转换为 utf8 字符串。如果失败,请尝试将不正确的部分转换为 cp1251 字符串。 此后你可以使用io。StringIO 来模拟打开的文件。

import csv, io
def convert(bl):
rslt=[]
done=False
pos=0
while not done:
try:
s=bl[pos:].decode("utf8")
rslt.append(s)
done=True
except UnicodeDecodeError as ev:
abs_start, abs_end= pos+ev.start, pos+ev.end
rslt.append(bl[pos:abs_start].decode("utf8"))
rslt.append(bl[abs_start:abs_end].decode("cp1251",errors="replace"))
pos= abs_end
if pos>= len(bl):
done=True
return "".join(rslt)

with open(path_to_csv,"rb") as ff:
data= ff.read().split(b'x0a')
text= [ convert(line)  for line in data ]
text="n".join(text)
print(text)
rdr= csv.DictReader(io.StringIO(text))

它可以一次完成,而不是逐行完成:

with open(path_to_csv,"rb") as ff:  
text= convert( ff.read() )
rdr= csv.DictReader(io.StringIO(text))

最新更新