使用Google App Engine上传并解析CSV文件
我在想有没有人能帮我解答一下关于Python和GAE的问题。我正在通过一个表单上传一个CSV文件到GAE的数据存储。
class CSVImport(webapp.RequestHandler):
def post(self):
csv_file = self.request.get('csv_import')
fileReader = csv.reader(csv_file)
for row in fileReader:
self.response.out.write(row)
我遇到了和其他人提到的同样的问题 - http://groups.google.com/group/google-appengine/browse_thread/thread/bb2d0b1a80ca7ac2/861c8241308b9717
也就是说,csv.reader在处理时是逐个字符地读取,而不是按行读取。谷歌的一位工程师给出了这样的解释:
调用self.request.get('csv')会返回一个字符串。当你遍历一个字符串时,你是逐个字符地遍历,而不是按行遍历。你可以在这里看到这个区别:
class ProcessUpload(webapp.RequestHandler):
def post(self):
self.response.out.write(self.request.get('csv'))
file = open(os.path.join(os.path.dirname(__file__), 'sample.csv'))
self.response.out.write(file)
# Iterating over a file
fileReader = csv.reader(file)
for row in fileReader:
self.response.out.write(row)
# Iterating over a string
fileReader = csv.reader(self.request.get('csv'))
for row in fileReader:
self.response.out.write(row)
我真的不太明白这个解释,而且尝试实现的时候也没有成功。有没有人能提供一个更清晰的解释和解决方案呢?
谢谢,
August
3 个回答
你需要使用 csv_file = self.request.POST.get("csv_import")
这个方式,而不是 csv_file = self.request.get("csv_import")
。
第二种方法只会给你一个字符串,就像你在最开始的帖子里提到的那样。但是通过 self.request.POST.get
访问的话,你会得到一个 cgi.FieldStorage 对象。
这意味着你可以用 csv_file.filename
来获取这个对象的文件名,用 csv_file.type
来获取文件的类型。此外,如果你访问 csv_file.file
,它是一个 StringO 对象(这是来自 StringIO 模块 的只读对象),而不仅仅是一个字符串。正如 ig0774 在 他的回答 中提到的,StringIO 模块让你可以把字符串当作文件来使用。
所以,你的代码可以简单写成:
class CSVImport(webapp.RequestHandler):
def post(self):
csv_file = self.request.POST.get('csv_import')
fileReader = csv.reader(csv_file.file)
for row in fileReader:
# row is now a list containing all the column data in that row
self.response.out.write(row)
我想不出比你提到的谷歌工程师更清楚的解释了。那我们来简单拆解一下。
Python的csv
模块是用来处理文件类对象的,也就是文件或者像Python文件那样工作的东西。因此,csv.reader()需要一个文件对象作为它唯一的必要参数。
webapp.RequestHandler
的请求对象可以访问在表单中提交的HTTP参数。在HTTP中,参数是以键值对的形式提交的,比如csv=record_one,record_two
。当你调用self.request.get('csv')
时,它会返回与键csv相关联的值,这个值是一个Python字符串。Python字符串并不是文件类对象。显然,当csv
模块不理解这个对象时,它会退而求其次,简单地逐个字符进行迭代(在Python中,字符串可以逐个字符遍历,比如for c in 'Test String': print c
会把字符串中的每个字符单独打印在一行上)。
幸运的是,Python提供了一个叫做StringIO的类,可以让字符串像文件类对象一样使用。所以(假设GAE支持StringIO,而没有理由不支持),你应该可以这样做:
class ProcessUpload(webapp.RequestHandler):
def post(self):
self.response.out.write(self.request.get('csv'))
# Iterating over a string as a file
stringReader = csv.reader(StringIO.StringIO(self.request.get('csv')))
for row in stringReader:
self.response.out.write(row)
这样就能按你预期的方式工作了。
编辑 我假设你使用的是类似<textarea/>
的东西来收集csv文件。如果你是上传附件,可能需要不同的处理方式(我对Python GAE或它如何处理附件不是很熟悉)。
简短的回答,试试这个:
fileReader = csv.reader(csv_file.split("\n"))
长一点的回答,考虑以下内容:
for thing in stuff:
print thing.strip().split(",")
如果“stuff”是一个文件指针,那么每一项就是一行。如果“stuff”是一个列表,那么每一项就是一个项目。如果“stuff”是一个字符串,那么每一项就是一个字符。
使用csv.reader返回的对象进行遍历,得到的效果就像遍历传入的对象,只不过每一项都是经过CSV解析的。如果你遍历一个字符串,你会得到每个字符的CSV解析版本。