Comment récupérer une url non-ascii avec urlopen Python?
je dois récupérer des données à partir d'une URL avec des caractères non-ascii mais urllib2.urlopen refuse d'ouvrir la ressource et soulève:
UnicodeEncodeError: 'ascii' codec can't encode character u'u0131' in position 26: ordinal not in range(128)
je sais que L'URL n'est pas conforme aux normes mais je n'ai aucune chance de la changer.
comment accéder à une ressource pointée par une URL contenant des caractères non-ascii en utilisant Python?
edit: en d'autres termes, peut / Comment urlopen ouvrir une URL comme:
http://example.org/Ñöñ-ÅŞÇİİ/
8 réponses
à proprement parler, les URIs ne peuvent pas contenir de caractères non ASCII; ce que vous avez là est un IRI.
pour convertir un IRI en un simple URI ASCII:
les caractères non-ASCII dans la partie nom d'hôte de l'adresse doivent être encodés en utilisant le Punycodebase de IDNA algorithme;
caractères non-ASCII dans le chemin, et la plupart des autres parties de l'adresse doivent être encodées en UTF-8 et %-encoding, selon la réponse D'Ignacio.
Donc:
import re, urlparse
def urlEncodeNonAscii(b):
return re.sub('[\x80-\xFF]', lambda c: '%%%02x' % ord(c.group(0)), b)
def iriToUri(iri):
parts= urlparse.urlparse(iri)
return urlparse.urlunparse(
part.encode('idna') if parti==1 else urlEncodeNonAscii(part.encode('utf-8'))
for parti, part in enumerate(parts)
)
>>> iriToUri(u'http://www.a\u0131b.com/a\u0131b')
'http://www.xn--ab-hpa.com/a%c4%b1b'
(techniquement ce n'est pas encore assez bon dans le cas général parce que urlparse
ne pas couper tout de suite user:pass@
préfixe ou :port
suffixe sur le nom d'hôte. Seule la partie nom d'hôte doit être codée IDNA. Il est plus facile de coder en utilisant la normale urllib.quote
et .encode('idna')
au moment où vous construisez une URL, vous n'avez pas à séparer une IRI.)
Python 3 a des bibliothèques pour gérer cette situation. Utiliser
urllib.parse.urlsplit
pour diviser L'URL en ses composants, et
urllib.parse.quote
pour citer correctement/échapper aux caractères unicode
et urllib.parse.urlunsplit
pour le rejoindre.
>>> import urllib.parse
>>> url = 'http://example.com/unicodè'
>>> url = urllib.parse.urlsplit(url)
>>> url = list(url)
>>> url[2] = urllib.parse.quote(url[2])
>>> url = urllib.parse.urlunsplit(url)
>>> print(url)
http://example.com/unicod%C3%A8
En python3, utilisez le urllib.parse.quote
function sur la non-ascii string:
>>> from urllib.request import urlopen
>>> from urllib.parse import quote
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页')
>>> urlopen(chinese_wikipedia)
Utiliser iri2uri
méthode httplib2
. Il fait la même chose que par bobin (il/elle est l'auteur de qui?)
C'est plus complexe que la réponse acceptée de @bobince suggère:
- netloc doit être encodé en utilisant IDNA;
- le chemin D'URL non-ascii doit être encodé en UTF-8 puis en pourcentage;
- les paramètres de requête non ascii doivent être encodés à l'encodage D'une page dont L'URL a été extraite (ou aux usages du serveur d'encodage), puis échappés en pourcentage.
C'est ainsi que tous les navigateurs fonctionnent; il est spécifié dans https://url.spec.whatwg.org/ - voir cette exemple. Une implémentation Python peut être trouvée dans w3lib (C'est la bibliothèque que Scrapy utilise); voir w3lib.URL.safe_url_string:
from w3lib.url import safe_url_string
url = safe_url_string(u'http://example.org/Ñöñ-ÅŞÇİİ/', encoding="<page encoding>")
un moyen facile de vérifier si une URL échappant l'implémentation est incorrecte/incomplète est de vérifier si elle fournit l'argument 'page encoding' ou non.
D'après la réponse de @darkfeline:
from urllib.parse import urlsplit, urlunsplit, quote
def iri2uri(iri):
"""
Convert an IRI to a URI (Python 3).
"""
uri = ''
if isinstance(iri, str):
(scheme, netloc, path, query, fragment) = urlsplit(iri)
scheme = quote(scheme)
netloc = netloc.encode('idna').decode('utf-8')
path = quote(path)
query = quote(query)
fragment = quote(fragment)
uri = urlunsplit((scheme, netloc, path, query, fragment))
return uri