问题细节
近期在用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)的标准化处理,在处理少量规范网页时没什么影响,但如果是要处理大量不规范网页则需要多多注意。