2010-12-08 9 views
23

necesito para obtener los datos desde una URL con caracteres no ASCII, pero urllib2.urlopen se niega a abrir el recurso y plantea:Cómo recuperar una url no ascii con Python urlopen?

UnicodeEncodeError: 'ascii' codec can't encode character u'\u0131' in position 26: ordinal not in range(128) 

Sé que la URL no es compatible con las normas pero no tienen ninguna posibilidad de cambiarlo .

¿Cuál es la manera de acceder a un recurso apuntado por una URL que contiene caracteres no ascii usando Python?

edición: En otras palabras, puede/urlopen cómo abrir una URL como:

http://example.org/Ñöñ-ÅŞÇİİ/ 

Respuesta

42

Estrictamente hablando, los URI no pueden contener caracteres que no sean ASCII; lo que tienes allí es un IRI.

Para convertir un IRI a un plano ASCII URI:

  • caracteres no ASCII en la parte nombre de host de la dirección han de ser codificados usando la Punycode -basado algoritmo IDNA;

  • caracteres no ASCII en la ruta, y la mayoría de las otras partes de la dirección deben codificarse usando UTF-8 y% -coding, según la respuesta de Ignacio.

Así:

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 como en el caso general, ya urlparse no se divide la basura cualquier user:[email protected] prefijo o sufijo :port en el nombre de host Sólo el nombre de host. la parte debe estar codificada con IDNA. Es más fácil codificar utilizando urllib.quote y .encode('idna') normales en el momento en que está construyendo una URL que tener que extraer una IRI).

+1

Aunque este parece ser un problema de nicho, es seguro que resolvió un problema muy específico. Gran respuesta. –

+1

¿Cómo manejar esto elegantemente en Python 3? ¿Alguna sugerencia? – zeekvfu

+0

Esto realmente funciona muy bien para servir archivos donde el nombre puede contener caracteres no estadounidenses como símbolos kanji! –

6

codificar el unicode a UTF-8, a continuación, cifrar la URL de.

+0

gracias por la respuesta. ¿Puede ser más específico, por favor? 'unicode (url, 'utf-8')' plantea 'TypeError: la decodificación de Unicode no es compatible'. también qué función sugieres para codificar url? urlencode por ejemplo es para construir cadena de consulta. pero el mío es solo un camino en el servidor. – omat

+2

http://farmdev.com/talks/unicode/ http://docs.python.org/library/urllib.html#urllib.quote –

+2

Para la primera parte, quiere 'url.encode ('utf-8') '(suponiendo que' url' es un objeto 'unicode'). –

4

Use iri2uri método de . Hace lo mismo que por bobin (¿es él/ella el autor de eso?)

11

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 correctamente los caracteres Unicode y urllib.parse.urlunsplit para unirlos de nuevo.

>>> 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 
+1

@ user230137 ¿Qué quieres decir con que no funciona? Funciona perfectamente para mi – darkfeline

7

En python3, utilice la función urllib.parse.quote en la cadena no ASCII:

>>> from urllib.request import urlopen                                        
>>> from urllib.parse import quote                                         
>>> chinese_wikipedia = 'http://zh.wikipedia.org/wiki/Wikipedia:' + quote('首页') 
>>> urlopen(chinese_wikipedia) 
+0

¡Simple y efectivo! : D – bodruk

+0

Mucho mejor que las otras respuestas. – nobism

1

Para aquellos que no dependen estrictamente en urllib, una alternativa práctica es requests, que se ocupa de 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 
1

Es más complejo que el aceptado @ respuesta de bobince sugiere:

  • netloc debe ser codificado usando IDNA;
  • no-ascii La ruta URL debe estar codificada en UTF-8 y luego porcentaje-escapado;
  • Los parámetros de consulta no ascii deben codificarse para la codificación de una página de la URL de la que se extrajo (o para el servidor de codificación), y luego para el porcentaje de escape.

Así es como funcionan todos los navegadores; está especificado en https://url.spec.whatwg.org/ - vea esto example. Se puede encontrar una implementación de Python en w3lib (esta es la biblioteca que usa Scrapy); ver w3lib.url.safe_url_string:

from w3lib.url import safe_url_string 
url = safe_url_string(u'http://example.org/Ñöñ-ÅŞÇİİ/', encoding="<page encoding>") 

Una forma sencilla de comprobar si un URL escapar de aplicación es incorrecto/incompleto es para verificar si proporciona página de codificación argumento o no.

0

Basado en respuesta @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 
Cuestiones relacionadas