web-dev-qa-db-fra.com

Comment récupérer une URL non-ASCII avec Python urlopen?

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/Ñöñ-ÅŞÇİİ/
32
omat

À 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.)

48
bobince

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
16
darkfeline

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)
13
Perry

Encodez la unicode en UTF-8, puis l'URL-encoder.

5

Utilisez la méthode iri2uri de httplib2. Cela fait la même chose que par bobin (est-il l'auteur de cela?)

4
eviltnan

C'est plus complexe que ne le suggère la réponse acceptée de @ bobince:

  • netloc devrait être encodé en utilisant IDNA;
  • le chemin d’URL non ASCII doit être encodé en UTF-8, puis échappé pour cent;
  • les paramètres de requête non ascii doivent être codés en fonction du codage de l'URL d'une page à extraire (ou aux utilisations du serveur de codage), puis du pourcentage d'échappement.

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'.

3
Mikhail Korobov

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

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

travaux! enfin

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")
0
Giovanni Gianni