如何读取包含多个引号分隔符的单个字段的CSV文件?

3

我希望能够分割一个包含多次引用定界符的字符串。csv模块中有没有处理这种类型字符串的参数?或者还有其他方法可以处理它吗?

text = '"a,b"-"c,d","a,b"-"c,d"'
next(csv.reader(StringIO(text), delimiter=",", quotechar='"', quoting=csv.QUOTE_NONE))

期望输出结果:['"a,b"-"c,d"', '"a,b"-"c,d"']

实际输出结果:['"a', 'b"-"c', 'd"', '"a', 'b"-"c', 'd"']

编辑: 上面的示例是简化的,但显然过于简化,因此一些评论提供了解决方案,但没有针对完整版本。下面是我想要处理的实际数据。

import csv
text = '"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-CD-0,"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-LS-0'
next(csv.reader(StringIO(text), delimiter=",", quotechar='"', quoting=csv.QUOTE_NONE))

期望输出

[
  '"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-CD-0',
  '"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-LS-0'
]

实际输出

[
  '"3-Amino-1',
  '2',
  '4-triazole"-text-0-"3-Amino-1',
  '2',
  '4-triazole"-CD-0','"3-Amino-1',
  '2', '4-triazole"-text-0-"3-Amino-1',
  '2',
  '4-triazole"-LS-0'
]

很遗憾我不能做出这个假设。我会更新这个例子! - TomTom
1
数据从哪里来?您能多介绍一下格式吗? - fsimonjetz
@Thomas,你说得对,我没有完全更新示例。现在已经更新了。 - TomTom
尝试使用以下代码:print(next(csv.reader(StringIO(text.replace('","', '";"')), delimiter=';', quoting=csv.QUOTE_NONE))) - Martin Evans
@MartinEvans 看起来我把例子简化得太多了。你的代码对我提供的简化示例有效,但对于完整的示例则不行。我会在问题中更新完整的示例。 - TomTom
3个回答

1
我只回答你问题的第一部分:使用内置的csv模块无法实现这一点。
查看CPython源代码,quotechar选项仅在字段开头被处理
    case START_FIELD:
        /* expecting field */
        ...
        else if (c == dialect->quotechar &&
                 dialect->quoting != QUOTE_NONE) {
            /* start quoted field */
            self->state = IN_QUOTED_FIELD;
        }
        ...
        break;

在一个领域内,没有这样的检查
    case IN_FIELD:
        /* in unquoted field */
        if (c == '\n' || c == '\r' || c == '\0') {
            /* end of line - return [fields] */
            if (parse_save_field(self) < 0)
                return -1;
            self->state = (c == '\0' ? START_RECORD : EAT_CRNL);
        }
        else if (c == dialect->escapechar) {
            /* possible escaped character */
            self->state = ESCAPED_CHAR;
        }
        else if (c == dialect->delimiter) {
            /* save field - wait for new field */
            if (parse_save_field(self) < 0)
                return -1;
            self->state = START_FIELD;
        }
        else {
            /* normal character - save in field */
            if (parse_add_char(self, module_state, c) < 0)
                return -1;
        }
        break;

在解析器处于 IN_QUOTED_FIELD 状态时,会检查 quotechar;然而,遇到引号后,它会返回到 IN_FIELD 状态,表示我们在未引用的字段内。因此,这是可能的:

>>> import csv
>>> import io
>>> print(next(csv.reader(io.StringIO('"a,b"cd,e'))))
['a,bcd', 'e']

但一旦解析器到达初始引号部分的末尾,它将把任何后续的引号视为数据的一部分。我不知道这种行为是符合任何(书面或非书面)CSV规范,还是只是一个错误。

尽管这并没有解决我的问题,但感谢您提供了关于为什么csv模块无法做到这一点的见解! - TomTom

1
数据格式不标准,因此任何解决方案都需要在完整数据集上进行测试。一个可能的解决方法是首先将,"字符替换为; ,然后只需在;上拆分即可。这可以在不使用CSV或RE的情况下完成:
tests = [
    '"a,b"-"c,d","a,b"-"c,d"',
    '"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-CD-0,"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-LS-0',
]

for test in tests:
    row = test.replace(',"' , ';"').split(';')
    print(len(row), row)

给予:

2 ['"a,b"-"c,d"', '"a,b"-"c,d"']
2 ['"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-CD-0', '"3-Amino-1,2,4-triazole"-text-0-"3-Amino-1,2,4-triazole"-LS-0'

0
如果结构总是相同的,逗号夹在整数和'"'之间,您可以使用正则表达式:
import re

re.split('(?<=[0-9]),(?=")', text)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接