J'ai besoin d'extraire des données d'une URL contenant 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 le changer.
Comment accéder à une ressource désignée par une URL contenant des caractères non-ascii à l'aide de Python?
edit: Autrement dit, comment/urlopen peut-il ouvrir une URL telle que:
http://example.org/Ñöñ-ÅŞÇİİ/
À proprement parler, les URI ne peuvent pas contenir de caractères non-ASCII. ce que vous avez est un IRI .
Pour convertir un IRI en un simple ASCII URI:
les caractères non-ASCII dans la partie de l'adresse correspondant au nom d'hôte doivent être codés à l'aide de l'algorithme IDNA basé sur Punycode -;
les caractères non-ASCII dans le chemin et la plupart des autres parties de l'adresse doivent être codés à l'aide de UTF-8 et du codage%, conformément à la réponse de Ignacio.
Alors:
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, cela n’est toujours pas suffisant dans le cas général, car urlparse
ne divise pas le préfixe user:pass@
ou le suffixe :port
sur le nom d’hôte. Seule la partie du nom d’hôte doit être codée IDNA. C’est plus simple pour encoder en utilisant normal urllib.quote
et .encode('idna')
au moment de la construction d’une URL, puis de séparer un IRI.)
Python 3 a des bibliothèques pour gérer cette situation. Utilisez urllib.parse.urlsplit
pour scinder l'URL en ses composants et urllib.parse.quote
pour bien citer/échapper les caractères unicode Et urllib.parse.urlunsplit
pour les associer.
>>> 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 la fonction urllib.parse.quote
sur la chaîne non-ascii:
>>> from urllib.request import urlopen
>>> from urllib.parse import quote
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页')
>>> urlopen(chinese_wikipedia)
Encodez la unicode
en UTF-8, puis l'URL-encoder.
Utilisez la méthode iri2uri
de httplib2
. Cela fait la même chose que par bobin (est-il l'auteur de cela?)
C'est plus complexe que ne le suggère la réponse acceptée de @ bobince:
Voici comment fonctionnent tous les navigateurs. il est spécifié dans https://url.spec.whatwg.org/ - voir cet exemple . Une implémentation Python peut être trouvée dans w3lib (c'est la librairie 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 simple de vérifier si une implémentation d'échappement URL est incorrecte/incomplète consiste à vérifier si elle fournit ou non l'argument 'encodage de page'.
Pour ceux qui ne dépendent pas strictement d'urllib, une alternative pratique est demandes , qui gère les adresses IRI "prêtes à l'emploi".
Par exemple, avec http://bücher.ch
:
>>> import requests
>>> r = requests.get(u'http://b\u00DCcher.ch')
>>> r.status_code
200
Basé sur la réponse @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
Je ne pouvais pas éviter de ces personnages étranges, mais à la fin je le traverse.
import urllib.request
import os
url = "http://www.fourtourismblog.it/le-nuove-tendenze-del-marketing-tenere-docchio/"
with urllib.request.urlopen(url) as file:
html = file.read()
with open("marketingturismo.html", "w", encoding='utf-8') as file:
file.write(str(html.decode('utf-8')))
os.system("marketingturismo.html")