在Python中高效使用HTMLParser

5 投票
2 回答
3547 浏览
提问于 2025-04-16 07:05

针对Python 正则表达式的问题,我尝试用HTMLParser来实现一个HTML解析器:

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

在这个过程中,我在想这个模块的接口是不是不好,或者我是不是忽略了一些重要的东西。我的问题是:

  • 为什么我实现的handle_charref要这么复杂?我本以为一个好的接口应该直接把代码点作为参数传递,而不是像x6c72这样的字符串。
  • 为什么handle_charref的默认实现不直接用合适的字符串来调用handle_data
  • 为什么没有一个可以直接调用的handle_entityref的实用实现?它可以叫做handle_entityref_HTML4,然后查找HTML 4中定义的实体,再调用handle_data

如果有这样的接口,写自定义的HTML解析器会简单很多。那么我到底哪里理解错了呢?

2 个回答

0

你需要自己写一个解析器,还是可以用现成的呢?可以看看Beautiful Soup这个工具。

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

... 当然,我们还应该加上更多的输入验证,并且把整数转换的部分放在异常处理代码里。

这样的话,基本的功能大约只需要10行代码。加上异常处理后,可能会把行数翻倍。

撰写回答