pyquery

基本CSS选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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')))

输出结果:
<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>

<class 'pyquery.pyquery.PyQuery'>

这里我们初始化pyquery对象之后,传入了一个CSS选择器#container.list li 它的意思是先选取id为container的节点,然后再选取其内部的class为list的节点内部的所有li节点。然后,打印输出。

查找节点

pyquery中的查询函数与jquery中函数的用法完全相同

子节点

查询子节点时,需要用到find()方法,此时传入的参数是CSS选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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)
items = doc('.list')
print(type(items))
print(items)
lis = items.find('li')
print(type(lis))
print(lis)

输出结果:
<class 'pyquery.pyquery.PyQuery'>
<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>

<class 'pyquery.pyquery.PyQuery'>
<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>

这里我们先选取class为list节点,然后调用find()方法,传入CSS选择器,选取其内部的li节点,最后打印输出

find()方法会将符合条件的所有节点选择出来

查找范围是节点的所有子孙节点,而如果我们只想查找子节点,那么可以用children()方法

1
2
3
4
5
6
7
8
9
10
11
lis=items.children()
print(type(lis))
print(lis)

输出结果:
<class 'pyquery.pyquery.PyQuery'>
<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>

如果要筛选所有子节点中符合条件的节点,例如筛选出子节点中class为active的节点,可以向children()方法传入CSS选择器.active:

1
2
3
4
5
6
lis=items.children('.active')
print(lis)

输出结果:
<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>

父节点

我们可以用parent()方法来获取某个节点的父节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)

输出结果:
<class 'pyquery.pyquery.PyQuery'>
<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>

这里首先用.list选取class为list的节点,然后调用parent()方法得到其父节点

这里的父节点是该节点的直接父节点,也就是说它不会再去查找父节点的父节点,即祖先节点

如果想要获取祖先节点,可以用parents()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pyquery import PyQuery as pq
doc = pq(html)
items = doc('.list')
parents = items.parents()
print(type(parents))
print(parents)

输出结果:
<class 'pyquery.pyquery.PyQuery'>
<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><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>

输出结果可以看到有两个,一个是class为wrap的节点,一个是id为container的节点,也就是说parents()方法会返回所有的祖先节点

如果想要筛选某个祖先节点的话,可以向parents()方法传入CSS选择器,这样就会返回祖先节点中CSS选择器的节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
parents = items.parents('.wrap')
print(parents)

输出结果:
<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>

这样就只会输出wrap这个祖先节点了

兄弟节点

如果想要查询兄弟节点,那就可以使用siblings()方法

1
2
3
4
5
6
7
8
9
10
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings())

输出结果:
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0">first item</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>

item-0的兄弟节点有4个,所以输出了所有满足条件的li节点

如果想要筛选某个兄弟节点,只需传入CSS选择器

1
2
3
4
5
6
7
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.list .item-0.active')
print(li.siblings('.active'))

输出结果:
<li class="item-1 active"><a href="link4.html">fourth item</a></li>

这里就只输出的class为active的节点

遍历

对于单个节点来说,可以直接打印输出,也可以直接转成字符串:

1
2
3
4
5
6
7
8
9
10
from pyquery import PyQuery as pq
doc = pq(html)
li = doc('.item-0.active')
print(li)
print(str(li))

输出结果:
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>

多个节点的结果,就需要遍历来获取了,我们需要调用items()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pyquery import PyQuery as pq
doc = pq(html)
lis = doc('li').items()
print(type(lis))
for li in lis:
print(li, type(li))

输出结果:
<class 'generator'>
<li class="item-0">first item</li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-1"><a href="link2.html">second item</a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<class 'pyquery.pyquery.PyQuery'>
<li class="item-0"><a href="link5.html">fifth item</a></li>
<class 'pyquery.pyquery.PyQuery'>

调用items()方法后,会得到一个生成器,遍历一下,就可以逐个得到li节点对象了

获取信息

获取属性

提到属性,基本上就与前面的一样使用attrs之类的方法,pyquery也不例外,使用attr()方法来获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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属性来获取属性

1
2
3
4
print(a.attr.href)

输出结果:
link3.html

这两种方法完全一样

attr()方法只会得到第一个节点的属性

想要获取所有a节点的属性,就要用到前面所说的遍历:

1
2
3
4
5
6
7
8
9
10
11
from pyquery import PyQuery as pq
doc = pq(html)
a = doc('a')
for item in a.items():
print(item.attr('href'))

输出结果:
link2.html
link3.html
link4.html
link5.html

获取文本

获取节点之后的另一个主要操作就是获取内部的文本了,我们可以调用text()方法来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 href="link3.html"><span class="bold">third item</span></a>
third item

抓取猫眼电影排行

因为这周目标只想把解析器结束,所以看的不是很多,打算做一个简单的实验,抓取一下猫眼电影的排行

抓取分析

访问站点https://maoyan.com/board/4,打开之后便可以查看到榜单信息
avatar

接下来换页,发现URL变成了https://maoyan.com/board/4?offset=10

URL比之前多了一个参数,而结果显示的是排行11-20名的电源,所以推断这个是一个偏移量的参数,如果排行是21-30的电影的话,参数就会变为20

抓取首页

爬取内容之前,先用抓取源码测试一下是否连通

1
2
3
4
5
6
7
8
9
10
import requests

url="https://maoyan.com/board/4?offset=0"
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36'
}

response=requests.get(url,headers=headers)
if response.status_code==200:
print(response.text)

运行之后就可以得到源码,证明可以抓取到

正则提取

F12打开开发者工具,在Network监听组件中,就可以查看到源代码

利用霸王别姬的来做分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <dd>
<i class="board-index board-index-1">1</i>
<a href="/films/1203" title="霸王别姬" class="image-link" data-act="boarditem-click" data-val="{movieId:1203}">
<img src="//s3plus.meituan.net/v1/mss_e2821d7f0cfe4ac1bf9202ecf9590e67/cdn-prod/file:5788b470/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
<img data-src="https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@160w_220h_1e_1c" alt="霸王别姬" class="board-img" />
</a>
<div class="board-item-main">
<div class="board-item-content">
<div class="movie-item-info">
<p class="name"><a href="/films/1203" title="霸王别姬" data-act="boarditem-click" data-val="{movieId:1203}">霸王别姬</a></p>
<p class="star">
主演:张国荣,张丰毅,巩俐
</p>
<p class="releasetime">上映时间:1993-07-26</p> </div>
<div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">5</i></p>
</div>

</div>
</div>

</dd>

可以看到与这个排行相关的都套在一个dd节点中

并且电源的排行信息是class为board-index的i节点内
所以构造正则就是

1
<dd>.*?board-index.*?>(.*?)</li>

还需要提取到电影的图片,仅检查后发现,a标签中的第二个img节点的data-src后面跟的是图片的地址
所以正则为
1
<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)"

还需要提取电影的名称,它在p节点内,class为name,所以正则修改为
1
<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>

用正则表达式测试工具测试之后,成功得到数据
avatar

测试代码为:

1
2
3
pattern=re.compile('<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>',re.S)
items=re.findall(pattern,html)
print(items)

最后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
import re
import time

def geturl(i):
url="https://maoyan.com/board/4?offset="+str(i)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
}
response=requests.get(url,headers=headers,allow_redirects=False)
# print(response.content.decode('utf-8'))
pattern = re.compile('<dd>.*?board-index.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>', re.S)
items = re.findall(pattern, response.text)
print(items)

if __name__ == '__main__':
for i in range(10):
geturl(i * 10)
time.sleep(1)

总结

1.pyquery的CSS选择器方式对于熟悉html的人来说,更容易上手,但是功能相对于beautiful soup较为简单

2.beautiful soup , xpath , pyquery这三个解析器中,xpath是基础,beautiful soup功能较为复杂,但是更加灵活,pyquery较为简单,适合简单的工作

3.总结下来三个选择器常用的方法都差不多,查询子节点用children(),查询父母节点用parent(),祖先节点用parents(),查询属性用attrs() pyquery用attr(),获取文本用text()

4.使用pyquery前,需要实例化一个对象,而XPATH使用时只需要导入etree库,就可以使用,beautiful soup只需要导入Beautiful soup库,直接soup后面接方法即可

5.后两个解析器几乎都是由Xpath衍生出来的,目前最常用的是Xpath和beautiful soup

6.通过带师傅的讲解,明白了需要用函数来规范会操作,方便读代码和逻辑方面

7.当遇到重定向问题时,request库中的allow_redirects参数,可以禁止自动重定向

8.如果遇到反爬虫机制,可以采用time.sleep来让其延时等待,还可以BP抓包,讲headers的数据全部添加进去,模拟登录