Python中利用xpath解析HTML的方法


Posted in Python onMay 14, 2018

在进行网页抓取的时候,分析定位html节点是获取抓取信息的关键,目前我用的是lxml模块(用来分析XML文档结构的,当然也能分析html结构), 利用其lxml.html的xpath对html进行分析,获取抓取信息。

首先,我们需要安装一个支持xpath的python库。目前在libxml2的网站上被推荐的python binding是lxml,也有beautifulsoup,不嫌麻烦的话还可以自己用正则表达式去构建,本文以lxml为例讲解。

假设有如下的HTML文档:

<html>
 <body>
  <form>
   <div id='leftmenu'>
    <h3>text</h3>
    <ul id='china'><!-- first location -->
     <li>...</li>
     <li>...</li>
       ......
    </ul>
    <ul id='england'><!-- second location-->
     <li>...</li>
     <li>...</li>
       ......
    </ul>
   </div>
  </form>
 </body>
</html>

直接使用lxml处理:

import codecs
 from lxml import etree
 f=codecs.open("ceshi.html","r","utf-8")
 content=f.read()
 f.close()
 tree=etree.HTML(content)

etree提供了HTML这个解析函数,现在我们可以直接对HTML使用xpath了,是不是有点小激动,现在就尝试下吧。

在使用xpath之前我们先来看看作为对照的jQuery和RE。

在jQuery里要处理这种东西就很简单,特别是假如那个ul节点有id的话(比如是<ul id='china'>):

$("#china").each(function(){...});

具体到此处是:

$("#leftmenu").children("h3:contains('text')").next("ul").each(function(){...});

找到id为leftmenu的节点,在其下找到一个内容包含为”text”的h3节点,再取其接下来的一个ul节点。

在python里要是用RE来处理就略麻烦一些:

block_pattern=re.compile(u"<h3>档案</h3>(.*?)<h3>", re.I | re.S)
m=block_pattern.findall(content)
item_pattern=re.compile(u"<li>(.*?)</li>", re.I | re.S)
items=item_pattern.findall(m[0])
for i in items:
  print i

那么用xpath要怎么做呢?其实跟jQuery是差不多的:

nodes=tree.xpath("/descendant::ul[@id='china']")

当然,现在没有id的话也就只能用类似于jQuery的方法了。完整的xpath应该是这样写的(注意,原文件中的TAG有大小写的情况,但是在XPATH里只能用小写):

nodes=tree.xpath(u"/html/body/form/div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

更简单的方法就是像jQuery那样直接根据id定位:

nodes=tree.xpath(u"//div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")

这两种方法返回的结果中,nodes[0]就是那个“text”的h3节点后面紧跟的第一个ul节点,这样就可以列出后面所有的ul节点内容了。

如果ul节点下面还有其他的节点,我们要找到更深节点的内容,如下的循环就是把这些节点的文本内容列出:

nodes=nodes[0].xpath("li/a")
for n in nodes:
  print n.text

对比三种方法应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行,而RE则纯粹是基于plain text。RE对付简单的页面是没有问题,如果页面结构复杂度较高的时候(比如一堆的DIV来回嵌套之类),设计一个恰当的RE pattern可能会远比写一个xpath要复杂。特别是目前主流的基于CSS的页面设计方式,其中大部分关键节点都会有id??对于使用jQuery的页面来说则更是如此,这时xpath相比RE就有了决定性的优势。

 附录:基本XPATH语法介绍,详细请参考XPath的官方文档

XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点(注意,不是指文档最外层的tag节点,而是指文档本身)。比如对于一个HTML文件来说,最外层的节点应该是”/html”。

同样的,“..”和“.”分别被用来表示父节点和本节点。

XPATH返回的不一定就是唯一的节点,而是符合条件的所有节点。比如在HTML文档里使用“/html/head/scrpt”就会把head里的所有script节点都取出来。

为了缩小定位范围,往往还需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。比如在HTML文档里使用“/html/body/div[@id='main']”,即可取出body里id为main的div节点。

其中@id表示属性id,类似的还可以使用如@name, @value, @href, @src, @class….

而 函数text()的意思则是取得节点包含的文本。比如:<div>hello<p>world</p>< /div>中,用”div[text()='hello']“即可取得这个div,而world则是p的text()。

函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点,它也可以被省略为“li[2]”。

不过要注意的是数字定位和过滤 条件的顺序。比如“ul/li[5][@name='hello']”表示取ul下第五项li,并且其name必须是hello,否则返回空。而如果用 “ul/li[@name='hello'][5]”的意思就不同,它表示寻找ul下第五个name为”hello“的li节点。

此外,“*”可以代替所有的节点名,比如用”/html/body/*/span”可以取出body下第二级的所有span,而不管它上一级是div还是p或是其它什么东东。

而 “descendant::”前缀可以指代任意多层的中间节点,它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的 div,可以用“/descendant::div[@id='leftmenu']”,也可以简单地使用“ //div[@id='leftmenu']”。

至于“following-sibling::”前缀就如其名所说,表示同一层的下一个节点。”following-sibling::*”就是任意下一个节点,而“following-sibling::ul”就是下一个ul节点。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python使用os模块的os.walk遍历文件夹示例
Jan 27 Python
python3 发送任意文件邮件的实例
Jan 23 Python
python3实现域名查询和whois查询功能
Jun 21 Python
Python 绘制酷炫的三维图步骤详解
Jul 12 Python
浅析python中while循环和for循环
Nov 19 Python
如何定义TensorFlow输入节点
Jan 23 Python
对python中各个response的使用说明
Mar 28 Python
解决django无法访问本地static文件(js,css,img)网页里js,cs都加载不了
Apr 07 Python
Python的历史与优缺点整理
May 26 Python
如何卸载python插件
Jul 08 Python
Python基础之Socket通信原理
Apr 22 Python
python 中的jieba分词库
Nov 23 Python
利用pyinstaller将py文件打包为exe的方法
May 14 #Python
django的登录注册系统的示例代码
May 14 #Python
django允许外部访问的实例讲解
May 14 #Python
运行django项目指定IP和端口的方法
May 14 #Python
python使用Flask操作mysql实现登录功能
May 14 #Python
查看Django和flask版本的方法
May 14 #Python
Python处理中文标点符号大集合
May 14 #Python
You might like
PHP 和 MySQL 开发的 8 个技巧
2007/01/02 PHP
PHP mail 通过Windows的SMTP发送邮件失败的解决方案
2009/05/27 PHP
linux下使用ThinkPHP需要注意大小写导致的问题
2011/08/02 PHP
php使用curl和正则表达式抓取网页数据示例
2014/04/13 PHP
windows的文件系统机制引发的PHP路径爆破问题分析
2014/07/28 PHP
php读取目录及子目录下所有文件名的方法
2014/10/20 PHP
JS父页面与子页面相互传值方法
2014/03/05 Javascript
JavaScript中的prototype和constructor简明总结
2014/04/05 Javascript
关于JavaScript中name的意义冲突示例介绍
2014/05/29 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
Javascript学习笔记之 函数篇(三) : 闭包和引用
2014/11/23 Javascript
js实现同一页面可多次调用的图片幻灯切换效果
2015/02/28 Javascript
用JavaScript显示浏览器客户端信息的超相近教程
2015/06/18 Javascript
JavaScript设计模式之单例模式详解
2017/06/09 Javascript
一个基于react的图片裁剪组件示例
2018/04/18 Javascript
vue+element+Java实现批量删除功能
2019/04/08 Javascript
JavaScript this使用方法图解
2020/02/04 Javascript
vuecli3.x中轻松4步带你使用tinymce的步骤
2020/06/25 Javascript
[01:09:23]KG vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
[51:27]LGD vs Liquid 2019国际邀请赛小组赛 BO2 第二场 8.16
2019/08/19 DOTA
Python中的闭包实例详解
2014/08/29 Python
用Python的Django框架完成视频处理任务的教程
2015/04/02 Python
python与C互相调用的方法详解
2017/07/14 Python
纽约和芝加哥当天送花:Ode à la Rose
2019/07/05 全球购物
Otiumberg官网:英国半精致珠宝品牌
2021/01/16 全球购物
学校百日安全生产活动总结
2014/07/05 职场文书
挂职学习心得体会
2014/09/09 职场文书
领导干部作风整顿剖析材料
2014/10/11 职场文书
教师正风肃纪剖析材料
2014/10/20 职场文书
法人身份证明书
2015/06/18 职场文书
Python3 使用pip安装git并获取Yahoo金融数据的操作
2021/04/08 Python
详解MySQL 用户权限管理
2021/04/20 MySQL
详解Go与PHP的语法对比
2021/05/29 PHP
使用pipenv管理python虚拟环境的全过程
2021/09/25 Python
你知道Java Spring的两种事务吗
2022/03/16 Java/Android
Nginx实现会话保持的两种方式
2022/03/18 Servers