3.3 pyquery的使用
3.3.1 准备工作
安装pyquery库,可以使用pip3安装pip3 install pyquery
3.3.2 初始化
在用pyquery库解析HTML文本的时候,需要先将其初始化为一个PyQuery对象。初始化方式包括直接传入字符串、传入URL、传入文件名等。
1)字符串初始化
直接把HTML的内容当作初始化参数,来初始化PyQuery对象。实例如下:
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 itemk/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('li'))
结果如下:
解释说明:这里首先引入PyQuery这个对象,取别名为pg。然后声明一个长HTML字符串,并将其当作参数传递给PyQuery类,这样就成功完成了初始化。接着,将初始化的对象传入CSS选择器。在这个实例中,我们传入li节点,这样就可以选择所有的li节点了。
2)URL初始化
初始化的参数除了能以字符串形式传递,还能是网页的URL,此时只需要指定PyQuery对象的参数为url即可,实例如下:
from pyquery import PyQuery as pq
doc =pq(url='https://cuiqingcai.com')
print(doc('title'))
解释说明:PyQuery对象会首先请求这个URL,然后用得到的HTML内容完成初始化,其实就相当于把网页的源代码以字符串形式传递给PyQuery类,来完成初始化操作。
实现相同功能:
from pyquery import PyQuery as pq
import requests
doc =pq(requests.get('https://cuiqingcai.com').text)
print(doc('title'))
结果如下:
3)文件初始化
除上面两种,还可以传递本地的文件名,此时将参数指定为filename即可,示例如下:
from pyquery import Pyquery as pq
doc =pq(filename='demo.html')
print(doc('li'))
注意:这里需要有一个本地HTML文件demo.html,其内容是待解析的HTML字符串。这样,PyQuery对象会首先读取本地的文件内容,然后将文件内容以字符串的形式传递给PyQuery类进行初始化。以上3种初始化方式均可采用,但常用的是以字符串形式传递。
3.3.3 基本CSS选择器
(1)实例导入
html='''
<div id="container">
<ul class="list">
<li class="item-O">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-O"><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的节点内部的所有li节点,然后打印输出。最后,将符合条件的节点的类型打印输出,可以看到依然是PyQuery类型。
(2)获取节点文本内容
先直接遍历获取的节点,然后调用text方法,就可以直接获取节点的文本内容了,代码如下:
for item in doc('#container .list li').items():
print(item.text())
(3)pyquery库的用法
包括如何查找节点、遍历节点,并获取各种信息等。
3.3.4 查找节点
(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)
解释说明:这里先通过.list参数选取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)
(2)父节点
我们可以用parent方法获取某个节点的父节点,实例如下:
html='''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-o">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-o 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方法得到其父节点,其类型依然是PyQueryc。这里的父节点是指直接父节点,也就是说,parent方法不会继续查找父节点的父节点,即祖先节点。
如果就想获取某个祖先节点,这时可以用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)
(3)兄弟节点
获取兄弟节点可以使用siblings方法。这里还是以上面的HTML文本为例:
from pyquery import PyQuery as pq
doc =pq(html)
li=doc('.list .item-o.active')
print(li.siblings())
解释说明:这里首先选择class为list的节点内部的class为item-0和active的节点,也就是第三个li节点。那么,很明显,其兄弟节点有4个,就是第一个、第二个、第四个、第五个li节点。
如果要筛选某个兄弟节点,依然可以向siblings方法传入CSS选择器,这样就能从所有兄弟节点中挑选出符合条件的节点了:
from pyquery import PyQuery as pq
doc =pq(html)
li=doc('.list .item-o.active')
print(li.siblings('.active'))
这里我们筛选了class为active的节点,通过刚才的结果可以观察到,class为active的兄弟节点只有第4个li节点满足,所以结果应该只包含一个节点。
3.3.5 遍历节点
pyquery库的选择结果可能是多个节点,也可能是单个节点,类型都是PyQuery类型,并没有像Beautiful Soup那样返回列表。
如果结果是单个节点,既可以直接打印输出,也可以直接转成字符串:
from pyquery import PyQuery as pq
doc =pq(html)
li=doc('.item-o.active')
print(li)
print(str(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。
(1)获取信息
提取到节点后,最终目的是提取节点包含的信息。比较重要的信息有两类,一是属性、二是文本。
获取属性
提取到某个PyQuery类型的节点后,可以调用attr方法获取其属性:
html = '''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-0">first itemk/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-O"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pqdoc =pq(html)
a=doc('.item-0.active a')
print(a,type(a))
print(a.attr('href'))
解释说明:这里首先选中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'))
因此,在获取属性时,可以观察返回的节点是一个还是多个,如果是多个,则需要遍历才能依次获取每个节点的属性。
(2)获取文本
获取节点之后的另一个主要操作就是获取其内部的文本,此时可以调用text方法实现:
html='''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-o">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-o 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-o.active a')
print(a)
print(a.text())
解释说明:这里首先选中a节点,然后调用text方法,就可以获取其内部的文本信息。此时text方法会忽略节点内部包含的所有HTML,只返回纯文字内容。
要想获取节点内部的HTML文本,需要用html方法:
from pyquery import PyQuery as pq
doc =pq(html)
li=doc('.item-o.active')
print(li)
print(li.html())
解释说明:这里选中了第三个li节点,然后调用了html方法,返回结果应该是li节点内的所有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方法返回的是第一个1i节点内部的HTML文本,而text返回了所有的1i节点内部的纯文本,各节点内容中间用一个空格分割开,即返回结果是一个字符串。
注意事项:如果得到的结果是多个节点,并且想获取所有节点的内部HTML文本,就需要遍历这些节点。而text方法不需要遍历即可获取,会对所有节点取文本之后合并成一个字符串。
3.3.6 节点操作
pyquery库提供了一系列方法对节点进行动态修改,如为某个节点添加一个class,移除某个节点等,有时候这些操作会为提取信息带来极大的便利。
(1)addClass和removeClass
实例导入:
html='''
<div class="wrap">
<div id="container">
<ul class="list">
<li class="item-o">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-o active">ka href="link 3.html"><span class="bold">third item</spany</a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-O"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</div>
'''
from pyquery import PyQuery as pq
doc =pq(html)
li =doc('.item-o.active')
print(li)
li.removeClass('active')
print(li)
li.addClass('active')
print(li)
原理:首先选中了第三个li节点,然后调用removeClass方法,将其中的active这个class移除,然后又调用addClass方法,将这个class添加回来。每执行一次操作,就会打印一次当前li节点的内容。可以看到,一共输出了3次。第二次输出时,li节点的active这个class已经不见了,第三次这个class又有了。所以说,addClass和removeClass方法可以动态改变节点的class属性。
(2)attr、text和html
除了addClass和removeClass方法,也可以用attr方法对属性进行操作。此外,text和html方法可以用来改变节点内部的内容。实例如下:
html='''
<ul class="list">
<li class="item-o 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-o.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方法修改其属性,该方法的第一个参数为属性名,第二个参数为属性值。接着,调用text和html方法改变li节点内部的内容。每次操作后,都会打印出当前的li节点。
解释说明:调用attr方法后,li节点多了一个原本不存在的属性name,其值为link。接着调用text方法,传入文本之后,li节点内部的文本全被改为传入的字符串文本了。最后,调用html方法传入HTML文本后,li节点内部又变为了传入的HTML文本。
注意:如果attr方法只传入第一个参数,即属性名,则表示获取这个属性值;如果传入第二个参数,则可以用来修改属性值。text和html方法如果不传参数,表示的是获取节点内的纯文本和HTML文本;如果传入参数,则表示进行赋值。
(3)remove
remove方法的作用是移除,有时会为信息的提取带来非常大的便利。实例根据一段文本提取字符串:
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等。
3.3.7 伪类选择器
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 itemk/a></li>
<li class="item-o"><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)
get方法的作用是从SelectorList里面提取第一个Selector对象,然后输出其中的结果。
再看实例:
result =selector.xpath('//li[contains(@class,"item-o")]//text()').get()
print(result)
解释说明:这里使用//li[contains(@class,"item-0“)]//text()选取了所有class包含item-0的li节点的文本内容。准确来说,返回结果SelectorList应该对应三个li对象,而这里get方法仅仅返回了第一个li对象的文本内容,因为它其实只会提取第一个Selector对象的结果。
getall方法。如果要提取所有对应的li节点的文本内容,写法如下:
result =selector.xpath('//li[contains(@class,"item-0")]//text()').getall()
print(result)
因此,如果要提取SelectorList里面对应的结果,可以使用get或getall方法,前者会获取第一个Selector对象里面的内容,后者会依次获取每个Selector对象对应的结果。
另外在上述案例中,如果把xpath方法改写成css方法,可以这么实现:
result =selector.css('.item-0 *::text').getall()
print(result)
这里*用来提取所有子节点(包括纯文本节点),提取文本需要再加上::text,最终的运行结果和上面是一样。
3.3.8 提取属性
直接在XPath或者CSS选择器中表示出来即可。
例如我们提取第三个li节点内部的a节点的href属性,写法如下:
from parsel import Selector
selector =Selector(text=html)
result =selector.css('.item-O.active a::attr(href)').get()
print(result)
result =selector.xpath('//li[contains(@class,"item-o") and contains(@class,"active")]/a/@href').get()
print(result)
这里实现了两种写法,分别用css和xpath方法实现。我们以同时包含item-0和active两个class为依据,来选取第三个li节点,然后进一步选取了里面的a节点。对于CSS选择器,选取
3.3.9 正则提取
除了常用的css和xpath方法,Selector对象还提供了正则表达式提取方法,实例引入:
from parsel import Selector
selector =Selector(text=html)
result =selector.css('.item-o').re('link.*')
print(result)
原理:这里先用css方法提取了所有class包含item-0的节点,然后使用re方法传入了link.*,用来匹配包含link的所有结果。
解释说明:re方法在这里遍历了所有提取到的Selector对象,然后根据传入的正则表达式,查找出符合规则的节点源码并以列表形式返回。
当然,如果在调用css方法时,已经提取了进一步的结果,例如提取了节点文本值,那么re方法就只会针对节点文本值进行提取:
from parsel import Selector
selector =Selector(text=html)
result =selector.css('.item-o*::text').re('.*item')
print(result)
也可以利用re_first方法来提取第一个符合规则的结果:
from parsel import Selector
selector =Selector(text=html)
result =selector.css('.item-o').re_first('<span class="bold">(.*?)</span>')
print(result)
原理:这里调用了refirst方法,提取的是被span标签包含的文本值,提取结果用小括号括起来表示一个提取分组,最后输出的结果就是小括号包围的部分。
3.3.10 小结
通过一些实例,了解了正则匹配的一些使用方法,re对应多个结果,re_first对应单个结果,在不同情况下可以选择合适的方法进行提取。