问题细节
近期在用lxml处理某个网页HTML源码时发现<font>
标签的结束标签位置会被改变,具体来说是<font>
标签本身包围了一些<p>
标签,当<font>
外存在<div
标签时,<font>
标签的结束标签</font>
位置会被改变,原本被包围在中间的的<p>
标签全部变成<font>
的兄弟节点。
寻找原因
一开始直接在 google 搜索问题原因,但一直没找到类似的问题。于是开始从使用的模块入手。笔者使用的HTML导入模块是lxml.html.soupparser
,这个模块的文档中并没提到相关的问题,不过模块调用了BeautifulSoup
模块处理HTML,于是查看了BeautifulSoup的源码。果然找到了影响这两个标签的部分:
1 | #According to the HTML standard, each of these inline tags can |
也就是BeautifulSoup
是根据HTML标准来规范那些不规范的HTML,其中这两类标签根据标准只能包围类别中的标签。
## 解决方法
修改源码
最简单的方法就是直接删掉BeautifulSoup
源码中这个list中的标签。不过这种方法弊端也很明显:会影响其他项目中引用的BeautifulSoup
功能。要不影响其他项目可以把修改后的BeautifulSoup
源码放在此项目中。不过这样也会影响此项目中其他用到BeautifulSoup
的部分。
继承修改
更普遍的做法是继承之后通过子类修改相应属性。
这里有个问题,由于我们使用的实际是lxml
,但修改属性只能通过继承BeautifulSoup
,所以要写两个新类分别继承两个模块,这样略显麻烦。幸运的是,在查看源码的过程中发现lxml.html.soupparser.fromstring()
方法中有一个beautifulsoup
参数,如果没有提供这个参数则默认使用BeautifulSoup
。这样就简单多了,不需要动lxml
部分,只需要写个BeautifulSoup的新类即可:
1 | from BeautifulSoup import BeautifulSoup |
虽然在源码中影响这两个标签的属性是NESTABLE_INLINE_TAGS
和NESTABLE_BLOCK_TAGS
,但实际测试发现直接覆写这两个属性没有效果,修改NESTABLE_TAGS
和RESET_NESTING_TAGS
则可以达到预期效果。
## 延伸问题
除了这种改变标签顺序的问题,lxml在导入HTML时还会影响其他结构,比如添加修改引号,修改标签多个属性的顺序等。这些问题可以当作是模块对非标准化源码(broken html)的标准化处理,在处理少量规范网页时没什么影响,但如果是要处理大量不规范网页则需要多多注意。