Necesito obtener datos de una URL con caracteres que no sean ascii, pero urllib2.urlopen se niega a abrir el recurso y genera:
UnicodeEncodeError: 'ascii' codec can't encode character u'\u0131' in position 26: ordinal not in range(128)
Sé que la URL no cumple con los estándares, pero no tengo la oportunidad de cambiarla.
¿Cuál es la manera de acceder a un recurso señalado por una URL que contiene caracteres no-ascii usando Python?
editar: En otras palabras, ¿puede / cómo urlopen abrir una URL como:
http://example.org/Ñöñ-ÅŞÇİİ/
En términos estrictos, los URI no pueden contener caracteres que no sean ASCII; Lo que tienes allí es un IRI .
Para convertir un IRI en un URI ASCII simple:
Los caracteres que no son ASCII en la parte del nombre de host de la dirección deben codificarse utilizando el algoritmo IDNA basado en Punycode ;
Los caracteres que no son ASCII en la ruta, y la mayoría de las otras partes de la dirección deben codificarse utilizando UTF-8 y%-encoding, según la respuesta de Ignacio.
Asi que:
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'
(Técnicamente, esto todavía no es lo suficientemente bueno en el caso general porque urlparse
no separa a ningún user:pass@
prefix o :port
sufix en el nombre de host. Solo la parte del nombre de host debe estar codificada por IDNA. Es más fácil de codificar usando normal urllib.quote
y .encode('idna')
en el momento en que estás construyendo una URL para tener que separar un IRI).
Python 3 tiene bibliotecas para manejar esta situación. Use urllib.parse.urlsplit
para dividir la URL en sus componentes, y urllib.parse.quote
para citar / escapar adecuadamente los caracteres Unicode y urllib.parse.urlunsplit
para unirlos nuevamente.
>>> 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, use la función urllib.parse.quote
en la cadena que no es ascii:
>>> from urllib.request import urlopen >>> from urllib.parse import quote >>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页') >>> urlopen(chinese_wikipedia)
Codifique el unicode
a UTF-8, luego codifique la URL.
Utilice el método httplib2
de httplib2
. Hace lo mismo que por Bobin (¿es él / ella el autor de eso?)
Es más complejo de lo que sugiere la respuesta aceptada de @ bobince:
Así es como funcionan todos los navegadores; se especifica en https://url.spec.whatwg.org/ – vea este ejemplo . Se puede encontrar una implementación de Python en w3lib (esta es la biblioteca que Scrapy está usando); vea w3lib.url.safe_url_string :
from w3lib.url import safe_url_string url = safe_url_string(u'http://example.org/Ñöñ-ÅŞÇİİ/', encoding="")
Una forma fácil de verificar si una implementación de escape de URL es incorrecta / incompleta es verificar si proporciona un argumento de ‘encoding de página’ o no.
Para aquellos que no dependen estrictamente de urllib, una alternativa práctica son las solicitudes , que manejan los IRI “fuera de la caja”.
Por ejemplo, con http://bücher.ch
:
>>> import requests >>> r = requests.get(u'http://b\u00DCcher.ch') >>> r.status_code 200
Basado en la respuesta 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
No pude evitar estos extraños personajes, pero al final llegué a través de él.
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")