当前位置: 首页 > 科技观察

通用爬虫技术要点:Dom树重构

时间:2023-03-21 10:26:54 科技观察

本题来自读者交流群。原问题如下图所示:这个问题在一般爬虫的开发中确实会涉及到。然而,由于网页的HTML结构千变万化,通用的爬虫需要在事先不知道目标网页的结构的情况下提取内容。在这种情况下,通用爬虫一般分为几个不同的部分,如下图所示:其中,HTML源代码重写组件会按照一定的策略修改网页的源代码,剔除不相关的节点,并合并复杂但没有必要的嵌套节点...重写后输出一个比较标准统一的HTML传递给下游的信息抽取组件进行内容抽取。同学的问题涉及重写源代码。其实使用lxml在DOM树中插入一个节点应该是完全没有问题的。会谷歌的同学,只要搜索lxmlhtmlinsertelement,自然会找到很多解法,如下图所示:不过这个问题比较奇怪的是,需要在元素的前面加上子节点文本节点。Drylectures可能不好描述,所以我用一个例子来说明这个问题。我们先来看这段代码:fromlxml.htmlimportfromstring,Element,etreefromhtmlimportunescapehtml='''

Hello

'''node=fromstring(html)p_node=node.find('.//p')element=Element('span')element.text='青南'p_node.insert(0,element)new_html=unescape(etree.tostring(node).decode())print(new_html)根据根据我们使用Python列表的经验,如果一个列表a现在是['Hello'],当我们执行a.insert(0,'Qingnan')时,结果应该是['Qingnan','Hello']。但是我们来看一下上面代码的运行效果:可以看到,Hello后面是青楠。我们再来看一下本文开头的那张图。在提问者给出的例子中,他希望在正文之前插入子节点。具体在这个例子中,应该是HelloQingnan。大家可以试试,谷歌怎么搜都找不到在文本前面插入节点的方法。但其实只要返回官方文档,就会发现整个问题的解决并不难。我们需要用到的是lxml.html.builder[1]。还是上面的例子,如何获取文本前面的span标签呢?我们使用builder来实现:fromlxml.htmlimportbuilderfromhtmlimportunescapehtml='''
'''node=fromstring(html)new_node=builder.P(builder.SPAN('青南'),'Hello')node.append(new_node)new_html=unescape(etree.tostring(node).decode())print(new_html)运行效果如下图所示:看到这里,可能有同学觉得我在耍流氓。这就像让我写一个程序来计算斐波那契数列前5项的值,所以我在5秒内写出了答案print(1,1,2,3,5)。在上面的代码中,我直接使用了builder.P(builder.SPAN('Qingnan'),'Hello'),这和直接写

QingnanHello

的区别?这不是作弊吗?我知道你不服气,但这是真实的情况。这就是一般的爬虫在重写HTML源代码时所做的事情。因为直接重写网页的Dom树是很麻烦的。如果直接修改Dom树,往往需要找到一个节点的父节点,然后再找到父节点的兄弟节点的子节点进行修改。或者判断一个节点是否有子节点,yesorno,需要两种逻辑来防止对Dom树的破坏。因此,我们一般不会直接修改Dom树,而是使用构建器在扫描原有Dom树的同时,重建一棵新的Dom树。重建Dom树的过程比修改Dom树要简单的多。毕竟,写过代码的人都知道,写新代码要比改别人的代码容易得多。参考资料[1]lxml.html.builder:https://lxml.de/api/lxml.html.builder-module.html本文转载自微信公众号“听不见的代码”,可通过以下二维码关注代码。转载本文请联系Code公众号。