Python - CSV: 大文件行长度不一

4 投票
4 回答
9470 浏览
提问于 2025-04-15 21:03

简单来说,我有一个有2000万行的csv文件,这个文件的每一行长度不一样。这是因为使用了老旧的数据记录仪和一些专有格式。最终我们得到的结果是一个特定格式的csv文件。我的目标是把这个文件放入一个postgres数据库里。我该怎么做呢:

  • 保留前8列和最后2列,以便让csv文件的格式保持一致
  • 在csv文件的最前面或最后面添加一列新的数据

1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0, img_id.jpg, -50
1, 2, 3, 4, 5, 0,0,0,0,0,0,0,0,0,0,0 img_id.jpg, -50

4 个回答

1

抱歉,这个问题需要你写一些代码来解决。当你面对这样一个巨大的文件时,检查一下所有内容,确保它和你预期的一致是很重要的。如果你把有问题的数据放进数据库里,以后就很难把它们全部取出来了。

记住CSV文件的一些奇怪之处:它是各种相似标准的混合体,里面有不同的规则,比如引号、转义字符、空字符、Unicode、空字段(",,,")、多行输入和空行。csv模块有“方言”和选项,你可能会觉得csv.Sniffer类很有帮助。

我建议你:

  • 运行一个'tail'命令,查看最后几行。
  • 如果看起来没问题,就用csv读取器处理整个文件,看看会不会出错。快速制作一个“每行字段数”的直方图。
  • 考虑一下“有效”的范围和字符类型,在读取时严格检查它们。特别要注意不寻常的Unicode或不可打印的字符。
  • 认真考虑是否要把额外的、奇怪的值放在“行的其余部分”文本字段中。
  • 把任何意外的行放到一个异常文件里。
  • 修改你的代码,以处理异常文件中的新模式。重复这个过程。
  • 最后,再次运行整个程序,实际将数据导入数据库。

在你完全完成之前,不要碰数据库,这样可以加快你的开发速度。另外,要注意SQLite在只读数据上非常快,所以PostGres可能不是最佳选择。

你的最终代码可能会像这样,但我不能确定,因为我不知道你的数据,尤其是它的“表现”如何:

while not eof
    out = []
    for chunk in range(1000):
       try:
          fields = csv.reader.next()
       except StopIteration:
          break
       except:
          print str(reader.line_num) + ", 'failed to parse'"
       try:
          assert len(fields) > 5 and len(fields < 12)
          assert int(fields[3]) > 0 and int(fields[3]) < 999999
          assert int(fields[4]) >= 1 and int(fields[4] <= 12) # date
          assert field[5] == field[5].strip()  # no extra whitespace
          assert not field[5].strip(printable_chars)  # no odd chars
          ...
       except AssertionError:
          print str(reader.line_num) + ", 'failed checks'"
       new_rec = [reader.line_num]  # new first item
       new_rec.extend(fields[:8])   # first eight
       new_rec.extend(fields[-2:])  # last two
       new_rec.append(",".join(field[8:-2])) # and the rest
       out.append(new_rec)
    if database:
       cursor.execute_many("INSERT INTO raw_table VALUES %d,...", out)

当然,你使用这段代码的效果可能会有所不同。这只是伪代码的初稿。预计编写稳健的输入代码会花费你大部分时间。

2

你可以把文件当作文本文件打开,然后一行一行地读取。文件里有没有被引号包起来或者转义的逗号,这些逗号不会“分割字段”?如果没有的话,你可以这样做:

with open('thebigfile.csv', 'r') as thecsv:
    for line in thecsv:
        fields = [f.strip() for f in thecsv.split(',')]
        consist = fields[:8] + fields[-2:] + ['onemore']
        ... use the `consist` list as warranted ...

我怀疑在我写的 + ['onemore'] 这里,你可能想要“添加一列”,就像你说的,内容可能会很不一样,但我当然无法猜测那会是什么。

不要把每一行都单独发送到数据库里插入——2000万次插入会花费很的时间。相反,应该把“处理一致”的列表分组,添加到一个临时列表中——每当这个列表的长度达到,比如说,1000时,就用 executemany 一次性添加所有这些条目。

编辑:为了更清楚,我建议使用 csv 来处理你知道不是“标准”csv格式的文件:直接处理它能让你有更多的控制权(尤其是当你发现除了每行逗号数量不一致之外的其他不规则情况时)。

8

csv来读取一行数据,然后:

newrow = row[:8] + row[-2:]

接着添加你想要的新字段,并用csv把它写出来。

撰写回答