Convertir des entités XML/HTML en chaîne Unicode en Python [dupliquer]
Question
Cette question a déjà une réponse ici :
Je fais du web scraping et les sites utilisent fréquemment des entités HTML pour représenter des caractères non ascii.Python dispose-t-il d'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 "ǎ" avec une marque de ton.En binaire, cela est représenté par le 01ce 16 bits.Je veux convertir l'entité HTML en valeur u'\u01ce'
La solution
Le HTMLParser de la bibliothèque standard possède une fonction non documentée unescape() qui fait exactement ce que vous pensez :
import HTMLParser
h = HTMLParser.HTMLParser()
h.unescape('© 2010') # u'\xa9 2010'
h.unescape('© 2010') # u'\xa9 2010'
Autres conseils
Python a le htmlentitydefs module, mais cela n'inclut pas de fonction pour annuler l'échappement des entités HTML.
Le développeur Python Fredrik Lundh (auteur d'elementtree, entre autres) a une telle fonction sur son site internet, qui fonctionne avec des entités décimales, hexadécimales 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)
Utilisez le module intégré unichr
-- BeautifulSoup n'est pas nécessaire :
>>> entity = 'ǎ'
>>> unichr(int(entity[3:],16))
u'\u01ce'
Une alternative, si vous avez lxml :
>>> import lxml.html
>>> lxml.html.fromstring('ǎ').text
u'\u01ce'
Si vous utilisez Python 3.4 ou plus récent, vous pouvez simplement utiliser le html.unescape
:
import html
s = html.unescape(s)
Vous pourriez trouver une réponse ici -- Obtenir des caractères internationaux à partir d'une page Web ?
MODIFIER:Il semble que BeautifulSoup
ne convertit pas les entités écrites sous forme hexadécimale.Il 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>ǎǎ</html>'
print repr(convert(html))
# u'\u01ce\u01ce'
MODIFIER:
unescape()
fonction mentionnée par @dF qui utilise htmlentitydefs
module standard et unichr()
pourrait être plus approprié dans ce cas.
Il s'agit d'une fonction qui devrait vous aider à bien faire les choses et à reconvertir les entités en caractères utf-8.
def unescape(text):
"""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.
from Fredrik Lundh
2008-01-03: input only unicode characters string.
http://effbot.org/zone/re-sub.htm#unescape-html
"""
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:
print "Value Error"
pass
else:
# named entity
# reescape the reserved characters.
try:
if text[1:-1] == "amp":
text = "&amp;"
elif text[1:-1] == "gt":
text = "&gt;"
elif text[1:-1] == "lt":
text = "&lt;"
else:
print text[1:-1]
text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
except KeyError:
print "keyerror"
pass
return text # leave as is
return re.sub("&#?\w+;", fixup, text)
Je ne sais pas pourquoi le thread de débordement de pile n'inclut pas le ';' ' dans la recherche / remplacer (c'est-à-direlambdam :'&#%d*;*') Si vous ne le faites pas, BeautifulSoup peut vomir car le caractère adjacent peut être interprété comme faisant partie du code HTML (c'est-à-dire'B pour 'Blackout).
Cela a mieux fonctionné pour moi :
import re
from BeautifulSoup import BeautifulSoup
html_string='<a href="/cgi-bin/article.cgi?f=/c/a/2010/12/13/BA3V1GQ1CI.DTL"title="">'Blackout in a can; on some shelves despite ban</a>'
hexentityMassage = [(re.compile('&#x([^;]+);'),
lambda m: '&#%d;' % int(m.group(1), 16))]
soup = BeautifulSoup(html_string,
convertEntities=BeautifulSoup.HTML_ENTITIES,
markupMassage=hexentityMassage)
- Le int(m.group(1), 16) reconvertit le format numérique (spécifié en base 16) en un entier.
- m.group(0) renvoie la correspondance entière, m.group(1) renvoie le groupe de capture d'expression rationnelle
- Fondamentalement, utiliser markupMessage revient à :
html_string = re.sub('&#x([^;]+);', lambda m :'&#%d;' % int (M.Group (1), 16), html_string)
Une autre solution est la bibliothèque intégrée xml.sax.saxutils (à la fois pour HTML et XML).Cependant, il convertira uniquement >, & et <.
from xml.sax.saxutils import unescape
escaped_text = unescape(text_to_escape)
Voici la version Python 3 de La réponse de DF:
import re
import html.entities
def unescape(text):
"""
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 fixup(m):
text = m.group(0)
if text[:2] == "&#":
# character reference
try:
if text[:3] == "&#x":
return chr(int(text[3:-1], 16))
else:
return chr(int(text[2:-1]))
except ValueError:
pass
else:
# named entity
try:
text = chr(html.entities.name2codepoint[text[1:-1]])
except KeyError:
pass
return text # leave as is
return re.sub("&#?\w+;", fixup, text)
Les principaux changements concernent htmlentitydefs
c'est maintenant html.entities
et unichr
c'est maintenant chr
.Regarde ça Guide de portage Python 3.