Python爬虫自动化从入门到精通第3天(Xpath和lxml库的使用)

数据解析

当我们将整个网页的内容全部爬取下来,但是这些数据的信息量非常庞大,不仅整体给人非常混乱的感觉,而且大部分数据并不是我们需要的。针对这种情况,需要对爬取的数据进行筛选,去掉没有用的数据,留下有价值的数据。要想过滤网页的数据,先要对服务器返回的数据形式做一些了解,这些数据一般可分为非结构化数据和结构化两种。对于不同类型的数据,需要采用不同的方案进行预处理。

网页数据和结构

网页数据格式

对于服务器来说,他返回给客户端的数据格式分为非结构化和结构化两种,那么,什么是非结构化数据?什么是结构化数据?

非结构化数据是指数据结构不规则或不完整,没有预定义的数据模型,不方便使用数据库二维逻辑来表现的数据,包括所有格式的办公文档、文本、HTML、图像等。

结构化数据就是能够用数据或统一的结构加以表示,具有模式的数据,包括XML和JSON等。

网页结构

想要了解一个网页的结构,可以直接在浏览器的右键菜单种选择“查看页面源代码”命令实现。例如,使用Google Chrome浏览器打开百度首页,右击“新闻”选项,选择“检查”,浏览器底部打开一个窗口,并显示选中元素周围的HTML层次结构,如下图:

在这里插入图片描述
图中带底色的行就是刚刚选择的“新闻”标签。从图中可以清楚看到选中的标签< a >位于id属性值为u1的标签< div >中,并且与其他标签< a >属于并列关系,只是每个标签内部的属性值不同而已。例如,要提取单击“新闻”后跳转的网页,可以获取href属性的值。

数据解析技术

了解网页的数据和结构后,可以借助网页解析器(用于解析网页的工具)从网页中解析和提取出有价值的数据,或者新的URL列表。为此,Python支持一些解析网页的技术,分别为正则表达式(有单独的专栏介绍哦!!!),XPath,Beautiful Soup和JSONPath。其中:

  • 针对文本的解析,有正则表达式。
  • 针对HTML/XML的解析,有XPath,Beautiful Soup,正则表达式
  • 针对JSON的解析,有JSONPath

几者的区别:

类型区别Python中的模块
正则表达式基于文本的特征来匹配或查找指定的数据,它可以处理任何格式的字符串文档,类似于模糊匹配的效果re模块
XPath和Beautiful Soup基于HTML/XML文档的层次来确定到达指定节点的路径,所以他们更适合处理层级比较明显的数据lxml库支持XPath语法的使用,Beautiful Soup本身就是一个Python库
JSONPath专门用于JSON文档的数据解析json模块

几者的性能比较:

爬取工具速度使用难度安装难度
re最快困难无(内置)
lxml简单一般
beautifulsoup4最简单简单

XPath与lxml解析库

与正则的使用不同,XPath是基于文档的层次结构来确定查找路径。借助网上一个例子比喻用来区分正则表达式和XPath。把提取的数据比作找建筑,如果使用正则表达式查找,则他会告诉你这个建筑本身有哪些特点,以及他的左边是什么,右边是什么。这样的描述限定查找范围较大,不易找到。而XPath会直接告诉你这个建筑位于“中国北京海淀区西三环中路电视塔“,相比较而言这种描述更加具体,易于找到。这两种解析网页的方法各有利弊,具体的选择还要看应用场景。为了能在Python中使用XPath语法,提供了一个第三库lxml。

XPath概述

为了能够在XML文档树中准确的找到某个节点,引入了XPath的概念。

XPath即为XML路径语言,用于确定XML树结构中某一部分的位置。XPath技术基于XML的树结构,能够在树结构中遍历节点(元素,属性)。

那么,XPath是如何查找信息呢?XPath使用路径表达式选取XML文档中的节点或者节点集,这些路径表示与常规的计算机文件系统中看到的路径非常相似,代表着从一个节点到另一个或者一组节点的顺序,并以“/”字符进行分隔。

在这里插入图片描述
XPath不仅能够查询XML文档,而且能够查询HTML文档。但是,它需要先借用lxml库技术将HTML文档转换为XML文档树对象,之后可以使用XPath语法查找此结构中的节点或元素。

XPath语法

在python中,XPath使用路径表达式在文档中进行导航。这个表达式是从某个节点开始,之后顺着文档树结构的节点进一步查找。由于查询路径的多样性,可以将XPath的语法按照如下情况进行划分:

  • 选取节点
    节点是沿着路径选取的,既可以从根节点开始,也可以从任意位置开始。
    下面为XPath用来选取节点的表达式:
表达式说明
nodename选取此节点的所有子节点
/从根节点选取
//从匹配选择的当前节点选取文档中的节点,而不用考虑他们的位置
.选取当前节点
选取当前节点的父节点
@选取属性

下面是XML文档示例:

<?xml version="1.0" encoding="UTF-8"?>
<people>
  <person id="1">
    <name>John</name>
    <age>25</age>
    <gender>Male</gender>
  </person>
  <person id="2">
    <name>Jane</name>
    <age>30</age>
    <gender>Female</gender>
  </person>
  <person id="3">
    <name>Bob</name>
    <age>40</age>
    <gender>Male</gender>
  </person>
</people>


参照上述示例文档,举例介绍如何使用XPath的基本语法来提取数据。以下是一些选取节点的表达式示例:

  1. 选取节点people的所有子节点,表达式如下:
people
  1. 选取根节点people,表达式如下:
/people
	需要注意的是,如果路径以“/”开始,那么该路径就代表着达到某个节点的绝对路径。
  1. 从根节点people开始,向下选取属于其所有person子节点,表达式如下:
people/person
  1. 从任意位置开始,选取名称为person的所有节点,表达式如下:
//person
	与上一个表达式相比,该表达式不用再说明符合要求的这些节点再文档树中的具体位置。
  1. 在节点people的后代中,选取所有名称为person的所有节点,而且不用管这写节点位于people之下的位置,表达式如下:
people//person
  1. 使用@选取名称为id的属性节点,表达式如下:
//@id
  • 谓语(补充说明节点)
    谓语指的是路径表达式的附加条件,这些条件都写在方括号中,表示对节点进一步筛选,用于查找某个特定节点或者包含某个指定值的节点,具体格式如下:
元素[表达式]

下面例举一些常用的带有谓语的路径表达式,以及对这些表达式功能的说明:

表达式说明
/people/person[1]选取属于people子元素的第一个person元素
/people/person[last()]选取属于people子元素的最后一个person元素
/perple/person[last()-1]选取属于people子元素的倒数第二个person元素
/people/person[position() < 3]选取最前面的两个属于people元素子元素的person元素
//person[@id]选取所有person元素,且这些元素拥有名称为id的属性
//person[@id=“1”]选取所有person元素,且这些元素拥有值为1的id属性
/people/person[age>39]选取people元素的所有person元素,且其中的age元素的值需大于39
/people/person[age>39]/name选取people元素中person元素的所有name元素,且其中的age元素值需大于39
  • 选取未知节点
    XPath可以使用通配符(*)来选取未知的节点。例如,使用“ * ”可以匹配任何元素节点,下面例举带有通配符的表达式。
通配符说明
*匹配任何元素节点
@*匹配任何属性节点
node()匹配任何类型的节点

以下是一些使用通配符的示例:

  1. 选取people元素的所有子元素,表达式如下:
/people/*
  1. 选取文档中的所有元素,表达式如下:
//*
  1. 选取所有带有属性的person元素,表达式如下:
//person[@*]
  • 选取若干路径

在路径表达式中可以使用“|”运算符,以选取若干个路径。以下是一些在路径表达式中使用“|”运算符的示例:

  1. 选取person元素中包含所有age和name子元素,表达式如下:
//person/age | //person/name
  1. 选取文档中的所有age和name元素,表达式如下:
//age | //name
  1. 选取位于/people/person/路径下的所有name元素,以及文档中所有的age元素,表达式如下:
/people/person/name | //age

XPath开发工具

对于编写网络爬虫或者做网页分析的人而言,会在定位和获取XPath路径上花费大量时间,当爬虫框架成熟以后,又会花费大量的时间来解析网页,针对这些情况Python提供了一些好的插件,如下Chorme浏览器插件:
在这里插入图片描述

lxml库概述

lxml是使用Python语言编写的库,主要用于解析和提取HTML或者XML格式的数据,它不仅功能丰富,而且便于使用,可以利用XPath语法快速的定位特定的元素或节点。
lxml库大部分都位于lxml.etree模块中,导入lxml。etree模块的常见方式如下:

from lxml imoport etree

lxml库的一些相关类如下:

  1. Element类:可以理解为XML的节点。
  2. ElementTree类:可以理解为一个完整的XML文档树。
  3. ElementPath类:可以理解为Xpath,用于搜索和定位节点 。

(1)Element类简介
Element类是XML处理的核心类,可以直观的理解为XML的节点,大部分XML节点的处理都是围绕着Element类斤进行的。要想创建一个节点的对象,则可以通过构造函数直接创建。例如:

root = etree.Element('root')

上述示例中,参数root表示节点的名称。
关于Element类的相关操作,主要可分为三部分,分别是节点操作、节点属性的操作、节点内文本的操作,下面进行介绍:

  1. 节点操作:若要获取节点的名称,可以通过tag属性获取;例如:
print(root.tag)

# 输出结果如下
root
  1. 节点属性的操作:在创建节点的同时,可以为节点增加点属性。节点中的属性是以key-value的形式进行储存的,类似于字典的存储方式。通过构造方法创建节点时,可以在该方法中以参数的形式设置属性,其中参数的名称表示属性的名称,参数的值表示为属性的值。创建属性的示例如下:
# 创建root节点,并为其添加属性
root = etree.Element('root',interesting = 'totally')
print(etree.tostring(root))

#输出结果如下
b'<root interesting="totally"/>'

此外,可以通过set()方法给已有的节点添加属性。在调用该方法时可以传入两个参数,其中第一个参数表示属性的名称,第二个参数表示属性值。例如:

# 再次给root节点添加age属性
root.set('age',20)
print(etree.tostring(root))

# 输出结果如下
b'<root interesting="totally"age="30"/>'

在上述两个示例中,都用到了tostring()函数,该函数可以将元素序列化为XML树的编码字符串表示形成。

  1. 节点内文本操作:一般情况下,可以通过text\tail属性或者xpath()方法来访问文本的内容。通过text属性访问节点的示意如下:
# 创建root节点
root = etree.Element('root')

# 给root节点添加文本
root.text = 'Hello,world!'

print(root.text)
print(etree.tostring(root))

# 输出结果如下
Hello,world!
b'<root>Hello,world!</root>'

(2)从字符串或文件中解析XML
为了能将XML文件解析为数结构,etree模块中提供了如下3个函数:

  1. fromstring()函数:从字符串中解析XML文档或片段,返回根节点(或解析器目标返回的结果)
  2. XML()函数:从字符串常量中解析XML文档或片段,返回根节点(或解析器目标返回的结果)
  3. HTML()函数:从字符串常量中解析HTML文档或片段,返回根节点(或解析器目标返回的结果)

其中,XML()函数的行为类似于fromstring()函数,通常用于将XML()字面量直接写入到源代码中;HTML()函数可ui自动补全缺少的< html >和< body >标签。以下是3个函数的示例:

xml_data = '<root>data</root>'

# fromstring()方法
root_one = etree.fromstring(xml_data)
print(root_one.tag)
print(etree.tostring(root_one)

# XML方法,与fromstring()方法基本一样
root_two = etree.XML(xml_data)
print(root_two.tag)
print(etree.tostring(root_two)

# HTML方法,如果没有<html>和<body>标签,会自动补上
root_three = etree.HTML(xml_data)
print(root_three.tag)
print(etree.tostring(root_three)


# 输出结果如下

root
b'<root>data</root>'

root 
b'<root>data</root>'

html
b'<html><body><root>data</root></body></html>'

除了上述3个函数之外,还可以调用parse()函数从XML文件中直接解析。在调用函数时,如果没有提供解析器,则使用默认的解析器,函数会返回一个ElementTree类的对象。例如:

html = etree.parse('./hello.html')

result = etree.tostring(html,prettu_print=True)

(3)ElementPath类简介
ElementTree类中附带了一个类似于XPath路径语言的ElementPath类。在ElementTree类或Elements类的API文档中,提供了3个常用的方法,可以满足大部分搜索和查询需求,并且这3个方法的参数都是XPath语句。具体如下:

  1. find()方法:返回匹配到的第一个子元素
  2. findall()方法:以列表的形式返回所有匹配的子元素
  3. iterfind()方法:返回一个所有匹配元素的迭代器
# 从字符串中解析XML,返回根节点
root = etree.XML("<root><a x='123'>aText<b/><c/><b/></a></root>")

# 从根节点开始查找,返回匹配到的节点名称
print(root.find("a").tag)

# 从根节点开始查找,返回匹配到的第一个节点的名称
print(root.findall(".//a[@x]")[0].tag)

# 输出结果如下
a
A

还可以调用xpath()方法,使用元素作为上下文节点来评估XPath表达式

lxml库的基本使用

!-- hello.html -->
<div>
	<ul>
		<li class="item-0"><a href="link1.html">first item</a></li>
		<li class="item-inactive"><a href="link3.html"><span
			class="bold">third item</span></a></li>
		<li class="item-0"><a href="link4.html">fifth item</a></li>
	</ul>
</div>

接下来,基于上述HTML文档,使用lxml库中的路径表达式技巧,通过调用xpath()方法匹配选取的节点,具体如下:

  1. 获取任意位置的li节点
    可以直接使用“//”从任意位置选取节点li,路径表达式如下:
//li

通过lxml.etree模块的xpath()方法,将hello.html文件的与该路径表达式匹配到的列表返回并打印输出。具体代码如下:

from lxml import etree
html = etree.parse('hello.html')

# 查找所有的li节点
result = html.xpath('//li')

# 打印<li>标签的元素集合
print(result)

# 打印<li>标签的个数
print(len(result))

# 打印返回结果的类型
print(type(result))

# 打印第一个元素的类型
print(type(result[0]))

# 输出结果如下
[<Element li at 0x2cc9a48><Elelnent li at 0x2cc99>,<Element li at 0x2cc9a88><Element li at 0x2ccac8>,<Element liat 0x2cc9b08>]
5
<class 'list'>
<class 'lxml.etree._Element'>
  1. 继续获取< li >标签的class属性
    在上个表达式的末尾,使用“/”向下选取节点,并使用@选取class属性节点,表达式如下:
//li/@class

获取< li >标签的class属性的示例代码如下:

from lxml import etree
html = etree.parse('hello.html')

# 查找位于li标签的class属性
result = html.xpath('//li/@class')

print(result)
  1. 获取倒数第二个元素的内容

从任意位置开始选取倒数第二个< li >标签,在向下选取标签< a >。如果要获取该标签中的文本,可以使用如下表达式:

//li[last()-1]/a

或者

//li[last()-1/a]/text()

不同的是,第一个表达式需要访问text属性,才能拿到标签文本,第二个表达式可直接获取文本。使用第一个路径表达式的示例如下:

from lxml import etree
html = etree.parse('hello.html')

# 获取倒数第二个元素的内容
result = html.xpath('//li[last()-1]/a')
ptint(result[0].text)

# 输出结果为
fourth item
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ProgramStack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值