Python 正则表达式读取类似CSV的行

13 投票
6 回答
17336 浏览
提问于 2025-04-15 18:57

我想处理一些类似CSV格式的数据行。数据值之间用逗号分隔(而且逗号周围可能有空格),值可以用单引号或双引号括起来。例如,下面这一行是有效的:

    data1, data2  ,"data3'''",  'data4""',,,data5,

但这一行是格式不正确的:

    data1, data2, da"ta3", 'data4',

-- 引号只能在前面或后面有空格。

这样的格式不正确的行应该被识别出来,最好能在行中标记出不正确的值,但如果正则表达式没有匹配整行也是可以接受的。

我正在尝试写一个正则表达式来解析这些数据,使用match()或findall(),但我想到的每一个正则表达式在处理一些特殊情况时都有问题。

所以,也许有经验的人能帮我解决这个问题?(或者说这对正则表达式来说太复杂了,我应该直接写一个函数)

编辑1:

csv模块在这里用处不大:

    >>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2',''')))
    [['2', ' "dat', 'a1"', " 'dat", "a2'", '']]

    >>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2',''')))
    [['2', 'dat,a1', "'dat", "a2'", '']]

-- 除非可以调整一下?

编辑2:做了一些语言上的修改 - 希望现在的英语更准确了

编辑3:感谢大家的回答,我现在很确定正则表达式在这里不是个好主意,因为(1)处理所有特殊情况可能很棘手(2)输出的格式不规律。写完这些,我决定去看看提到的pyparsing,或者自己写一个类似有限状态机的解析器。

6 个回答

4

Python有一个标准库模块可以用来读取csv文件:

import csv

reader = csv.reader(open('file.csv'))

for line in reader:
    print line

对于你提供的示例输入,这段代码会输出:

['data1', ' data2 ', "data3'''", ' \'data4""\'', '', '', 'data5', '']

编辑:

你需要添加skipinitalspace=True,这样在双引号前面允许有空格,适用于你提供的额外示例。至于单引号的情况我还不太确定。

>>> list(csv.reader(StringIO('''2, "dat,a1", 'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]

>>> list(csv.reader(StringIO('''2,"dat,a1",'dat,a2','''), skipinitialspace=True))
[['2', 'dat,a1', "'dat", "a2'", '']]
12

虽然使用 csv 模块是解决这个问题的正确方法,但其实用正则表达式也能做到这一点:

import re

r = re.compile(r'''
    \s*                # Any whitespace.
    (                  # Start capturing here.
      [^,"']+?         # Either a series of non-comma non-quote characters.
      |                # OR
      "(?:             # A double-quote followed by a string of characters...
          [^"\\]|\\.   # That are either non-quotes or escaped...
       )*              # ...repeated any number of times.
      "                # Followed by a closing double-quote.
      |                # OR
      '(?:[^'\\]|\\.)*'# Same as above, for single quotes.
    )                  # Done capturing.
    \s*                # Allow arbitrary space before the comma.
    (?:,|$)            # Followed by a comma or the end of a string.
    ''', re.VERBOSE)

line = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""

print r.findall(line)

# That prints: ['data1', 'data2', '"data3\'\'\'"', '\'data4""\'', 'data5']

编辑:如果你想验证行内容,可以在上面的正则表达式基础上稍微加一些内容:

import re

r_validation = re.compile(r'''
    ^(?:    # Capture from the start.
      # Below is the same regex as above, but condensed.
      # One tiny modification is that it allows empty values
      # The first plus is replaced by an asterisk.
      \s*([^,"']*?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')\s*(?:,|$)
    )*$    # And don't stop until the end.
    ''', re.VERBOSE)

line1 = r"""data1, data2  ,"data3'''",  'data4""',,,data5,"""
line2 = r"""data1, data2, da"ta3", 'data4',"""

if r_validation.match(line1):
    print 'Line 1 is valid.'
else:
    print 'Line 1 is INvalid.'

if r_validation.match(line2):
    print 'Line 2 is valid.'
else:
    print 'Line 2 is INvalid.'

# Prints:
#    Line 1 is valid.
#    Line 2 is INvalid.
7

虽然通过一些预处理、使用 csv 模块、后处理和正则表达式的组合,可能可以实现你的需求,但你提到的要求和 csv 模块的设计不太匹配,正则表达式也可能不太适合(这取决于你需要处理的嵌套引号的复杂程度)。

在复杂的解析情况下,pyparsing 是一个很好的选择。如果这不是一次性的情况,使用它可能会得到最简单且易于维护的结果,虽然一开始可能需要多花一点力气。不过,考虑到这笔投资很快就能回报,因为你可以省去调试正则表达式来处理特殊情况的额外麻烦……

你可以很容易找到基于 pyparsing 的 CSV 解析示例,像 这个问题 可能就能帮助你入门。

撰写回答