验证CSV格式是否正确

0 投票
6 回答
8962 浏览
提问于 2025-04-15 14:07

我希望用户能在一个网页表单上上传一个最大为1MB的CSV文件,这个文件需要符合特定的格式,类似于:

"<String>","<String>",<Int>,<Float>

这个文件会在之后进行处理。我想确认这个文件符合指定的格式,这样后续使用这个文件的程序就不会收到意外的数据输入,也能避免一些安全问题(比如说,防止对解析脚本的注入攻击,这个脚本会进行一些计算和数据库插入)。

(1) 有什么好的方法可以快速又全面地做到这一点吗?根据我查找的信息,我可以考虑使用正则表达式,或者类似于这个的方法。我也看过Python的CSV模块,但它似乎没有内置的验证功能。

(2) 如果我选择使用正则表达式,有人能告诉我最好的做法吗?我应该匹配非法字符并拒绝这些文件吗?(比如说,不允许出现'/'、'\'、'<'、'>'、'{'、'}'等字符)还是说我应该匹配所有合法字符,比如说字符串部分可以是[a-zA-Z0-9]{1,10}?我对正则表达式不太熟悉,所以如果能给我一些指引或例子就太好了。

编辑: 字符串中不应该包含逗号或引号,只能包含名字(比如说,名字和姓氏)。对了,我忘了补充,它们会被双引号包围。

编辑 #2: 感谢大家的回答。Cutplace很有趣,但它是一个独立的工具。最后我决定使用pyparsing,因为它在添加更多格式时提供了更大的灵活性。

6 个回答

1

这是我写的一个小代码片段:

import csv 

f = csv.reader(open("test.csv"))

for value in f:
    value[0] = str(value[0])
    value[1] = str(value[1])
    value[2] = int(value[2])
    value[3] = float(value[3])

如果你用一个格式不符合你指定要求的文件来运行这个代码,你会遇到一个错误:

$ python valid.py 
Traceback (most recent call last):
  File "valid.py", line 8, in <module>
    i[2] = int(i[2])
ValueError: invalid literal for int() with base 10: 'a3'

你可以使用一个try-except来捕捉这个ValueError错误,这样就能告诉用户他们哪里做错了。

2

我建议先解析文件,检查每条记录是否有4个部分,前两个部分是字符串,第三个部分是整数(要检查是否是NaN),第四个部分是浮点数(同样要检查是否是NaN)。

用Python来做这个工作非常合适。

我不知道Python有没有专门用来验证CSV文件是否符合规范的库,但其实写一个这样的程序应该不会太难。

import csv
import math

dataChecker = csv.reader(open('data.csv'))
for row in dataChecker:
    if len(row) != 4:
        print 'Invalid row length.'
        return

    my_int = int(row[2])
    my_float = float(row[3])

    if math.isnan(my_int):
        print 'Bad int found'
        return

    if math.isnan(my_float):
        print 'Bad float found'
        return

print 'All good!'
4

Pyparsing这个工具可以处理这些数据,而且它对一些意外情况很宽容,比如逗号前后有空格、引号内有逗号等等。(csv模块也是这样,但用正则表达式的解决方案需要到处加上"\s*"这样的东西。)

from pyparsing import *

integer = Regex(r"-?\d+").setName("integer")
integer.setParseAction(lambda tokens: int(tokens[0]))
floatnum = Regex(r"-?\d+\.\d*").setName("float")
floatnum.setParseAction(lambda tokens: float(tokens[0]))
dblQuotedString.setParseAction(removeQuotes)
COMMA = Suppress(',')
validLine = dblQuotedString + COMMA + dblQuotedString + COMMA + \
        integer + COMMA + floatnum + LineEnd()

tests = """\
"good data","good2",100,3.14
"good data" , "good2", 100, 3.14
bad, "good","good2",100,3.14
"bad","good2",100,3
"bad","good2",100.5,3
""".splitlines()

for t in tests:
    print t
    try:
        print validLine.parseString(t).asList()
    except ParseException, pe:
        print pe.markInputline('?')
        print pe.msg
    print

输出结果是

"good data","good2",100,3.14
['good data', 'good2', 100, 3.1400000000000001]

"good data" , "good2", 100, 3.14
['good data', 'good2', 100, 3.1400000000000001]

bad, "good","good2",100,3.14
?bad, "good","good2",100,3.14
Expected string enclosed in double quotes

"bad","good2",100,3
"bad","good2",100,?3
Expected float

"bad","good2",100.5,3
"bad","good2",100?.5,3
Expected ","

你可能在将来的某个时候需要去掉那些引号,pyparsing可以在解析的时候通过添加以下内容来做到这一点:

dblQuotedString.setParseAction(removeQuotes)

如果你想在输入文件中添加注释支持,比如在行首加一个'#'后面跟着这一行的其余部分,你可以这样做:

comment = '#' + restOfline
validLine.ignore(comment)

你还可以给这些字段添加名字,这样你就可以通过名字来访问它们,而不是通过索引位置(我发现这样写的代码在将来修改时更稳健):

validLine = dblQuotedString("key") + COMMA + dblQuotedString("title") + COMMA + \
        integer("qty") + COMMA + floatnum("price") + LineEnd()

然后你的后处理代码可以这样做:

data = validLine.parseString(t)
print "%(key)s: %(title)s, %(qty)d in stock at $%(price).2f" % data
print data.qty*data.price

撰写回答