Lea el archivo .csv de la URL en Python 3.x – _csv.Error: el iterador debe devolver cadenas, no bytes (¿abrió el archivo en modo de texto?)

He estado luchando con este simple problema durante demasiado tiempo, así que pensé que pediría ayuda. Estoy tratando de leer una lista de artículos de revistas del sitio ftp de la Biblioteca Nacional de Medicina en Python 3.3.2 (en Windows 7). Los artículos de la revista están en un archivo .csv.

He intentado el siguiente código:

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] 

Da como resultado el siguiente error:

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

¿Supongo que debería estar trabajando con cadenas no bytes? Cualquier ayuda con el problema simple, y una explicación de lo que está mal sería muy apreciada.

El problema se basa en los bytes de retorno urllib . Como prueba, puede intentar descargar el archivo csv con su navegador y abrirlo como un archivo normal y el problema desaparecerá.

Un problema similar fue abordado aquí .

Se puede resolver decodificando bytes en cadenas con la encoding apropiada. Por ejemplo:

 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] 

La última línea también podría ser: data = list(csvfile) que puede ser más fácil de leer.

Por cierto, dado que el archivo csv es muy grande, puede ralentizarse y consumir memoria. Tal vez sería preferible utilizar un generador.

EDITAR: Usar los códecs propuestos por Steven Rumbalski para que no sea necesario leer todo el archivo para decodificar. Consumo de memoria reducido y velocidad aumentada.

 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 

Tenga en cuenta que la lista tampoco se crea por el mismo motivo.

Aunque ya hay una respuesta aceptada, pensé que agregaría al cuerpo de conocimiento al mostrar cómo logré algo similar utilizando el paquete de requests (que a veces se ve como una alternativa a urlib.request ).

La base de usar codecs.itercode() para resolver el problema original sigue siendo la misma que en la respuesta aceptada .

 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 

Aquí también vemos el uso de la transmisión por secuencias a través del paquete de requests para evitar tener que cargar primero todo el archivo a través de la red en la memoria (lo que podría llevar mucho tiempo si el archivo es grande).

Pensé que podría ser útil ya que me ayudó, ya que estaba usando requests lugar de urllib.request en Python 3.6.

Algunas de las ideas (p. Ej., Usar closing() ) se seleccionan de esta publicación similar

urlopen devolverá una instancia urllib.response.addinfourl para una solicitud ftp.

Para las direcciones URL de ftp, archivos y datos, así como las solicitudes de información explícitas manejadas por URLopener heredadas y clases FancyURLopener, esta función devuelve un objeto urllib.response.addinfourl que puede funcionar como administrador de contexto …

 >>> urllib2.urlopen(url) >> 

En este punto, ftpstream es un archivo como objeto, el uso de .read() devolvería el contenido, sin embargo, csv.reader requiere una iterable en este caso:

Definiendo un generador así:

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

Podemos crear nuestro lector csv así:

 reader = csv.reader(to_lines(ftps)) 

Y con una url

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

El código:

 for row in reader: print row 

Huellas dactilares

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