2010-12-08 65 views
23

J'ai besoin de 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:Comment extraire une url non-ASCII avec Python urlopen?

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 pas la chance de le changer . Quel est le moyen d'accéder à une ressource pointée par une URL contenant des caractères non-ascii en utilisant Python?

modifier: En d'autres termes, peut/comment urlopen ouvrir une URL comme:

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

Répondre

42

Strictement parlant, les URI ne peuvent pas contenir de caractères non-ASCII; ce que vous avez là est un IRI.

Pour convertir un IRI à un URI ASCII:

  • caractères non-ASCII dans la partie du nom d'hôte de l'adresse doivent être codées en utilisant l'algorithme Punycode à base IDNA;

  • caractères non-ASCII dans le chemin, et la plupart des autres parties de l'adresse doivent être codés en utilisant UTF-8 et% -encoding, selon la réponse d'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, il est pas tout à fait assez bon dans le cas général, car urlparse ne se fend pas loin tout suffixe de préfixe user:[email protected] ou :port sur le nom d'hôte Seul le nom d'hôte. la partie doit être codée IDNA Il est plus facile d'encoder en utilisant urllib.quote et .encode('idna') normal au moment où vous construisez une URL que d'avoir à séparer un IRI.)

+1

Bien que cela semble être un problème de niche, il est certain que j'ai résolu un problème très spécifique. Très bonne réponse. –

+1

Comment gérer cela élégamment dans Python 3? Aucune suggestion? – zeekvfu

+0

Cela fonctionne vraiment bien pour servir des fichiers où le nom peut contenir des caractères non-américains tels que les symboles kanji! –

6

Encode le unicode en UTF-8, puis URL-encoder.

+0

merci pour la réponse. Pouvez-vous être plus précis s'il vous plaît? 'unicode (url, 'utf-8')' déclenche 'TypeError: décodage Unicode n'est pas supporté'. aussi quelle fonction suggérez-vous pour encoder l'url? urlencode est par exemple pour construire une chaîne de requête. mais le mien est seulement un chemin sur le serveur. – omat

+2

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

+2

Pour la première partie, vous voulez 'url.encode ('utf-8') '(en supposant que' url' est un objet 'unicode'). –

4

Utilisez iri2uri méthode de . Il fait la même chose que par Bobin (est-il/elle l'auteur de cela?)

11

Python 3 a des bibliothèques pour gérer cette situation. Utilisez urllib.parse.urlsplit pour diviser l'URL en ses composants, et urllib.parse.quote pour correctement citer/échapper les caractères unicode et urllib.parse.urlunsplit pour le joindre ensemble.

>>> 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 Que voulez-vous dire par là que ça ne marche pas? Fonctionne parfaitement pour moi. – darkfeline

7

Dans 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) 
+0

Simple et efficace! : D – bodruk

+0

Beaucoup mieux que les autres réponses. – nobism

1

Pour ceux qui ne dépendent pas strictement urllib, une alternative pratique est requests, qui gère IRIs « de la boîte ".

Par exemple, avec http://bücher.ch:

>>> import requests 
>>> r = requests.get(u'http://b\u00DCcher.ch') 
>>> r.status_code 
200 
1

Il est plus complexe que l'a accepté @ réponse de bobince suggère:

  • Netloc doit être codé en utilisant IDNA;
  • Le chemin d'URL non-ASCII doit être codé en UTF-8, puis en pourcentage;
  • Les paramètres de requête non ascii doivent être codés selon le codage de l'URL d'une page qui a été extrait (ou utilisé par le serveur de codage), puis évité en pourcentage.

Voici comment fonctionnent tous les navigateurs. il est spécifié dans https://url.spec.whatwg.org/ - voir ce example. 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 est incorrect échapper la mise en œuvre/incomplète est de vérifier si l'argument fournit page encoding 'ou non.

0

Sur la base de 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