web-dev-qa-db-fra.com

Lire le fichier .csv de l'URL dans Python 3.x - _csv.Error: l'itérateur doit retourner des chaînes, pas des octets (avez-vous ouvert le fichier en mode texte?)

Je lutte depuis trop longtemps avec ce problème simple, alors j'ai pensé demander de l'aide. J'essaie de lire une liste d'articles de journaux du site ftp de la Bibliothèque nationale de médecine dans Python 3.3.2 (sous Windows 7). Les articles de journaux sont dans un fichier .csv.

J'ai essayé le code suivant:

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]

Il en résulte l'erreur suivante:

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?)

Je suppose que je devrais travailler avec des chaînes et non des octets? Toute aide concernant le problème simple et une explication de ce qui ne va pas seraient grandement appréciées.

29
Chris

Le problème repose sur urllib renvoyant des octets. Pour preuve, vous pouvez essayer de télécharger le fichier csv avec votre navigateur et de l'ouvrir en tant que fichier normal et le problème a disparu.

Un problème similaire a été résolu ici .

Il peut être résolu en décodant les octets en chaînes avec le codage approprié. Par exemple:

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 dernière ligne pourrait également être: data = list(csvfile) qui peut être plus facile à lire.

Soit dit en passant, étant donné que le fichier csv est très volumineux, il peut ralentir et consommer de la mémoire. Il serait peut-être préférable d'utiliser un générateur.

EDIT: Utilisation des codecs comme proposé par Steven Rumbalski donc il n'est pas nécessaire de lire le fichier entier à décoder. Consommation de mémoire réduite et vitesse augmentée.

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

Notez que la liste n'est pas créée non plus pour la même raison.

43
Diego Herranz

Même s'il existe déjà une réponse acceptée, j'ai pensé ajouter au corps des connaissances en montrant comment j'ai réalisé quelque chose de similaire en utilisant le package requests (qui est parfois considéré comme une alternative à urlib.request).

La base de l'utilisation de codecs.itercode() pour résoudre le problème d'origine est toujours la même que dans réponse acceptée .

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   

Ici, nous voyons également l'utilisation de streaming fourni via le package requests afin d'éviter d'avoir à charger le fichier entier sur le réseau dans la mémoire d'abord (ce qui peut prendre du temps si le fichier est volumineux).

J'ai pensé que cela pourrait être utile car cela m'a aidé, car j'utilisais requests plutôt que urllib.request Dans Python 3.6.

Certaines des idées (par exemple en utilisant closing()) sont choisies dans cette similaire post

8
Irvin H.