# **《浅谈解析库XPath,bs4和pyquery》**
### 作者:*墨非墨菲非菲*
前几天在CSDN看到一篇帖子,题目是“如何让自己像打王者一样发了疯,拼了命,石乐志的学习”。这里面讲到了阶段性反馈机制,我觉得蛮有意思的,正好前两天用python写了一个scrawler爬取了某XXXX软件上面的挑战答题并自动匹配。在解析题库网页的时候碰到了一系列的问题,把三种解析库都回顾了个遍。借着这个兴奋劲儿,决定码一篇python解析库————lxml,bs4,以及pyquery的简要概述。![某xxxx](https://img-blog.csdnimg.cn/20200401100514534.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2NjgzNzQ0,size_16,color_FFFFFF,t_70) :happy:
下面仅仅是我个人的回忆和记录,仅供参考,错误之处还请多多指正。
## **写在前面**
以上提到的三个是python语言中最最最常用的三个解析库,解析库是用来提取网页的html信息的。首先要解释一下,网页中如此多的信息,为什么能够被精准的获取到。网页可以分为三大部分————HTML,JavaScrip和CSS,这些要素构成了我们看到的丰富多彩的网页。
解析库基于网页的两类特征,一类是节点树(HTML DOM),一类是css选择器。如果把节点树比作一个家庭,那么它就包含了它爷爷,它老汉(parent),它兄弟(sibling)以及它儿子和孙孙(children)。有了这个节点树,网页里面每一个元素就层级分明的展现了出来。![节点树](https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1585714661896&di=60199f4f824cb632393634936d57e943&imgtype=0&src=http%3A%2F%2Fimg1.imgtn.bdimg.com%2Fit%2Fu%3D137618396%2C3170534982%26fm%3D214%26gp%3D0.jpg) 具体的,构成这棵树的枝节,又包根元素(html),元素(title,body,a...),属性(class,href...)和文本等等。css,即层叠样式表,它有一套属于自己的语法规则,举个例子,选择器(.link)代表了'class=“link”',(#song)代表了'id=“song”',(a)代表了'a'的所有节点。 ![html网页](https://img-blog.csdnimg.cn/20200401100658276.PNG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2NjgzNzQ0,size_16,color_FFFFFF,t_70)
有了上面的规则,每个元素的位置就被唯一确定了下来了,接下来解析库就登上了舞台,使出了元哥般的秀发。
>下面是分割线
---
## **【解析库一:XPath】**
XPath,全名是XML Path Lauguage。拿到一个库,如果有精力和毅力阅读官方文档当然最好,虽然我几乎没有过。知道名字,好像意义不大,所以,直接上干货。
### 1.初始化
先说说初始化(initition),可被解析的有字符串(string)和html文件(filename.html)。具体的使用方法:
~~首先要安装lxml库,python CMD:pip install lxml~~
```python
res = '''
'''
from lxml import etree
html = etree.HTML(res) #调用HTML()方法初始化字符串(string)例
html = etree.tostring() #调用tostring()方法可以补全未闭合的标签
print(html.decoding('utf8')) #以字符串的格式打印
```
文件采用以下方法:
```python
from lxml import etree
html = etree.parse('./text.html',etree.HTMLParser()) #调parse()方法来实例化文件(filename.html)
result = html.xpath('//*') #xpath()里面传入匹配规则
```
到这里,初始化的方法就说完了(我所知道的,手动狗头保命/滑稽)
### 2.匹配方法
#### (1)子节点(/)和子孙节点(//)
例如,我想获取以上res文本中的“射雕英雄传之东成西就”和'a'标签里的网址,
以下涉及到了子节点,子孙节点的选择,属性的匹配,文本和属性值的获取。
```python
movie = html.xpath('//div[@class="info"]//a/span/text()')
url = html.xpath('//div[@class="info"]//a/@href')
```
#### (2)获取文本“东成西就”,class属性值有两个,那么就引入了属性多值匹配和多属性匹配
```python
text=html.xpath('//div[@class="info"]//span[contains(@class,"other") and @name="item"]/text()')
```
### 3.节点选择
在匹配方法中主要涉及了子节点和子孙节点。接下来还有父节点(parent)和祖父节点(parents)
```python
result = html.xpath('//span[@class="titile"]/../a')
```
因为xpath默认解析全部符合规则的标签,所以这里就不提及sibling节点。
### 4.类型
如果可爱的你玩元哥不想成为神仙打架,那么时时刻刻清楚自己的本体在哪是很重要的。如果在运用解析库的时候不想天花乱坠,得时时刻刻清楚下一步输出的文件类型!!!知道类型才知道应该用什么方法。
```python
print(type(result),result,sep='|')
>>>
```
是列表类型记得迭代,for搞定一切!
### 5.按序选择
因为同级的标签是有很多的,上面得出了输出文件是list的结论,那么聪明的你一定知道了,它可以切片,我仿佛看到了乐事薯片发出的香甜可口的气息~好了,pia!回到正题,按序选择就是用中括号传入索引获取特定了次序的节点。
==注意是从“1”开始==
直接上码
```python
result = html.xpath('//li[1]/a/text()')
#可替换的例如:[last()], [last()-1], [positon()<3]等等
```
### 6.好了,没有6了
当然,更多详细的关于python的lxml库的使用可以[访问网址](http://lxml.de/)。
个人的一点小总结,lxml的使用方法和BeautifulSoup是比较接近的,都是依赖于节点树和标签。功能上说得上中规中矩,该有的它都有。一定要说一点优势嘛,它的层级结构是非常清楚的,非常易于阅读。并且直接建立在lxml之上,匹配速度应该算是很快的了(空口说这个速度会不会有些有失公允,让用户感知不强?)。不过关于属性匹配的写法,难免复杂了些。遇到属性多值的匹配还傲娇,必须得用[constains(@class,"...")来进行匹配,多多少少有些复杂。当然,这都是些后话啦。
## **【解析库二:BeautifulSoup】**
一个强大的解析工具BeautifulSoup,借助网页结构和属性来解析网页。最先接触的解析库,本着“国外的月亮更圆”的陋习,在只知道BeautifulSoup的时候,还去硬刚过re,结果被re复杂的匹配规则劝退。顺便说一下,re它长这样:
```python
>data-src = re.findall('
```
手动(黑人脸问好.png),废话不多说,直接上菜。
### 1.初始化
BeautifulSoup可传入字符串(string),在解析时依赖于解析器,例如'BeautifulSoup(res,'html.parser'),这里的html.parser就是python内置的标准解析库,执行速度适中,文档容错性强。如果你非要快,'lxml'和'xml'是你的不二选择,不过提前安装好C语言库哦。
~~首先要安装bs4库,python CMD:pip install bs4~~
```python
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)
实操环节
```python
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即可,例如:
```python
print(items.a.contents)
```
如果要得到子孙节点,可以调用descendants属性。值得一提,descendants会递归查询所有的子孙节点。
#### (2)父节点和祖父节点
```python
print(items.a.parent) #父节点
print(items.a.parents) #祖先节点
```
#### (3)兄弟节点
```python
print(items.a.sibling)
```
哈哈哈,到这里,你以为只是简单的复述几个单词咯?下面才是冷知识点。
**next_sibling, previous_sibling, list(enumerate(next_siblings)), list(enumerate(previous_siblings))**
### 4.类型
类型永远是个重点,初次试水记得时时刻刻用type()函数查询,直接捡现的:
```python
print(type(soup))
>>>
```
在没有被text之前,它始终是个bs4.element.Tag类型,这也意味着,能够在bs4.element.Tag上面套娃————实现嵌套功能:find('ul').find_all('li)。
### 5.CSS选择器
BeautifulSoup还提供了css选择器,只需要调用select()方法,传入相应的css选择即可。并且,它继承了BeautifulSoup的bs4.element.Tag类型,支持嵌套功能。举个例子:
```python
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。给它个“好看又能打”的称号一点也不为过了。从初始化开始,它的文件类型始终是
## **【解析库三:pyquery】**
接下来,让我们来感受一下这个偏科生————pyquery的强大之处。
### 1.初始化
~~首先安装pyquery库,python CMD:pip install pyquery~~
pyquery支持三种类型的参数传入,分别是字符串(string),网址(url='www.baidu.com'),文件(filename='.*.html'),举个例子:
```python
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 = '''
导演: 姜文 Wen Jiang 主演: 夏雨 Yu Xia / 宁静 Jing Ning / 陶虹 Hong Tao
1994 / 中国大陆 中国香港 / 剧情 爱情
'''
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()获取祖先节点,可以传入参数筛选。
```python
items = lis('span.title').parents('a')
```
#### (3)兄弟节点
获取兄弟节点可以用siblings()方法
### 4.文件类型
如期而至,让我们来看看pyquery的文件类型是什么。
```python
print(type(doc))
```
如果获取的是单个节点,可以直接输出,也可以转化为字符串输出。
让我们来看看下面这种情况:
```python
print(type(items))
for item in items.items()
print(item)
```
!!!**重点在这里,pyquery返回的多个节点需要用遍历处理。**
### 5.节点处理
下面列举几种pyquery常用的节点处理的方法。
#### (1)addClass( ) 和removeClass( )
```python
lis='''
'''from pyquery import PyQuery as pq
doc = pq(lis)
print(doc('.hd').removeClass('hd').addClass('hahah'))
```
#### (2)attr( )和text( )
```python
print(doc('a').attr('name','link'))
print9doc('a').text('changed items)
```
#### (3)remove( )
比如提取标签里面“阳光灿烂的日子”
```python
item = lis('a')
item.find('.other').remove()
print(item)
```
### 6.好了,pyquery的用法到这里也介绍完了
总结一下,功能强大,写法简洁。愿你解析半天,归来还是pq。
#**Title1**
##*line2*
###==line3== >muname
<'alert('hello world');' >
'''
'''
![picture](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官方文档](https://docs.python.org/zh-cn/3.7/)。
[3]python3 lxml标准库[点击阅读lxml文档](http://lxml.de/)。
[4]沈承放,莫达隆,beautifulsoup库在网络爬虫中的使用技巧及应用,[J],2019.3,2019(28)[点击阅读paper](https://login.cnki.net/login/?platform=kdoc&ForceReLogin=1&ReturnUrl=%2f%2fkns.cnki.net%2fKXReader%2fDetail%3fdbcode%3dCJFQ%26filename%3dDNZS201928007%26uid%3d)。
[5]风变编程——BeautifulSoup实践。
![](https://img-blog.csdnimg.cn/20200401101928419.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2NjgzNzQ0,size_16,color_FFFFFF,t_60)