77 votes

Convertir des entités XML/HTML en chaînes Unicode en Python

Je fais du scraping web et les sites utilisent fréquemment des entités HTML pour représenter des caractères non ascii. Est-ce que Python a un utilitaire qui prend une chaîne avec des entités HTML et renvoie un type unicode ?

Par exemple :

Je reviens :

ǎ

qui représente un "a" avec une marque de tonalité. En binaire, ce chiffre est représenté par le 01ce (16 bits). Je veux convertir l'entité html en la valeur u'\u01ce'

0 votes

61voto

Le HTMLParser de la librairie standard possède une fonction non documentée unescape() qui fait exactement ce que vous pensez qu'elle fait :

jusqu'à Python 3.4 :

import HTMLParser
h = HTMLParser.HTMLParser()
h.unescape('© 2010') # u'\xa9 2010'
h.unescape('© 2010') # u'\xa9 2010'

Python 3.4+ :

import html
html.unescape('© 2010') # u'\xa9 2010'
html.unescape('© 2010') # u'\xa9 2010'

0 votes

8 votes

Cette méthode n'est pas documentée dans la documentation HTMLParser de Python, et un commentaire dans le source indique qu'elle est destinée à un usage interne. Cependant, elle fonctionne comme un charme dans Python 2.6 et 2.7, et c'est probablement la meilleure solution qui existe. Avant la version 2.6, il ne décodait que les entités nommées comme & ou > .

7 votes

Il est exposé comme html.unescape() dans Python 3.4+

60voto

dF. Points 29787

Python dispose de la htmlentitydefs mais celui-ci n'inclut pas de fonction pour désencoder les entités HTML.

Le développeur Python Fredrik Lundh (auteur de elementtree, entre autres) dispose d'une telle fonction sur son site internet qui fonctionne avec des entités décimales, hexagonales et nommées :

import re, htmlentitydefs

##
# Removes HTML or XML character references and entities from a text string.
#
# @param text The HTML (or XML) source text.
# @return The plain text, as a Unicode string, if necessary.

def unescape(text):
    def fixup(m):
        text = m.group(0)
        if text[:2] == "&#":
            # character reference
            try:
                if text[:3] == "&#x":
                    return unichr(int(text[3:-1], 16))
                else:
                    return unichr(int(text[2:-1]))
            except ValueError:
                pass
        else:
            # named entity
            try:
                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
            except KeyError:
                pass
        return text # leave as is
    return re.sub("&#?\w+;", fixup, text)

0 votes

Absolument. Pourquoi n'est-il pas dans la stdlib ?

0 votes

En regardant son code, il ne semble pas fonctionner avec & et autres, n'est-ce pas ?

0 votes

Vient d'être testé avec succès pour &

18voto

chryss Points 3420

Utilisez la fonction intégrée unichr -- BeautifulSoup n'est pas nécessaire :

>>> entity = '&#x01ce'
>>> unichr(int(entity[3:],16))
u'\u01ce'

2 votes

Mais cela implique que vous sachiez automatiquement et sans ambiguïté à quel endroit de la chaîne se trouve le ou les caractères Unicode encodés - ce que vous ne pouvez pas savoir. Et vous devez try...catch l'exception qui en résulte en cas d'erreur.

0 votes

unichar a été supprimé dans python3. Une suggestion pour cette version ?

16voto

pragmar Points 435

Une alternative, si vous avez lxml :

>>> import lxml.html
>>> lxml.html.fromstring('&#x01ce').text
u'\u01ce'

0 votes

Attention cependant, car cela peut également renvoyer un objet de type str s'il n'y a pas de caractère spécial.

0 votes

La meilleure solution quand tout échoue, seul lxml vient à la rescousse :)

8voto

J.F. Sebastian Points 102961

Vous pourriez trouver une réponse ici -- Obtenir des caractères internationaux à partir d'une page web ?

EDIT : Il semble que BeautifulSoup ne convertit pas les entités écrites sous forme hexadécimale. Cela peut être corrigé :

import copy, re
from BeautifulSoup import BeautifulSoup

hexentityMassage = copy.copy(BeautifulSoup.MARKUP_MASSAGE)
# replace hexadecimal character reference by decimal one
hexentityMassage += [(re.compile('&#x([^;]+);'), 
                     lambda m: '&#%d;' % int(m.group(1), 16))]

def convert(html):
    return BeautifulSoup(html,
        convertEntities=BeautifulSoup.HTML_ENTITIES,
        markupMassage=hexentityMassage).contents[0].string

html = '<html>&#x01ce;&#462;</html>'
print repr(convert(html))
# u'\u01ce\u01ce'

EDIT :

unescape() mentionnée par @dF qui utilise htmlentitydefs module standard et unichr() pourrait être plus approprié dans ce cas.

0 votes

Cette solution ne fonctionne pas avec l'exemple : print BeautifulSoup('<html>ǎ</html>', convertEntities=BeautifulSoup.HTML_ENTITIES) Ceci renvoie la même entité HTML

0 votes

Note : ceci ne s'applique qu'à BeautifulSoup 3, déprécié et considéré comme obsolète depuis 2012. BeautifulSoup 4 gère automatiquement les entités HTML de ce type.

0 votes

@MartijnPieters : c'est exact. html.unescape() est une meilleure option sur le Python moderne.

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