Подтвердить что ты не робот

Прочтите CSV файл из URL-адреса в Python 3.x - _csv.Error: iterator должен возвращать строки, а не байты (вы открыли файл в текстовом режиме?)

Я слишком долго боролся с этой простой проблемой, поэтому я подумал, что попрошу о помощи. Я пытаюсь прочитать список статей журнала с сайта 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?)

Предполагаю, что я должен работать со строками, а не байтами? Любая помощь с простой проблемой и объяснение того, что происходит неправильно, будут с благодарностью.

4b9b3361

Ответ 1

Проблема основана на возврате байтов 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 файл очень большой, он может замедляться и потреблять память. Возможно, было бы предпочтительнее использовать генератор.

EDIT: Использование кодеков, предложенных Стивеном Румбальским, поэтому нет необходимости читать весь файл для декодирования. Уменьшено потребление памяти и увеличена скорость.

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

Обратите внимание, что список не создается по той же причине.

Ответ 2

Несмотря на то, что уже есть принятый ответ, я думал, что добавлю к телу знания, показывая, как я достиг чего-то подобного, используя пакет requests (который иногда рассматривается как альтернатива urlib.request).

Основа использования codecs.itercode() для решения исходной проблемы по-прежнему такая же, как в принятом ответе .

import requests
from contextlib import closing
import csv

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.

Некоторые из идей (например, с помощью closing()) выбираются из этого похожего сообщения

Ответ 3

urlopen вернет экземпляр urllib.response.addinfourl для запроса ftp.

Для URL-адресов, файлов и данных URL-адресов и запросов, явно обработанных устаревшим URLopener и FancyURLopener, эта функция возвращает urllib.response.addinfourl, который может работать как менеджер контекста...

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

В этот момент ftpstream является файлом типа object, использование .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-адресом

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']