2008-09-11 6 views
65

Я делаю некоторые веб-соскабливания, и сайты часто используют объекты HTML для представления символов не ascii. У Python есть утилита, которая берет строку с объектами HTML и возвращает тип юникода?Преобразование XML/HTML-объектов в строку Unicode в Python

Например:

я вернусь:

ǎ 

, которая представляет собой "ǎ" с отметкой тона. В двоичном формате это представлено как 16 бит 01ce. Я хочу, чтобы преобразовать HTML объект в значение u'\u01ce'

+0

связанные с: [Decode HTML сущности в строке Python?] (Http://stackoverflow.com/q/2087370/4279) – jfs 2016-02-02 08:36:49

ответ

57

Python имеет htmlentitydefs модуль, но это не включает в себя функцию экранирования в HTML сущности.

Python разработчик Фредрик Lundh (автор ElementTree, между прочим) имеет такую ​​функцию on his website, которая работает с десятичным, шестнадцатеричным и названными объектами:

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

Вы можете найти ответ здесь - Getting international characters from a web page?

EDIT: Похоже, BeautifulSoup не преобразует объекты, написанные в шестнадцатеричном виде. Это может быть исправлено:

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() функция упоминается @dF, которая использует htmlentitydefs стандартный модуль и unichr() может быть более подходящим в данном случае.

+0

Это решение не работает на примере: печати BeautifulSoup (» & # x01ce ;», convertEntities = BeautifulSoup.HTML_ENTITIES) Это возвращает тот же HTML сущности – Cristian 2008-09-11 22:03:56

+0

Я редактировал ответ – jfs 2008-09-12 14:25:55

+0

Примечание: это относится только к BeautifulSoup 3, устаревшее и считается наследие с 2012 года BeautifulSoup 4 обрабатывает HTML-сущности, как это автоматически , – 2017-09-05 07:28:32

18

Используйте встроенный unichr - BeautifulSoup не надо:

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

Но это требует, чтобы вы автоматически и недвусмысленно знали, где в строке находится кодированный символ Юникода, - который вы не можете знать. И вам нужно «попробовать ... уловить» возникающее исключение, когда вы ошибетесь. – smci 2012-08-13 11:22:40

+0

`unichar` удален в python3. Любое предложение для этой версии? – Splatmistro 2018-01-30 15:31:14

6

Это функция, которая должна помочь вам получить это правильно и преобразовать объекты обратно в символы 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;amp;" 
      elif text[1:-1] == "gt": 
       text = "&amp;gt;" 
      elif text[1:-1] == "lt": 
       text = "&amp;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) 
3

Не знаете, почему поток переполнения стека не включает ';' в поиске/замене (т.е. lambda m: '& #% d * ; *') Если вы этого не сделаете, BeautifulSoup может быть barf, потому что смежный символ может быть интерпретирован как часть HTML-кода (то есть & # 39B для & # 39Blackout).

Это работало лучше для меня:

import re 
from BeautifulSoup import BeautifulSoup 

html_string='<a href="/cgi-bin/article.cgi?f=/c/a/2010/12/13/BA3V1GQ1CI.DTL"title="">&#x27;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) 
  1. формате INT (m.group (1), 16) преобразует номер (указанный в базе-16) обратно в целое число.
  2. m.group (0) возвращает весь матч, m.group (1) возвращает регулярное выражение группы записи
  3. В основном с помощью markupMessage такого же, как:
    html_string = re.sub (»& #x ([ ^;] +); ', lambda m:' & #% d; '% int (m.группа (1), 16), html_string)
16

Альтернатива, если у вас есть LXML:

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

собственный HTMLparser для стандартных библитеков имеет недокументированную функцию экранирование в(), которая делает именно то, что вы думаете он делает:

import HTMLParser 
h = HTMLParser.HTMLParser() 
h.unescape('&copy; 2010') # u'\xa9 2010' 
h.unescape('&#169; 2010') # u'\xa9 2010' 
9

Если вы на Python 3.4 или новее, вы можете просто использовать html.unescape:

s = html.unescape(s) 
1

Другое решение - встроенная библиотека xml.sax.saxutils (как для html, так и для xml). Однако он будет преобразовывать только & gt, & amp и & lt.

from xml.sax.saxutils import unescape 

escaped_text = unescape(text_to_escape) 
0

Вот версия Python 3 из dF's answer:

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) 

Основной проблемой изменения htmlentitydefs, что теперь html.entities и unichr что теперь chr. См. Это Python 3 porting guide.