将 .csv 文件从 URL 读取到 Python 3.x - _csv.Error:迭代器应返回字符串,而不是字节(您是不是以文本模式打开文件?)

Posted

技术标签:

【中文标题】将 .csv 文件从 URL 读取到 Python 3.x - _csv.Error:迭代器应返回字符串,而不是字节(您是不是以文本模式打开文件?)【英文标题】:Read .csv file from URL into Python 3.x - _csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)将 .csv 文件从 URL 读取到 Python 3.x - _csv.Error:迭代器应返回字符串,而不是字节(您是否以文本模式打开文件?) 【发布时间】:2013-09-24 16:14:16 【问题描述】:

我已经为这个简单的问题苦苦挣扎了太久,所以我想我会寻求帮助。我正在尝试将国家医学图书馆 ftp 站点的期刊文章列表读入 Python 3.3.2(在 Windows 7 上)。期刊文章位于 .csv 文件中。

我已经尝试了以下代码:

import csv
import urllib.request

url = "ftp://ftp.ncbi.nlm.nih.gov/pub/pmc/file_list.csv"
ftpstream = urllib.request.urlopen(url)
csvfile = csv.reader(ftpstream)
data = [row for row in csvfile]

这会导致以下错误:

Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
data = [row for row in csvfile]
File "<pyshell#4>", line 1, in <listcomp>
data = [row for row in csvfile]
_csv.Error: iterator should return strings, not bytes (did you open the file in text mode?)

我认为我应该使用字符串而不是字节?对于这个简单问题的任何帮助,以及对出了什么问题的解释将不胜感激。

【问题讨论】:

【参考方案1】:

urlopen 将为 ftp 请求返回一个 urllib.response.addinfourl 实例。

对于 ftp、文件和数据 url 以及由 legacy 显式处理的请求 URLopener 和 FancyURLopener 类,这个函数返回一个 urllib.response.addinfourl 对象,可以作为上下文管理器...

>>> urllib2.urlopen(url)
<addinfourl at 48868168L whose fp = <addclosehook at 48777416L whose fp = <socket._fileobject object at 0x0000000002E52B88>>>

此时ftpstream 是一个文件like 对象,使用.read() 将返回内容但是csv.reader 在这种情况下需要一个可迭代对象:

像这样定义一个生成器:

def to_lines(f):
    line = f.readline()
    while line:
        yield line
        line = f.readline()

我们可以像这样创建我们的 csv 阅读器:

reader = csv.reader(to_lines(ftps))

还有一个网址

url = "http://pic.dhe.ibm.com/infocenter/tivihelp/v41r1/topic/com.ibm.ismsaas.doc/reference/CIsImportMinimumSample.csv"

代码:

for row in reader: print row

打印

>>> 
['simpleci']
['SCI.APPSERVER']
['SRM_SaaS_ES', 'MXCIImport', 'AddChange', 'EN']
['CI_CINUM']
['unique_identifier1']
['unique_identifier2']

【讨论】:

不正确。 StringIO 是一个 Python 2 模块。答案需要针对 Python 3。这尤其重要,因为 Python 3 处理字符串的方式。 @StevenRumbalski 我认为使用docs.python.org/3.4/library/io.html#io.StringIO 会好吗? StringIO 不接受字节:TypeError: initial_value must be str or None, not bytes @StevenRumbalski 看到我更新的答案,它没有读入整个文件或使用 stringIO【参考方案2】:

问题依赖于urllib 返回字节。作为证明,您可以尝试使用浏览器下载 csv 文件并将其作为常规文件打开,问题就消失了。

here 解决了类似的问题。

可以通过适当的编码将字节解码为字符串。例如:

import csv
import urllib.request

url = "ftp://ftp.ncbi.nlm.nih.gov/pub/pmc/file_list.csv"
ftpstream = urllib.request.urlopen(url)
csvfile = csv.reader(ftpstream.read().decode('utf-8'))  # with the appropriate encoding 
data = [row for row in csvfile]

最后一行也可以是:data = list(csvfile),这样更容易阅读。

顺便说一句,由于 csv 文件非常大,它可能会很慢并且会消耗内存。也许最好使用生成器。

编辑: 使用 Steven Rumbalski 提出的编解码器,因此无需读取整个文件进行解码。内存消耗减少,速度提高。

import csv
import urllib.request
import codecs

url = "ftp://ftp.ncbi.nlm.nih.gov/pub/pmc/file_list.csv"
ftpstream = urllib.request.urlopen(url)
csvfile = csv.reader(codecs.iterdecode(ftpstream, 'utf-8'))
for line in csvfile:
    print(line)  # do something with line

请注意,该列表也不是出于同样的原因而创建的。

【讨论】:

+1。但是,在解码之前必须读取所有数据感觉有些不对劲。 Python 3 是否提供任何允许作为生成器完成此操作的功能? 想通了。 Python 3 的流式传输方式是使用codecs.iterdecode 添加了一个使用编解码器的 sn-p 版本以使用生成器。 使用responseHeader = response.info(),您甚至可以获取响应标头,从中可以获取正确的编码,例如使用encoding = responseHeader['Content-Type'].split(';')[1].split('=')[1],您可以使用它来解码响应response.read().decode(encoding),因此您不必对编码进行硬编码并对不同的编码做出反应【参考方案3】:

即使已经有一个公认的答案,我想我会通过展示我如何使用requests 包(有时被视为urlib.request 的替代品)实现类似的东西来增加知识体系。

使用codecs.itercode()解决原问题的基础还是和accepted answer一样。

import codecs
from contextlib import closing
import csv
import requests

url = "ftp://ftp.ncbi.nlm.nih.gov/pub/pmc/file_list.csv"

with closing(requests.get(url, stream=True)) as r:
    reader = csv.reader(codecs.iterdecode(r.iter_lines(), 'utf-8'))
    for row in reader:
        print row   

这里我们还看到了通过requests 包提供的流媒体的使用,以避免必须首先通过网络将整个文件加载到内存中(如果文件是大)。

我认为它可能对我有用,因为我在 Python 3.6 中使用的是 requests 而不是 urllib.request

一些想法(例如使用closing())是从这个类似的post中挑选出来的

【讨论】:

【参考方案4】:

我在使用 requests 包和 csv 时遇到了类似的问题。 来自 post 请求的响应类型为 bytes。 为了使用csv库,首先我将它们作为字符串文件存储在内存中(在我的情况下大小很小),解码为utf-8。

import io
import csv
import requests

response = requests.post(url, data)

# response.content is something like: 
# b'"City","Awb","Total"\r\n"Bucuresti","6733338850003","32.57"\r\n'    
csv_bytes = response.content

# write in-memory string file from bytes, decoded (utf-8)
str_file = io.StringIO(csv_bytes.decode('utf-8'), newline='\n')
    
reader = csv.reader(str_file)
for row_list in reader:
    print(row_list)

# Once the file is closed,
# any operation on the file (e.g. reading or writing) will raise a ValueError
str_file.close()

打印类似:

['City', 'Awb', 'Total']
['Bucuresti', '6733338850003', '32.57']

【讨论】:

以上是关于将 .csv 文件从 URL 读取到 Python 3.x - _csv.Error:迭代器应返回字符串,而不是字节(您是不是以文本模式打开文件?)的主要内容,如果未能解决你的问题,请参考以下文章

将数据从 csv 读取到类对象列表中 - Python

python pandas 中文件的读写——read_csv()读取文件

Python 从 CSV 读取数据

gh读取csv文件

python 读取CSV 文件

Python:从 CSV 读取数据类型 LineString