【问题标题】: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?)

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

【问题讨论】:

    标签: python url csv python-3.x


    【解决方案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']
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-21
        • 2016-04-07
        • 2020-12-17
        • 2023-01-22
        • 2018-03-24
        • 2020-05-06
        相关资源
        最近更新 更多