53 votes

Comment dé-citer une chaîne unicode urlencodée en python ?

J'ai une chaîne unicode comme "Tanm" qui est codée comme "Tan%u0131m" en quelque sorte. Comment puis-je reconvertir cette chaîne encodée en unicode original ? Apparemment, urllib.unquote ne supporte pas l'unicode.

70voto

Aaron Maenpaa Points 39173

%uXXXX est un schéma de codage non standard qui a été rejeté par le w3c, malgré le fait qu'une implémentation continue de vivre au pays du JavaScript.

La technique la plus courante semble être de coder la chaîne en UTF-8, puis d'échapper les octets résultants en utilisant %XX. Ce schéma est supporté par urllib.unquote :

>>> urllib2.unquote("%0a")
'\n'

Malheureusement, si vous voulez vraiment besoin de pour prendre en charge %uXXXX, vous devrez probablement mettre au point votre propre décodeur. Sinon, il est probablement bien plus préférable de simplement encoder votre unicode en UTF-8, puis d'échapper les octets résultants en %.

Un exemple plus complet :

>>> u"Tanım"
u'Tan\u0131m'
>>> url = urllib.quote(u"Tanım".encode('utf8'))
>>> urllib.unquote(url).decode('utf8')
u'Tan\u0131m'

4 votes

Urllib2.unquote' devrait être 'urllib.unquote'.

0 votes

Il est intéressant de noter qu'un URI est une chaîne d'octets codée en pourcentage, plutôt qu'une chaîne de caractères.

1 votes

@jamtoday pas nécessairement, dans Python 2.7.5+ vous pouvez utiliser urllib2.unquote essayez juste print(dir(urllib2))

11voto

Markus Jarderot Points 33893
def unquote(text):
    def unicode_unquoter(match):
        return unichr(int(match.group(1),16))
    return re.sub(r'%u([0-9a-fA-F]{4})',unicode_unquoter,text)

0 votes

Cela ne fonctionne que pour Python 2, malheureusement, qui est qui approche rapidement de sa fin de vie . Il n'est pas difficile de corriger pour rendre ce Python 2 et 3 compatible ( try: unichr , except NameError: unichr = chr ), mais cette version ne gère pas les paires de substituts. L'intention de la %hhhh Le format d'échappement consistait à encoder UTF-16 Ainsi, pour les séquences non-BMP (telles qu'un grand nombre d'emoji), vous obtiendrez une chaîne de caractères non valide, sauf avec une version UCS-2 Python 2.

7voto

Ali Afshar Points 22836

Cela fera l'affaire si vous devez absolument l'avoir (je suis vraiment d'accord avec les cris de "non standard") :

from urllib import unquote

def unquote_u(source):
    result = unquote(source)
    if '%u' in result:
        result = result.replace('%u','\\u').decode('unicode_escape')
    return result

print unquote_u('Tan%u0131m')

> Tanım

1 votes

Un cas légèrement pathologique, mais : unquote_u('Tan%25u0131m') --> u'Tan \u0131m plutôt que 'Tan%u0131' comme il se doit. C'est un rappel de la raison pour laquelle vous ne voulez probablement pas écrire un décodeur à moins que vous n'en ayez vraiment besoin.

0 votes

Je suis tout à fait d'accord. C'est pourquoi je ne tenais pas vraiment à offrir une solution réelle. Ces choses ne sont jamais aussi simples. L'O.P. était peut-être désespéré, et je pense que cela complète votre excellente réponse.

1 votes

Cela ne fonctionne que pour Python 2, malheureusement, qui est qui approche rapidement de sa fin de vie . L'utilisation de unicode_escape rend la correction pour Python 3 un peu plus difficile (il faut d'abord encoder en utf-8), mais cette version ne gère pas les paires de substituts. L'intention du %hhhh Le format d'échappement consistait à encoder UTF-16 Ainsi, pour les séquences non-BMP (telles qu'un grand nombre d'emoji), vous obtiendrez une chaîne de caractères non valide, sauf avec une version UCS-2 Python 2.

4voto

Il y a un bogue dans la version ci-dessus où il se déclenche parfois lorsqu'il y a à la fois des caractères codés ascii et des caractères codés unicode dans la chaîne. Je pense que c'est spécifiquement lorsqu'il y a des caractères de la gamme 128 supérieure comme ' \xab en plus de l'unicode.

eg. "%5B%AB%u03E1%BB%5D" provoque cette erreur.

J'ai découvert que si vous faites d'abord les unicodes, le problème disparaît :

def unquote_u(source):
  result = source
  if '%u' in result:
    result = result.replace('%u','\\u').decode('unicode_escape')
  result = unquote(result)
  return result

0 votes

\xab n'est pas un caractère mais un octet. En effet, votre exemple de "chaîne" contient à la fois des octets et des caractères, ce qui n'est valable comme chaîne unique dans aucun langage que je connaisse.

1 votes

Qu'est-ce que "%5B%AB%u03E1%BB%5D" décoder comme ? 0x5B 0xAB et 0xBB 0x5D ne sont pas des séquences UTF-8 valides.

0 votes

@wberry : J'ai vu des cas réels (une bibliothèque Java quelque part) qui encodent certains points de code ASCII comme les espaces en %hh et tout ce qui dépasse 0x7F à %uhhhh séquences. Terrible, mais analysable.

2voto

Martijn Pieters Points 271458

Vous avez une URL utilisant un schéma de codage non standard rejeté par les organismes de normalisation, mais toujours produit par certains codeurs. Le système Python urllib.parse.unquote() ne peut pas les gérer.

Créer son propre décodeur n'est pas si difficile, heureusement. %uhhhh les entrées sont destinées à être UTF-16 points de code ici, donc nous devons prendre paires de substituts en compte. J'ai également vu %hh des points de code mélangés, pour plus de confusion.

En gardant cela à l'esprit, voici un décodeur qui fonctionne à la fois avec Python 2 et Python 3, à condition de passer un fichier str dans Python 3 (Python 2 s'en soucie moins) :

try:
    # Python 3
    from urllib.parse import unquote
    unichr = chr
except ImportError:
    # Python 2
    from urllib import unquote

def unquote_unicode(string, _cache={}):
    string = unquote(string)  # handle two-digit %hh components first
    parts = string.split(u'%u')
    if len(parts) == 1:
        return parts
    r = [parts[0]]
    append = r.append
    for part in parts[1:]:
        try:
            digits = part[:4].lower()
            if len(digits) < 4:
                raise ValueError
            ch = _cache.get(digits)
            if ch is None:
                ch = _cache[digits] = unichr(int(digits, 16))
            if (
                not r[-1] and
                u'\uDC00' <= ch <= u'\uDFFF' and
                u'\uD800' <= r[-2] <= u'\uDBFF'
            ):
                # UTF-16 surrogate pair, replace with single non-BMP codepoint
                r[-2] = (r[-2] + ch).encode(
                    'utf-16', 'surrogatepass').decode('utf-16')
            else:
                append(ch)
            append(part[4:])
        except ValueError:
            append(u'%u')
            append(part)
    return u''.join(r)

Cette fonction est fortement inspirée de la implémentation actuelle de la bibliothèque standard .

Démonstration :

>>> print(unquote_unicode('Tan%u0131m'))
Tanım
>>> print(unquote_unicode('%u05D0%u05D9%u05DA%20%u05DE%u05DE%u05D9%u05E8%u05D9%u05DD%20%u05D0%u05EA%20%u05D4%u05D8%u05E7%u05E1%u05D8%20%u05D4%u05D6%u05D4'))
איך ממירים את הטקסט הזה
>>> print(unquote_unicode('%ud83c%udfd6'))  # surrogate pair

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X