41

我一直在为这个简单的问题苦苦挣扎太久,所以我想我会寻求帮助。我正在尝试将国家医学图书馆 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?)

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

4

4 回答 4

57

问题依赖于urllib返回字节。作为证明,您可以尝试使用浏览器下载 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.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

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

于 2013-09-19T14:26:09.740 回答
14

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

用于解决原始问题的基础codecs.itercode()仍然与接受的答案相同。

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的使用,以避免必须首先通过网络将整个文件加载到内存中(如果文件很大,这可能需要很长时间)。

我认为它可能很有用,因为它对我有帮助,因为我正在使用requests而不是urllib.request在 Python 3.6 中。

一些想法(例如 using closing())是从这个类似的帖子中挑选出来的

于 2017-03-23T15:09:00.570 回答
0

我在使用requestspackage 和csv. 发布请求的响应是 type 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']
于 2020-11-17T23:53:30.357 回答
-1

urlopen将返回一个urllib.response.addinfourlftp 请求的实例。

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

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

此时ftpstream是一个类似文件对象,使用.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']
于 2013-09-19T14:17:18.920 回答