¿Cómo obtener una url no ascii con Python urlopen?

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:

  • Netloc debe codificarse utilizando IDNA;
  • la ruta de la URL que no sea ascii se debe codificar en UTF-8 y luego se debe escapar al porcentaje;
  • los parámetros de consulta no ascii deben codificarse para que la encoding de una página URL se extraiga de (o para los usos del servidor de encoding), luego se escape el porcentaje.

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 

¡trabajos! finalmente

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