Прочтите 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?)
Предполагаю, что я должен работать со строками, а не байтами? Любая помощь с простой проблемой и объяснение того, что происходит неправильно, будут с благодарностью.
Ответы
Ответ 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']