Python爬虫〇七———数据解析之XPATH的使用

今天来总结最后一种说句解析的方式——XPath。 

XPath是一门在XML文档中查找信息的语言,用于在XML文档中通过元素和属性进行导航。

XPath简介

 下面来简单介绍一下XPath。

XPath的特点

  1. XPath使用路径表达式在XML文档中进行导航
  2. XPath包含一个标准函数库
  3. XPath是XSLT(Extensible Stylesheet Language Transformation)中的主要元素
  4. XPath是一个W3C标准

XPath解析方法

  1. 实例化一个etree对象,在实例化的时候讲被解析的页面源码数据加载到该对象中
  2. 调用etree的xpath方法结合xpath表达式实现标签的定位和数据的获取

环境的配置

XPath需要lxml库,lxml库的安装我们在前面讲bs4的时候已经说过了,可以通过pip直接安装。

pip3 install lxml

 

etree对象的实例化

和bs4一样,etree对象的实例化分两种情况

加载本地的html文件

加载本地html文件有两种方式,比方有个test.html文件

####################方法1####################
from lxml import etree
with open('./test.html','r') as f:
    data = f.read()
    tree = etree.HTML(data)

####################方法2####################
from lxml import etree
tree = etree.parse('./test.html',etree.HTMLParser())

一个是吧html代码读取以后用HTML方法处理,还有一种方法是直接指定路径,但是这种方法一定要指定一个解释器。

加载互联网上爬取的源码数据

加载网络爬取的数据的方法和上面的第一种方法一样,就是把拿到的字符串加载到HTML方法。

 XPath表达式

在拿到tree对象以后,我们要通过XPath表达式来定位到所需要的标签以及里面的内容 。假设我们本地存有一个test.html文件,内容和上一章的一样

 <html>
  <head>
   <title>
    The Dormouse's story
   </title>
  </head>
  <body>
   <p class="title">
    <b>
     The Dormouse's story
    </b>
   </p>
   <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a class="sister" href="http://example.com/elsie" id="link1">
     Elsie
    </a>
    ,
    <a class="sister" href="http://example.com/lacie" id="link2">
     Lacie
    </a>
    and
    <a class="sister" href="http://example.com/tillie" id="link2">
     Tillie
    </a>
    ; and they lived at the bottom of a well.
   </p>
   <p class="story">
    ...
   </p>
  </body>
 </html>
test.html

然后实例化一个tree对象,并且把带解析的源码加载进去

from lxml import etree
tree = etree.parse('./test.html',etree.HTMLParser())

下面我们就用这个tree对象来讲xpath到用法

XPath术语

想要了解XPath的用法,我们要先了解XPath的基本术语

节点(Node)

在XPath中有其中类型的节点:元素,属性,文本,命名空间,处理指令以及文档节点,整个文档是被作为节点树来对待的,树的根被称为文档节点或根节点。

基本值(Atomic Value)

又称原子值,无父或子节点

节点关系

节点关系分为父,子同胞,先辈,后代。顾名思义就是各个节点之间的关系,要注意的是先辈是包含父类关系的,而后代也包含子级关系的。

xpath节点选取

首先要了解最基础的xpath表达式

表达式 描述
nodename 选取此节点的所有子节点
/ 从根节点选取
//

放在开始从匹配到当前节点选择文档中的节点,而不考虑其位置

放在两个标签内表示间隔多个标签层级

. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性

下面可以针对上面的表达式,结合test.html实例化的tree对象代码来演示一下

依靠节点

a_tags = tree.xpath('/html/body/p/a')
print(a_tags)
##########输出##########
[<Element a at 0x7fdb8f189e00>, <Element a at 0x7fdb8f226b00>, <Element a at 0x7fdb8f10f8c0>]

上面的代码就是搜索html——>body——>p——>a标签,返回值是一个列表,里面放到都是<class 'lxml.etree._Element'>对象

依靠属性定位

print(tree.xpath('//a[@class="sister"]'))
##########输出##########
[<Element a at 0x7fdb8f36cfc0>, <Element a at 0x7fdb8f11d4c0>, <Element a at 0x7fdb8f099380>]

上面的代码就实现了通过指定class的值搜索到对应的a标签。

谓语

我们还可以通过谓语(Predicates)来超找某个特定的节点或者包含某个指定值得节点

在下面的表格中,列出来一些带有谓语的表达式和其对应的结果

表达式 结果
/html/body/p/a[1] 选取a元素的第一个元素
/html/body/p/a[last()] 获取最后一个a元素(注意带括号)
/html/body/p/a[last()-1] 获取倒数第二个a元素(注意-1的位置)
/html/body/p/a[position()<3] 获取最前面两个属于p元素的子元素a
//a[@class] 获取所有有class属性的a元素

//a[@class='sister]

获取所有class属性值为sister的a元素
/html/body/div[p>20] 获取html->body->div下值大于20的p标签(该用法常用于xml中)
/html/body/div[p>20]/a 获取html->body->div下单a标签,且其中p标签内的值应大于20

这里有个要注意点地方,就是在获取第n个元素的时候,在表达式和列表里切片的n的值出来的是不一样的

st = """
<html>
<body>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<body>
</html>
"""

tree = etree.HTML(st,etree.HTMLParser())
t1 = tree.xpath('/html/body/ul/li[1]/text()')
print('t1',t1)
t2 = tree.xpath('/html/body/ul/li/text()')
print('t2',t2[1])
##########输出##########
t1 ['0']
t2 1

 

通配符

在匹配到时候还可以使用通配符来匹配任意一个节点

通配符 效果 案例 效果
* 匹配任何元素节点 /html/body/*/li 匹配body标签下任意标签下单li标签
@* 匹配任何属性节点 //p[@*] 匹配具有任意属性的p标签
node() 匹配任何类型的节点 //* 选取文档中所有元素

多个路径的选取

在xpath表达式中我们可以通过管道符来选取多个路径,这里不在放案例了。

文本获取

在索引到需要的标签后我们就要获取标签里的文本。文本的获取有两个方法

  • /text()
  • //text()

注意,一个是单斜杠,另一个是双斜杠,效果如下

s="""
<div class='test'>div标签内<p>p标签内</p></div>
"""

tree = etree.HTML(s,etree.HTMLParser())
print('//',tree.xpath('//div//text()'))
print('/',tree.xpath('//div/text()'))
##########输出##########
// ['div标签内', 'p标签内']
/ ['div标签内']

单斜杠是获取本标签直系的文本内容

双斜杠包含了本标签及后代标签内的文本内容。

案例

 下面结合一个案例来试一下XPath的使用

需求,从站长之家爬取免费的简历模板

url:https://sc.chinaz.com/jianli/free.html,要爬取5页的数据内容

直接放代码吧,看看怎么讲一下

 1 import requests
 2 from lxml import etree
 3 
 4 for page_index in range(1,6):     #爬取1-5页内容,指定url
 5     if page_index ==1:            #第一页的url和后面的规律不同
 6         url = 'https://sc.chinaz.com/jianli/free.html'
 7     else:
 8         url = 'https://sc.chinaz.com/jianli/free_{}.html'.format(page_index)
 9     
10     page = requests.get(url=url)
11     page.encoding='utf-8'
12     page_text = page.text
13     
14 
15     tree = etree.HTML(page_text,etree.HTMLParser())
16 
17     tag = tree.xpath('//div[@class="box col3 ws_block masonry-brick"]')
18 
19     tags = tree.xpath('//div[@id="main"]//div[contains(@class,"box") and contains(@class,"ws_block")]//p/a')
20     for tag in tags:
21         model_url = 'http:'+tag.xpath('./@href')[0]
22         model_name = tag.xpath('./text()')[0]
23         print(model_url)
24         
25         model_page_text = requests.get(url=model_url).text
26         model_tree = etree.HTML(model_page_text,etree.HTMLParser())
27         
28         model_link = model_tree.xpath('//ul[@class="clearfix"]//a/@href')[0]
29         
30     
31         print(model_link)
32         file_name = model_name+'.rar'
33         
34         with open('./简历模板/'+file_name,'wb') as f:
35             data = requests.get(url=model_link).content
36             f.write(data)
37         print(file_name,'finished!')
38         
39     print('----------page{}finish!----------')

就是分了三个层次

第一层先爬取主页内容,在主页上有若干模板的子链接(页面2),第二层爬取页面2的内容,获取到待下载文件的名称以及链接地址;第三层直接爬取文件(二进制的压缩包),爬取后永久化存储至本地。

这里有个问题,运行一段时间后可能会报一个错误:Httpconnectionpool,是因为短时间发起来高频的请求导致ip被禁,或者http连接处中的连接资源被耗尽。针对第一个我们在下一章会讲到请求代理操作

第二个可以在headers里加一个新的键值对:Conection:"close"

 

posted @ 2021-02-28 12:28  银色的音色  阅读(625)  评论(0编辑  收藏  举报