浅谈解析库XPath,bs4和pyquery

《浅谈解析库XPath,bs4和pyquery》

作者:墨非墨菲非菲

前几天在CSDN看到一篇帖子,题目是“如何让自己像打王者一样发了疯,拼了命,石乐志的学习”。这里面讲到了阶段性反馈机制,我觉得蛮有意思的,正好前两天用python写了一个scrawler爬取了某XXXX软件上面的挑战答题并自动匹配。在解析题库网页的时候碰到了一系列的问题,把三种解析库都回顾了个遍。借着这个兴奋劲儿,决定码一篇python解析库————lxml,bs4,以及pyquery的简要概述。浅谈解析库XPath,bs4和pyquery :happy:
下面仅仅是我个人的回忆和记录,仅供参考,错误之处还请多多指正。

写在前面

以上提到的三个是python语言中最最最常用的三个解析库,解析库是用来提取网页的html信息的。首先要解释一下,网页中如此多的信息,为什么能够被精准的获取到。网页可以分为三大部分————HTML,JavaScrip和CSS,这些要素构成了我们看到的丰富多彩的网页。

解析库基于网页的两类特征,一类是节点树(HTML DOM),一类是css选择器。如果把节点树比作一个家庭,那么它就包含了它爷爷,它老汉(parent),它兄弟(sibling)以及它儿子和孙孙(children)。有了这个节点树,网页里面每一个元素就层级分明的展现了出来。浅谈解析库XPath,bs4和pyquery 具体的,构成这棵树的枝节,又包根元素(html),元素(title,body,a…),属性(class,href…)和文本等等。css,即层叠样式表,它有一套属于自己的语法规则,举个例子,选择器(.link)代表了’class=“link”’,(#song)代表了’id=“song”’,(a)代表了’a’的所有节点。 浅谈解析库XPath,bs4和pyquery
有了上面的规则,每个元素的位置就被唯一确定了下来了,接下来解析库就登上了舞台,使出了元哥般的秀发。

下面是分割线


【解析库一:XPath】

XPath,全名是XML Path Lauguage。拿到一个库,如果有精力和毅力阅读官方文档当然最好,虽然我几乎没有过。知道名字,好像意义不大,所以,直接上干货。

1.初始化

先说说初始化(initition),可被解析的有字符串(string)和html文件(filename.html)。具体的使用方法:
首先要安装lxml库,python CMD:pip install lxml


```python

```python
res = '''   <div class="info">
        <div class="hd">
            <a href="https://movie.douban.com/subject/1316510/" class="">
                <span class="title">射雕英雄传之东成西就</span>
                        <span class="title"> / 射鵰英雄傳之東成西就</span>
                    <span class="other class-1" name="item"> / 东成西就  /  大英雄 (日本)</span>
            </a>
                <span class="playable">[可播放]</span>
    </div></div>
            '''
from lxml import etree
html = etree.HTML(res) #调用HTML()方法初始化字符串(string)例
html = etree.tostring() #调用tostring()方法可以补全未闭合的标签
print(html.decoding('utf8')) #以字符串的格式打印

 

文件采用以下方法:

from lxml import etree  
html = etree.parse('./text.html',etree.HTMLParser()) #调parse()方法来实例化文件(filename.html)  
result = html.xpath('//*') #xpath()里面传入匹配规则  

到这里,初始化的方法就说完了(我所知道的,手动狗头保命/滑稽)

2.匹配方法

(1)子节点(/)和子孙节点(//)

例如,我想获取以上res文本中的“射雕英雄传之东成西就”和’a’标签里的网址,
以下涉及到了子节点,子孙节点的选择,属性的匹配,文本和属性值的获取。

movie = html.xpath('//div[@class="info"]//a/span/text()')  
url = html.xpath('//div[@class="info"]//a/@href')  

(2)获取文本“东成西就”,class属性值有两个,那么就引入了属性多值匹配和多属性匹配

text=html.xpath('//div[@class="info"]//span[contains(@class,"other") and @name="item"]/text()')  

3.节点选择

在匹配方法中主要涉及了子节点和子孙节点。接下来还有父节点(parent)和祖父节点(parents)

result = html.xpath('//span[@class="titile"]/../a')  

因为xpath默认解析全部符合规则的标签,所以这里就不提及sibling节点。

4.类型

如果可爱的你玩元哥不想成为神仙打架,那么时时刻刻清楚自己的本体在哪是很重要的。如果在运用解析库的时候不想天花乱坠,得时时刻刻清楚下一步输出的文件类型!!!知道类型才知道应该用什么方法。

print(type(result),result,sep='|')  
>>><class 'list'>|[<Element div at 0x19804f1d308>]  

是列表类型记得迭代,for搞定一切!

5.按序选择

因为同级的标签是有很多的,上面得出了输出文件是list的结论,那么聪明的你一定知道了,它可以切片,我仿佛看到了乐事薯片发出的香甜可口的气息~好了,pia!回到正题,按序选择就是用中括号传入索引获取特定了次序的节点。
注意是从“1”开始
直接上码

result = html.xpath('//li[1]/a/text()')
#可替换的例如:[last()], [last()-1], [positon()<3]等等

6.好了,没有6了

当然,更多详细的关于python的lxml库的使用可以访问网址
个人的一点小总结,lxml的使用方法和BeautifulSoup是比较接近的,都是依赖于节点树和标签。功能上说得上中规中矩,该有的它都有。一定要说一点优势嘛,它的层级结构是非常清楚的,非常易于阅读。并且直接建立在lxml之上,匹配速度应该算是很快的了(空口说这个速度会不会有些有失公允,让用户感知不强?)。不过关于属性匹配的写法,难免复杂了些。遇到属性多值的匹配还傲娇,必须得用[constains(@class,"…")来进行匹配,多多少少有些复杂。当然,这都是些后话啦。

【解析库二:BeautifulSoup】

一个强大的解析工具BeautifulSoup,借助网页结构和属性来解析网页。最先接触的解析库,本着“国外的月亮更圆”的陋习,在只知道BeautifulSoup的时候,还去硬刚过re,结果被re复杂的匹配规则劝退。顺便说一下,re它长这样:

>data-src = re.findall('<dd>.*?class="star">(.*?)</dd>))$。  

手动(黑人脸问好.png),废话不多说,直接上菜。

1.初始化

BeautifulSoup可传入字符串(string),在解析时依赖于解析器,例如’BeautifulSoup(res,‘html.parser’),这里的html.parser就是python内置的标准解析库,执行速度适中,文档容错性强。如果你非要快,'lxml’和’xml’是你的不二选择,不过提前安装好C语言库哦。
首先要安装bs4库,python CMD:pip install bs4

import requests
from bs4 import BeautifulSoup
res = requests.get('https://www.baidu.com/s?ie=UTF-8wd=%E5%B0%8F%E8%AF%B4').text    #获取网页源代码
soup = BeautifulSoup(res,'lxml')     #格式化字符串,依靠lxml析库

2.基本用法

find_all(),顾名思义,查找所有符合条件的元素。它的api如下:
find_all(name,attr,text,**kwargs)
实操环节

items = soup.find('div', class_="c-tabs c-gap-top-small")find('ul').find_all('li)['href']
for item in items[0]:
    print(item)

以上实例展示了find():查找符合条件的第一个标签,属性的引用需要说明,在python中class为关键字,所以碰到class标签要加上下划线,获取属性值[‘href’] or .get('herf), 文本的获取(.text)/(.string)

3.节点选择

(1)子节点和子孙节点

选取节点元素之后,如果想要获取它的直接子节点,直接调用contents属性或children即可,例如:

print(items.a.contents)

如果要得到子孙节点,可以调用descendants属性。值得一提,descendants会递归查询所有的子孙节点。

(2)父节点和祖父节点

print(items.a.parent)   #父节点
print(items.a.parents)   #祖先节点

(3)兄弟节点

print(items.a.sibling)  

哈哈哈,到这里,你以为只是简单的复述几个单词咯?下面才是冷知识点。
next_sibling, previous_sibling, list(enumerate(next_siblings)), list(enumerate(previous_siblings))

4.类型

类型永远是个重点,初次试水记得时时刻刻用type()函数查询,直接捡现的:

print(type(soup))
>>><class 'bs4.element.Tag'>

在没有被text之前,它始终是个bs4.element.Tag类型,这也意味着,能够在bs4.element.Tag上面套娃————实现嵌套功能:find(‘ul’).find_all('li)。

5.CSS选择器

BeautifulSoup还提供了css选择器,只需要调用select()方法,传入相应的css选择即可。并且,它继承了BeautifulSoup的bs4.element.Tag类型,支持嵌套功能。举个例子:

items = soup.find(‘div’, class_=“c-tabs c-gap-top-small”)find(‘ul’).find_all('li)[‘href’] #find()写法
items = soup.select(‘div[contains(@class=“c-tabs”)]/ul/li@href’) #select()写法


6.好了,BeautifulSoup的基本用法就介绍完了

最后来点小总结。聪明的你一定会发现,BeautifulSoup的篇幅足够短,因为它足够easy和brief。而且它向左兼容节点,向右能用css。给它个“好看又能打”的称号一点也不为过了。从初始化开始,它的文件类型始终是<class ‘bs4.element.Tag’>,你可以随意嵌套。碰到单个文件,直接打印;多个文件,迭代打印。唯一需要注意的是class_="…",一定不要忘记!

【解析库三:pyquery】

接下来,让我们来感受一下这个偏科生————pyquery的强大之处。

1.初始化

首先安装pyquery库,python CMD:pip install pyquery
pyquery支持三种类型的参数传入,分别是字符串(string),网址(url=‘www.baidu.com’),文件(filename=’.*.html’),举个例子:

from pyquery import PyQuery as pq
doc = pq(url='www.baidu.com) 

细心的你在这里已经发现了问题:为啥传了一个网址进去,这怎么用。其实,pyquery自带了获取html的功能,它相当于:

doc = pq(requests.get('www.baidu.com).text) 

2.基本用法

复习一下CSS选择器的规则。

lis = '''<div id="container">
    <div class="hd">
        <a href="https://movie.douban.com/subject/1291875/" class="">
            <span class="title">阳光灿烂的日子</span>
            <span class="other">&nbsp;/&nbsp;In the Heat of the Sun</span>
        </a>
    </div>
    <div class="bd">
        <p class="">导演: 姜文 Wen Jiang&nbsp;&nbsp;&nbsp;主演: 夏雨 Yu Xia / 宁静 Jing Ning / 陶虹 Hong Tao<br>
        1994&nbsp;/&nbsp;* 中国香港&nbsp;/&nbsp;剧情 爱情</p>
    </div>
</div>
'''  
from pyquery import PyQuery as pq
doc = pq(lis)
print(doc('#container .hd a span.title).text())
>>>阳光灿烂的日子 

获取属性:调用attr方法(a.attr('href))或(a.attr.href);获取文本:text();获取html:html()

3.查找节点

(1)子节点和子孙节点

查找子节点,需要用到find()方法,会将符合条件的所有节点全部提取出来。如果只查找子节点,调用children()方法就即可。

(2)父节点和祖父节点

我们可以用parent()来获取某个节点的父节点,parent()获取祖先节点,可以传入参数筛选。

items = lis('span.title').parents('a')

(3)兄弟节点

获取兄弟节点可以用siblings()方法

4.文件类型

如期而至,让我们来看看pyquery的文件类型是什么。

print(type(doc))
<class 'pyquery.pyquery.PyQuery'>

如果获取的是单个节点,可以直接输出,也可以转化为字符串输出。
让我们来看看下面这种情况:

print(type(items))
<class 'generator'> #generator意思是"发生器",这个时候就需要历了。使用之前需要先用items()格式化。
for item in items.items()
    print(item)

!!!重点在这里,pyquery返回的多个节点需要用遍历处理。

5.节点处理

下面列举几种pyquery常用的节点处理的方法。

(1)addClass( ) 和removeClass( )

lis='''<div class="hd">
        <a href="https://movie.douban.com/subject/1291875/" class="">
            <span class="title">阳光灿烂的日子</span>
            <span class="other">&nbsp;/&nbsp;In the Heat of the Sun</span>
        </a>
    </div>'''  
from pyquery import PyQuery as pq
doc = pq(lis)
print(doc('.hd').removeClass('hd').addClass('hahah'))

(2)attr( )和text( )

print(doc('a').attr('name','link'))
print9doc('a').text('changed items)

(3)remove( )

比如提取标签里面“阳光灿烂的日子”

item = lis('a')
item.find('.other').remove()
print(item)

6.好了,pyquery的用法到这里也介绍完了

总结一下,功能强大,写法简洁。愿你解析半天,归来还是pq。

#Title1
##line2
###line3 >muname
<‘alert(‘hello world’);’ >
‘’’
<print(‘hello world’)>
‘’’
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvW7TjBx-1585707653825)(C:\Users\tingy\Desktop\节点树.jpg)]

写在最后

从2020/03/31 15:20开始,到04/01 09:59,这一篇文章已经算是圆满了。再次感谢我妈和我的芬芬儿昨天晚上帮我洗碗,我才能够比较顺利的,时效的完成这篇梳理。

引用

[1]崔庆才,Python3网络开发与实践,[M],2018.4,人民邮电出版社。
[2]URL:https://docs.python.org/zh-cn/3.7/ 点击阅读python官方文档
[3]python3 lxml标准库点击阅读lxml文档
[4]沈承放,莫达隆,beautifulsoup库在网络爬虫中的使用技巧及应用,[J],2019.3,2019(28)点击阅读paper
[5]风变编程——BeautifulSoup实践。
浅谈解析库XPath,bs4和pyquery

上一篇:python3爬虫(7)--使用pyquery的CSS选择器(Selectors)解析数据


下一篇:两种清除html中标签的方法