在 Python 中高效使用 HTMLParser

Rol*_*lig 5 python api html-parsing

为了响应Python 正则表达式,我尝试使用以下方法实现 HTML 解析器HTMLParser

import HTMLParser

class ExtractHeadings(HTMLParser.HTMLParser):

  def __init__(self):
    HTMLParser.HTMLParser.__init__(self)
    self.text = None
    self.headings = []

  def is_relevant(self, tagname):
    return tagname == 'h1' or tagname == 'h2'

  def handle_starttag(self, tag, attrs):
    if self.is_relevant(tag):
      self.in_heading = True
      self.text = ''

  def handle_endtag(self, tag):
    if self.is_relevant(tag):
      self.headings += [self.text]
      self.text = None

  def handle_data(self, data):
    if self.text != None:
      self.text += data

  def handle_charref(self, name):
    if self.text != None:
      if name[0] == 'x':
        self.text += chr(int(name[1:], 16))
      else:
        self.text += chr(int(name))

  def handle_entityref(self, name):
    if self.text != None:
      print 'TODO: entity %s' % name

def extract_headings(text):
  parser = ExtractHeadings()
  parser.feed(text)
  return parser.headings

print extract_headings('abdk3<h1>The content we need</h1>aaaaabbb<h2>The content we need2</h2>')
print extract_headings('before<h1>&#72;e&#x6c;&#108;o</h1>after')
Run Code Online (Sandbox Code Playgroud)

这样做时我想知道这个模块的 API 是否不好或者我是否没有注意到一些重要的事情。我的问题是:

  • 为什么我的实现handle_charref必须那么复杂?我本来期望一个好的 API 将代码点作为参数传递,而不是x6c作为72字符串传递。
  • handle_charref为什么调用的默认实现不handle_data使用适当的字符串?
  • 为什么没有handle_entityref我可以调用的实用程序实现?它可以被命名handle_entityref_HTML4并查找 HTML 4 中定义的实体,然后调用handle_data它们。

如果提供了该 API,编写自定义 HTML 解析器就会容易得多。那么我的理解误区在哪里呢?

Jim*_*nis 1

嗯,我倾向于同意 HTMLParser 不包含将 HTML 实体引用转换为正常 ASCII 和/或其他字符的代码是一个可怕的疏忽。我认为 Python3 中完全不同的工作可以解决这个问题。

然而,我们似乎可以编写一个相当简单的实体处理程序,如下所示:

import htmlentitydefs
def entity2char(x):
    if x.startswith('&#x'):
        # convert from hexadecimal
        return chr(int(x[3:-1], 16))
    elif x.startswith('&#'):
        # convert from decimal
        return chr(int(x[2:-1]))
    elif x[1:-1] in htmlentitydefs.entitydefs:
        return htmlentitydefs.entitydefs[x[1:-1]]
    else:
        return x
Run Code Online (Sandbox Code Playgroud)

...尽管我们应该包装进一步的输入验证,并将整数转换包装在异常处理代码中。

但这应该可以处理最少大约 10 行代码。添加异常处理可能会使行数增加一倍。