Python爬虫第13节-解析库pyquery 的使用
目录
前言
一、pyquery 初始化
1.1 字符串初始化
1.2 URL 初始化
1.3 文件初始化
二、基本 CSS 选择器
三、pyquery 查找节点
3.1 子节点
3.2 父节点
3.3 兄弟节点
四、遍历
五、获取信息
5.1 获取属性
5.2 获取文本
六、节点操作
6.1 addClass 和 removeClass
6.2 attr、text、html
6.3 remove
七、伪类选择器
前言
上两节我们讲了Beautiful Soup这个网页解析库,它确实很厉害。不过,大家用它的一些方法时,会不会感觉不太顺手?还有它的CSS选择器,用起来是不是觉得功能没那么强呢?
要是你接触过Web开发,平时习惯用CSS选择器,或者对jQuery有一定了解,那我得给你介绍一个更称手的解析库,它就是pyquery。 下面,咱们就一起来见识下pyquery有多厉害。
一、pyquery 初始化
在正式开始前,得先确认你已经把pyquery正确安装好了。要是还没安装,就得自己动手通过pip或pip3安装一下。 和Beautiful Soup类似,初始化pyquery时,同样要传入HTML文本,以此来创建一个PyQuery对象。它有好几种初始化的办法,既可以直接传入字符串,也能传入URL,还能传入文件名等等。接下来,我们就详细讲讲这些方法。
1.1 字符串初始化
咱们先通过一个实际例子来体验体验:
html = '''
<div><ul><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
for item in doc('li').items():print(item.outer_html())
运行结果如下:
咱们先引入PyQuery对象,给它取个pq的别名。接着弄了个很长的HTML字符串,把这个字符串作为参数,传给PyQuery类,这样就完成了初始化。初始化完之后,再把得到的对象放到CSS选择器里。在这个例子里,我们输入li节点,这么一来,就能选中所有的li节点了 。
1.2 URL 初始化
初始化时,参数不只能用字符串形式来传递。要是你想传入网页的URL,也完全没问题,只要把参数指定为url就行 :
from pyquery import PyQuery as pq
doc = pq(url='https://linshantang.blog.csdn.net/')
print(doc('title'))
运行结果:
<title>攻城狮7号-CSDN博客</title>
这么操作后,PyQuery对象会先对这个URL发起请求。等拿到网页的HTML内容,就用这些内容完成初始化。这和直接把网页的源代码,以字符串形式传给PyQuery类来初始化,效果是一样的 。
它与下面的代码功能是相同的:
from pyquery import PyQuery as pq
import requests
doc = pq(requests.get('https://linshantang.blog.csdn.net/').text)
print(doc('title'))
1.3 文件初始化
当然啦,初始化的时候,除了能传一个URL,要是你想传本地的文件名也是可以的,只要把参数指定成filename就行:
from pyquery import PyQuery as pq
doc = pq(filename='demo.html')
print(doc('li'))
当然,得先有个本地的HTML文件,叫demo.html,里面的内容就是要解析的HTML字符串。这样一来,它会先读取这个本地文件里的内容,接着把文件内容当成字符串,传给PyQuery类进行初始化。
上面这3种初始化方法都能用,不过在实际使用中,最常用的初始化方式还是用字符串来传递 。
二、基本 CSS 选择器
咱们先通过一个实际例子,来体验体验pyquery里CSS选择器该怎么用:
html = '''
<div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
print(doc('#container .list li'))
print(type(doc('#container .list li')))
运行结果:
这里呢,我们先初始化了PyQuery对象,接着输入了一个CSS选择器#container .list li 。这个选择器的意思是,先找到id是container的节点,再从这个节点里面,找到class是list的节点,最后把这个list节点里面所有的li节点选出来。然后我们把选出来的结果打印出来,能看到,确实成功找到了符合条件的节点。 最后呢,我们把选出来结果的类型也打印出来。可以看到,它还是PyQuery类型 。
三、pyquery 查找节点
接下来给大家讲讲一些常用的查询方法,这些方法的使用方式跟jQuery里的方法一模一样 。
3.1 子节点
要是想查找子节点,就得用find方法,这个方法的参数是CSS选择器。咱们还是拿上面那个HTML做例子来说:
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
print(type(items))
print(items)
lis = items.find('li')
print(type(lis))
print(lis)
运行结果:
首先,我们选中class是list的节点,接着调用find()方法,把CSS选择器作为参数传进去,这样就选中了这个节点里面的li节点,最后把结果打印出来。可以看到,find()方法会把所有符合条件的节点都选出来,选出来的结果是PyQuery类型。 实际上,find方法会在节点的所有子孙节点里查找。要是我们只想找子节点,那就可以用children方法。
lis = items.children()
print(type(lis))
print(lis)
运行结果如下:
要是想从所有子节点里挑出符合条件的节点,就拿筛选出子节点里class是active的节点来说,可以给children()方法传入CSS选择器.active。
lis = items.children('.active')
print(lis)
运行结果:
从输出结果能明显看出,已经筛选过了,只剩下class是active的节点 。
3.2 父节点
我们可以用parent方法获取某个节点的父节点,下面通过一个例子来看看效果:
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
container = items.parent()
print(type(container))
print(container)
运行结果如下:
这里我们先用.list选中class是list的节点,接着调用parent方法,就能得到这个节点的父节点,这个父节点也是PyQuery类型。 这里得到的父节点是直接的父节点,不会再去查父节点的父节点,也就是不会找祖先节点。 要是想获取某个祖先节点,该咋整呢?这时候就可以用parents方法。
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
parents = items.parents()
print(type(parents))
print(parents)
运行结果:
能看到,输出结果有俩:一个是class为wrap的节点,另一个是id为container的节点。这说明,parents()方法会把所有的祖先节点都返回。 要是想筛选出某个祖先节点,就可以给parents方法传入CSS选择器,这样就能得到祖先节点里符合这个CSS选择器的节点。
parent = items.parents('.wrap')
print(parent)
运行结果:
从输出结果能明显看出来,少了一个节点,现在只剩下class是wrap的那个节点了。
3.3 兄弟节点
上面我们讲了子节点和父节点的用法,还有一种节点叫兄弟节点。要是想获取兄弟节点,可以用siblings()方法。咱们还是接着用上面的HTML代码来说明:
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings())
这里先选中class是list的节点里面,class分别为item - 0和active的节点,也就是第三个li节点。显然,它有4个兄弟节点,分别是第一个、第二个、第四个和第五个li节点。
运行结果:
能看到,这就是我们刚才说的那4个兄弟节点。 要是想筛选出某个兄弟节点,还是可以给siblings方法传入CSS选择器,这样就能从所有兄弟节点里挑出符合条件的节点。
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings('.active'))
这里我们筛选了 class 为 active 的节点,通过刚才的结果可以观察到,class 为 active 的兄弟节点只有第四个 li 节点,所以结果应该是一个。我们再看一下运行结果:
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
四、遍历
刚才能看到,pyquery选择出来的结果可能是多个节点,也可能是单个节点,但类型都是PyQuery类型,不会像Beautiful Soup那样返回列表。 要是选出来的是单个节点,既可以直接打印,也能直接转成字符串。
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(str(li))
运行结果:
要是选择结果有多个节点,就得通过遍历来获取每个节点。就像这里,要遍历每个li节点的话,就得调用items方法。
from pyquery import PyQuery as pq
doc = pq(html)
lis = doc('li').items()
print(type(lis))
for li in lis:print(li, type(li))
运行结果如下:
能发现,调用items()方法后会得到一个生成器,对这个生成器进行遍历,就能逐个拿到li节点对象,这些对象也是PyQuery类型。每个li节点都能调用前面提到的方法来做选择操作,像接着查找子节点、找某个祖先节点之类的,用起来很灵活。
五、获取信息
把节点提取出来以后,我们的最终目标肯定是要提取出节点里包含的信息。其中比较关键的信息有两种,一种是获取节点的属性,另一种是获取节点的文本内容。下面我就分别给大家讲一讲。
5.1 获取属性
当提取到一个PyQuery类型的节点后,就能调用attr()方法来获取这个节点的属性了。
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active a')
print(a, type(a))
print(a.attr('href'))
运行结果如下:
<a href="link3.html"><span class="bold">third item</span></a> <class 'pyquery.pyquery.PyQuery'>
link3.html
这里先选中class是item - 0和active的li节点里面的a节点,这个节点是PyQuery类型。 接着调用attr方法,在方法里传入属性名,就能得到对应的属性值。 另外,也能通过调用attr属性来获取属性,具体用法如下:
print(a.attr.href)
这两种方法得到的结果是完全相同的。 要是选中了多个元素,再去调用attr方法,会得到什么样的结果呢?下面我们通过实际例子来测试看看。
a = doc('a')
print(a, type(a))
print(a.attr('href'))
print(a.attr.href)
运行结果如下:
按道理,我们选中的a节点应该有4个,打印结果也该是4个。但调用attr方法时,返回的却只有第一个节点的属性。这是因为,当返回结果包含多个节点时,调用attr方法只能得到第一个节点的属性。 要是遇到这种情况,想获取所有a节点的属性,就得用前面说过的遍历方法了。
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('a')
for item in a.items():print(item.attr('href'))
运行结果:
所以,在获取属性的时候,要先看返回的节点是一个还是多个。要是返回多个节点,就得通过遍历才能逐个获取每个节点的属性。
5.2 获取文本
提取到节点后,另一个重要操作就是获取其内部的文本内容,这时调用text方法就能达成这一目的。
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('.item-0.active a')
print(a)
print(a.text())
运行结果:
这里先选中一个a节点,接着调用text方法,就能获取该节点内部的文本信息。这时它会把节点内部的所有HTML内容忽略掉,只返回纯文本。 不过要是想获取这个节点内部的HTML文本,那就得使用html方法了。
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(li.html())
这里我们选定了第三个li节点,随后调用了html()方法。该方法返回的结果会是这个li节点内包含的所有HTML文本内容。
运行结果:
这里存在一个疑问,如果我们选中的结果包含多个节点,那么调用 text() 或 html() 方法会返回什么样的内容呢?下面我们通过实际例子来探究一下。
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li')
print(li.html())
print(li.text())
print(type(li.text()))
运行结果如下:
结果可能有点让人意外,html方法返回的是第一个li节点内部的HTML文本,而text方法返回的是所有li节点内部的纯文本,各文本间用一个空格分隔,也就是返回一个字符串。 所以这里要特别留意,如果得到的结果是多个节点,还想获取每个节点内部的HTML文本,那就需要对每个节点进行遍历。而text()方法不用遍历就能获取文本,它会把所有节点的文本提取出来并合并成一个字符串。
六、节点操作
pyquery提供了一系列可对节点进行动态修改的方法,像给某个节点添加一个class,或者移除某个节点等。这些操作有时能为信息提取带来极大便利。 鉴于节点操作的方法众多,下面我会列举几个典型例子来说明其用法。
6.1 addClass 和 removeClass
那咱们先通过一个具体的实例来体验一番:
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.removeClass('active')
print(li)
li.addClass('active')
print(li)
先是选中了第三个li节点,接着调用removeClass()方法,把li节点上的active这个class给去掉了,之后又调用了addClass()方法,再把这个class添加回来。每次执行完一次操作,就把当前li节点的内容打印输出。 运行后得到的结果如下:
从结果能看到,总共输出了3次。在第二次输出的时候,li节点的active这个class已经被移除掉了,而到第三次输出时,这个class又被重新添加回来了。 由此可见,addClass和removeClass这两个方法是能够对节点的class属性进行动态修改的。
6.2 attr、text、html
当然,除了对 class 属性进行操作外,还能使用 attr 方法操作其他属性。另外,也可借助 text 和 html 方法来改变节点内部的内容。下面是相关示例:
html = '''
<ul class="list"><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
</ul>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
li.attr('name', 'link')
print(li)
li.text('changed item')
print(li)
li.html('<span>changed item</span>')
print(li)
这里我们先选中了li节点,之后调用attr方法修改属性。attr方法的第一个参数是属性名,第二个参数是属性值。接着,我们又调用text和html方法来改变节点内部的内容。每次操作完成后,都会打印输出当前的li节点。 下面是运行结果:
可以看出,调用attr方法后,li节点新增了一个原本不存在的属性“name”,其值为“link”。随后调用text方法并传入文本,li节点内部的文本就都变成了传入的字符串文本。最后,调用html方法并传入HTML文本,li节点内部又变成了传入的HTML文本。 由此可知,attr方法若只传入第一个参数即属性名,是用于获取该属性值;若传入第二个参数,则可用来修改属性值。text和html方法若不传入参数,分别是获取节点内的纯文本和HTML文本;若传入参数,则是进行赋值操作。
6.3 remove
从名字就能知道,remove 方法的作用是移除节点,在某些情况下,它能极大地便利信息提取。下面给出一段 HTML 文本,咱们接着分析它的应用。
html = '''
<div class="wrap">Hello, World<p>This is a paragraph.</p></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
wrap = doc('.wrap')
print(wrap.text())
现在你想提取“Hello, World”这个字符串,同时排除 p 节点内部的字符串,该怎么做呢? 这里直接先试着提取 class 为 wrap 的节点的内容,看看是否是我们想要的结果。下面是运行结果:
从这个结果能看出,它还包含了内部 p 节点的内容,也就是说 text 方法把所有纯文本都提取出来了。若想去掉 p 节点内部的文本,一种做法是先提取 p 节点内的文本,再从整个结果里移除这个子串,但这种做法显然比较繁琐。 这时就可以发挥 remove 方法的作用了,我们可以接着这样操作:
wrap.find('p').remove()
print(wrap.text())
首先我们选中 p 节点,接着调用 remove() 方法把它移除掉。此时,wrap 节点内部就只剩下“Hello, World”这句话了,之后利用 text() 方法就能把它提取出来。 此外,实际上还有不少节点操作的方法,像 append()、empty() 和 prepend() 等,这些方法的用法和 jQuery 完全相同。若想了解详细用法,可以参考官方文档:[http://pyquery.readthedocs.io/en/latest/api.html](http://pyquery.readthedocs.io/en/latest/api.html)
七、伪类选择器
CSS 选择器如此强大,一个重要原因是它支持丰富多样的伪类选择器。这些伪类选择器能实现很多特殊的选择功能,比如选择第一个节点、最后一个节点、奇偶数节点,以及包含特定文本的节点等。下面通过示例来具体说明:
html = '''
<div class="wrap"><div id="container"><ul class="list"><li class="item-0">first item</li><li class="item-1"><a href="link2.html">second item</a></li><li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li><li class="item-1 active"><a href="link4.html">fourth item</a></li><li class="item-0"><a href="link5.html">fifth item</a></li></ul></div></div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('li:first-child')
print(li)
li = doc('li:last-child')
print(li)
li = doc('li:nth-child(2)')
print(li)
li = doc('li:gt(2)')
print(li)
li = doc('li:nth-child(2n)')
print(li)
li = doc('li:contains(second)')
print(li)
在这个示例里,我们运用了 CSS3 的伪类选择器,分别选中了第一个 li 节点、最后一个 li 节点、第二个 li 节点、第三个 li 节点之后的所有 li 节点、偶数位置的 li 节点,以及包含“second”文本的 li 节点。 若你想了解 CSS 选择器更多的用法,可以参考 [http://www.w3school.com.cn/css/index.asp](http://www.w3school.com.cn/css/index.asp)。
至此,pyquery 的常用用法就介绍完毕了。要是你还想了解更多内容,可查阅 pyquery 的官方文档:[http://pyquery.readthedocs.io](http://pyquery.readthedocs.io)。我们相信,有了 pyquery 的助力,网页解析将不再困难。
学习参考书籍:Python 3网络爬虫开发实战