全称:XML Path Language,即XML路径语言。
XPath提供了非常简洁明了的路径选择表达式。还提供了100个内键函数,用于字符串、数值、时间的匹配以及节点、序列的处理等。
XPath常用规则
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从当前节点选取直接子节点 |
// | 从当前节点选择子孙节点 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
举例:
//title[@lang='eng']
选择所有名称为title,同时属性lang的值为eng的节点。
下载lxml库
pip install lxml
from lxml import etree
text = '''
'''
# 使用HTML类初始化,构建一个XPath解析对象
html = etree.HTML(text)
# 输出修正后的html代码
result = etree.tostring(html)
print(result.decode('utf-8'))
首先导入lxml库的etree模块,然后声明一个HTML文本,调用etree的HTML类进行初始化,这里就成功构建了一个XPath解析对象。
注意:声明的文本中最后一个li节点是没有闭合的,但是etree模块自动修正了HTML文本。
调用tostring()方法,即可输出修正后的HTML,但是结果是bytes类型。这里利用decode()方法将其转换为str类型。
# 结果
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>
看到结果发现,li节点标签被补全了,而且还自动添加了body、html节点。
另外,还可直接读取文本文件进行解析:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
# 输出修正后的html代码
result = etree.tostring(html)
print(result.decode('utf-8'))
test.html文件中的代码就是上一个案例声明的代码。
这次输出的结果略有不同,多了一个DOCTYPE的声明,不过对解析无影响。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div></body></html>
我们一般会用//开头的XPath规则来选取所有符合要求的节点。
这里以前面的HTML文本为例,如要要选取出所有节点,可以这样实现:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)
运行结果如下:
[<Element html at 0x234ab053648>, <Element body at 0x234ab053748>, <Element div at 0x234ab053788>, <Element ul at 0x234ab0537c8>, <Element li at 0x234ab053808>, <Element a at 0x234ab053888>, <Element li at 0x234ab0538c8>, <Element a at 0x234ab053908>, <Element li at 0x234ab053948>, <Element a at 0x234ab053848>, <Element li at 0x234ab053988>, <Element a at 0x234ab0539c8>, <Element li at 0x234ab053a08>, <Element a at 0x234ab053a48>]
这里使用*来匹配所有节点,也就是整个HTML文本中的所有节点都会被获取。可以看到,返回形式是一个列表,每个元素是Element类型,其后跟了节点的名称,如html、body、div、ul、li、a等。
匹配也可以指定节点名称。如果想要获取所有li节点:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)
这里选取所有li节点,可以使用//加上节点名即可。
[<Element li at 0x2e3ff7d0748>, <Element li at 0x2e3ff7d0788>, <Element li at 0x2e3ff7d07c8>, <Element li at 0x2e3ff7d0808>, <Element li at 0x2e3ff7d0848>]
提取结果是一个列表形式。其中每个元素都是一个Element对象。如果想要取出其中一个对象,可以直接用中括号加索引,例[0]
可以通过/或//查找子节点或子孙节点。
/ 是查找子节点
// 是查找子孙节点
选择 li 节点中的所有直接 a 子节点:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)
要获取 ul 节点下的所有子孙 a 节点:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)
如果这里用了//ul/a,就无法获取任何结果了。因为/用于获取直接子节点,而ul节点下没有a节点。
通过 … 来查找父节点。
首先选中href属性为link4.html的a节点,然后在获取其父节点,然后其class属性:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)
'''
结果:
['item-1']
'''
通过parent::来获取父节点:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)
'''
结果:
['item-1']
'''
使用@符号进行属性过滤
选取class为item-0的 li 节点:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)
'''
# 结果:
[, ]
'''
我们通过加入[@class=“item-0”],限制了节点的class属性为item-0。
用XPath中的text()方法获取节点中的文本
获取前面li节点中的文本:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)
'''
结果:
['\r\n']
'''
没有获取到任何文本,只是获取到了回车符和换行符,这是为什么?
因为XPath中text()前面是/,而此处/的含义是选取直接子节点,很明显li的直接子节点都是a节点,文本都是在a节点内部的,所以这里匹配的结果就是被修正的li节点内部的回车符和换行符,因为自动修正的li节点的尾标签换行了。
因此,如想获取li节点内部文本,就有两种方式:一是先选取a节点在获取文本,二是使用//
第一种:先选取a节点在获取文本
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)
'''
结果:
['first item', 'fifth item']
'''
第二种:使用//
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)
'''
结果:
['first item', 'fifth item', '\r\n']
'''
根据结果发现,返回的是3个结果。这里是选取子孙节点的文本,其中前两个是li的子节点中a节点内部的文本,最后一个是li节点内部的文本。
用@就可以获取属性的值
获取所有li节点下a节点的href值:
from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)
'''
结果:
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
'''
通过@href获取节点的href属性。
和属性匹配不同,属性匹配是中括号加属性名和值来限定某个属性:[@class=“item-0”]
属性获取的@href指的是获取节点的某个属性。
通过contains()方法,第一个参数传入属性,第二个参数传入属性值。
text = '''
first item
'''
这里的HTML文本中li节点的class属性有两个值分别是li和li-first
使用contains()方法,匹配class值为li和li-first的li节点:
from lxml import etree
text = '''
first item
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li li-first")]')
print(result)
'''
结果:
[]
'''
使用运算符连接
根据多个属性来确定一个节点,可以使用and符号连接
from lxml import etree
text = '''
first item
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)
'''
结果:
['first item']
'''
li节点有增加一个name属性。同时需要根据class属性和name属性来选择,一个条件是class属性里面包含li字符串,另一个条件是name属性为item字符串,二者需要同时满足,需要使用and符号相连,相连之后之置于中括号内进行条件筛选。
运算符及其介绍
运算符 | 描述 |
---|---|
and | 与 |
or | 或 |
mod | 计算除法的余数 |
| | 计算两个节点集 |
+ | 加法 |
- | 减法 |
* | 乘法 |
div | 除法 |
= | 等于 |
!= | 不等于 |
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
在选择的时候某些属性可能同时匹配了多个节点,但是只想要其中的某个节点。
[index] 中括号中传入下标,从1开
result = html.xpath('//li[1]/a/text()')
[last()] 最后一个
result = html.xpath('//li[last()]/a/text()')
[position()❤️] 位置小于3的
result = html.xpath('//li[position()<3]/a/text()')
[last()-2 ] 倒数第三个,last()是最后一个,所以last()-2就是倒数第三个
result = html.xpath('//li[last()-2]/a/text()')
XPath提供了很多节点轴选择方法,包括获取子元素,兄弟元素、父元素、祖先元素等
ancestor轴,获取祖先节点
result = html.xpath('//li[1]/ancestor::*')
# 获取所有祖先节点
result = html.xpath('//li[1]/ancestor::div')
# 获取祖先节点中的所有div节点
attribute轴,获取所有属性值
result = html.xpath('//li[1]/attribute::*')
child轴,获取所有直接子节点
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
descendat轴,获取所有子孙节点
result = html.xpath('//li[1]/descendant::span')
# 限制必须是span节点
following轴,获取当前节点之后的所有节点
result = html.xpath('//li[1]/following::*[2]')
# 有加了索引选择,所以只获取2个后续节点
flolowing-sibling轴,获取当前节点之后的所有同级节点
result = html.xpath('//li[1]/follwing-sibling')
Beautiful Soup 就是Python的一个HTML或XML的解析库
pip install lxml
pip install beautifulsoup4
Beautiful soup实际上是依赖解解析器的。
它除了支持Python标准库中的HTML解析器外,还支持一些第三方解析器(比如:lxml)。
Beautiful soup支持的解析器
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | Python的内置标准库、执行速度适中,文档容错能力强。 | Python2.7.3及3.2.2之前的版本文档容错能力差。 |
lxml HTML解析器 | BeautifulSoup(markup, “lxml”) | 速度快、文档容错能力强 | 需要安装C语言库 |
lxml XML解析器 | BeautifulSoup(markup, “xml”) | 速度快,唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 速度慢、不依赖外部扩展 |
html = '''
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie
Tillie;
and they lived at the bottom of a well.
...
'''
# 从bs4中导入BeautifulSoup
from bs4 import BeautifulSoup
# 生成一个BeautifulSoup对象,参数1:html文本,参数2:解析器
soup = BeautifulSoup(html, 'lxml')
# prettify()方法是可以把要解析的字符串格式化
print(soup.prettify())
print(soup.title.string)
'''
结果:
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie
Tillie
;
and they lived at the bottom of a well.
...
The Dormouse's story
'''
首先是声明一个变量html,它是一个HTML字符串。注意,并不是一个完整的HTML字符串,因为body和html节点都没有闭合。
接着将声明的html作为第一个参数传个BeautifulSoup对象,改对象的第二个参数为解析器的类型(这里使用lxml),此时就完成了BeautifulSoup对象的初始化,并赋值给soup变量。
接下来,就可以调用soup()各个方法和属性解析这串HTML代码了。
首先,使用prettify()方法。这个方法可以把要解析的字符串以标准的缩进格式输出。需要注意的是:不标准的HTML字符串是由BeautifulSoup自动修正的,prettify()方法只是按照缩进标准格式化了代码。
然后调用了soup.title.string,这实际上是输出HTML中title节点的文本内容。所以soup.title可以选出HTML中的title节点,再调用string属性就可以得到里面的文本了。
直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本了。这种选择方式速度快。如果单个节点结构层次清晰,可以选用这种方式来解析。
html = '''
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie
Tillie;
and they lived at the bottom of a well.
...
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
'''
结果:
The Dormouse's story
The Dormouse's story
The Dormouse's story
The Dormouse's story
'''
首先打印输出的是title节点的选择结果,输出的结果是节点加上节点里面的内容。
接下来输出它的类型,是bs4.element.Tag类型,经过选择器后,选择结构都是这种Tag类型。Tag具有一些属性,比如string,调用该属性可以得到节点内的文本
接下来又选择了head节点,输出的结果是节点加上节点里面的内容。
又选择了p节点,发现结果是第一个p节点的内容,后面的几个p节点并没有选到。也就是说,当有多个节点的时候,这种方式只会选择到第一个匹配的节点,其他的节点都会被忽略。
上面调用string属性来获取文本的值,那么如何获取节点属性的值?
可以利用name属性获取节点的名称。
print(soup.title.name)
'''
结果:
title
'''
调用attr获取节点的所有属性
print(soup.p.attrs)
print(soup.p.attrs['name'])
'''
结果:
{'class': ['title'], 'name': 'dormouse'}
dormouse
'''
attrs返回结果是字典类型。它把选择的节点的所有属性和属性值组合成一个字典,
接下来想要获取name属性,就相当于从字典中获取某个键值。
其实还有更简单的获取方式:
print(soup.p['name'])
print(soup.p['class'])
'''
结果:
dormouse
['title']
'''
使用string属性获取节点元素包含的文本内容
比如:获取第一个p节点的文本
html = '''
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie
Tillie;
and they lived at the bottom of a well.
...
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.string)
'''
结果:
The Dormouse's story
# 注意,这里选择到的p节点是第一个p节点,所以获取的文本也是第一个p节点里面的文本。
'''
从上面得知,每一个返回的结果都是bs4.element.Tag类型,它同样可以继续调用节点进行下一步的选择。
html = '''
The Dormouse's story
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)
'''
结果:
The Dormouse's story
The Dormouse's story
'''
需要先选取一个节点元素,然后依照这节点元素为基准再选择它的子节点、父节点、兄弟节点等。
子节点:contents属性 children属性 子孙节点:descendants属性
选取节点元素后,如果想要获取它的直接子节点,可以调用contents属性。
html = '''
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie
Lacie
and
Tillie;
and they lived at the bottom of a well.
...
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.contents)
'''
运行结果:
['\n Once upon a time there were three little sisters; and their names were\n ',
Elsie
, '\n', Lacie, '\nand\n', Tillie, ';\nand they lived at the bottom of a well.\n']
'''
返回的结果是列表形式。列表中的每个元素都是p节点的直接子节点。比如第一个a节点里面包含一层span节点,这相当于子孙节点了,但是返回结果并没有单独把span节点选出来。所以说contents属性得到的结果是直接子节点的列表。
获取直接子节点调用children属性 有与contents相同的结果:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
# enumerate()函数,可以将一个可遍历对象组合为一个索引序列,同时列出下标和数据在元组中。
'''
结果:
0
Once upon a time there were three little sisters; and their names were
1
Elsie
2
3 Lacie
4
and
5 Tillie
6 ;
and they lived at the bottom of a well.
'''
这里调用children属性,返回一个生成器,使用for循环输出相应内容。
获取子孙节点使用descendants属性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.descendants)
for i, child in enumerate(soup.p.descendants):
print(i, child)
'''
结果:
0
Once upon a time there were three little sisters; and their names were
1
Elsie
2
3 Elsie
4 Elsie
5
6
7 Lacie
8 Lacie
9
and
10 Tillie
11 Tillie
12 ;
and they lived at the bottom of a well.
'''
返回的结果还是一个生成器,遍历输出可以看到,这输出结果就包含了span节点。
descendants会递归查询所有子节点,得到所有的子孙节点。
父节点:parent, 祖先节点:parents
获取某个节点元素的父节点,可以调用parent属性
html = '''
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie
Lacie
and
Tillie;
and they lived at the bottom of a well.
...
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parent)
'''
结果:
Once upon a time there were three little sisters; and their names were
Elsie
Lacie
and
Tillie;
and they lived at the bottom of a well.
'''
我们选择的是第一个a节点的父节点元素。很明显,它的父节点是p节点,输出结果便是p节点及其内部的全部内容。
获取某个节点的祖先节点,使用parents属性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.a.parents)
print(type(soup.a.parents))
print(list(enumerate(soup.a.parents)))
返回的结果是生成器类型。这里用列表输出了它索引和内容,而列表中的元素就是a节点的祖先节点。
next_sibling:下一个兄弟节点,返回元素
previous_sibling:上一个兄弟节点,返回元素
next_siblings:所有后面的兄弟节点,返回生成器类型
previous_siblings:所有前面的兄弟节点,返回生成器类型
html = '''
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie
Hello
Lacie
and
Tillie;
and they lived at the bottom of a well.
...
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print('next_sibling>>>', soup.a.next_sibling)
print('previous_sibling>>>', soup.a.previous_sibling)
print('next_siblings>>>', list(enumerate(soup.a.next_siblings)))
print('previous_siblings>>>', list(enumerate(soup.a.previous_siblings)))
和上面节点选择器的提取信息,使用的方法一样
获取节点名称:name
获取节点属性:attrs
获取节点文本:string
查询所有符合条件的元素,返回所有匹配的元素
find_all(name, attrs, recursive, text, **kwargs)
可以根据节点名来查询元素:
html = '''
Hello
- Foo
- Bar
- Jay
- Foo
- Bar
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
'''
结果:
[
- Foo
- Bar
- Jay
,
- Foo
- Bar
]
'''
这里调用了find_all()方法,传入name参数,其参数值为ul。也就是说,查询所有ul节点,返回结果是列表类型,长度为2:,每个元素依然都是bs4.element.Tag类型。
因为都是Tag类型,所以依然可以进行嵌套查询。还是同样的文本,这里查询出所有ul节点或,在继续查询其内部的li节点:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
'''
结果:
[Foo , Bar , Jay ]
[Foo , Bar ]
'''
返回的结果是列表类型,列表中的每个元素依然还是Tag类型。
接下来,遍历每个li,获取它的文本:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
for li in ul.find_all(name='li'):
print(li.string)
'''
结果:
[Foo , Bar , Jay ]
Foo
Bar
Jay
[Foo , Bar ]
Foo
Bar
'''
还可以根据,节点属性来查询:
html = '''
Hello
- Foo
- Bar
- Jay
- Foo
- Bar
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={
'id': 'list-1'}))
print(soup.find_all(attrs={
'name': 'elements'}))
'''
结果:
[
- Foo
- Bar
- Jay
]
[
- Foo
- Bar
- Jay
]
'''
这里查询的时候传入的是attrs参数,参数的类型是字典类型。比如,要查询id为list-1的节点,可以传入attrs={‘id’ : ‘list-1’}的查询条件。得到的结果是列表类型,包含的内容就是符合id为list-1的所有节点。
对于一些常用的属性,比如id、class等,可以不同attrs来传递。比如要查询id为list-1的节点,可以直接传入id这个参数。还是上面的文本:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
'''
结果:
[
- Foo
- Bar
- Jay
]
[Foo , Bar , Jay , Foo , Bar ]
'''
这里直接传入id=‘list-1’,就可以查询id为list-1的节点元素。
而对于class来说,由于class在Python是关键字,所以后面需要加上一个下划线,即class_=‘element’,返回的结果依然还是Tag组成的列表。
text参数可用来匹配节点的文本,传入形式可以是字符串,可以是正则表达式对象:
html = '''
'''
import re
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text=re.compile('link')))
'''
结果:
['Hello, this is a link', 'Hello, this is a link, too']
'''
find()方法,返回的是单个元素,也就是第一个匹配的元素。
html = '''
Hello
- Foo
- Bar
- Jay
- Foo
- Bar
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
'''
结果:
- Foo
- Bar
- Jay
- Foo
- Bar
- Jay
'''
这里返回的不再是列表,而是第一个匹配的节点元素,类型依然是Tag。
还有其他的查询方法,用法和find_all()、find()用法一样,只不过是查询范围不一样:
find_parents() 和 find_parent():前者返回所有祖先节点,后者返回直接父节点。
find_next_siblings() 和 find_next_sibling():前者返回后面的所有兄弟节点,后者返回后面第一个兄弟节点。
find_previous_siblings() 和 find_previous_sibling():前者返回前面所有兄弟节点,后者返回上一个兄弟节点。
find_all_next() 和
() :前者返回节点后所有符合条件的节点,后者返回节点后第一个符合条件的节点。
find_all_previous() 和 find_previous():前者返回节点前所有符合条件的节点,后者返回节点前第一个符合条件的节点。
使用css选择器时,只需要调用select()方法,传入相应的css选择器即可。
html = '''
Hello
- Foo
- Bar
- Jay
- Foo
- Bar
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list2 .element'))
print(type(soup.select('ul')[0]))
'''
结果:
[
Hello
]
[Foo , Bar , Jay , Foo , Bar ]
[Foo , Bar ]
'''
用了3次css选择器,返回的结果均是符合css选择器节点组成的列表。
例如select(‘ul li’)则是返回所有ul节点下面的所有li节点,结果便是所有li节点组成的列表。
最后一句打印输出了列表中元素的类型。可以看出,类型依然是Tag
select()方法同样支持嵌套选择。
例如:先选择所有ul节点,在遍历每个ul节点,选择器li节点:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul.select('li'))
'''
结果:
[Foo , Bar , Jay ]
[Foo , Bar ]
'''
我们知道节点类型是Tag类型,也可以用前面讲的attrs方法
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):
print(ul.attrs)
print(ul['id'])
print(ul.attrs['id'])
'''
结果:
{'class': ['list'], 'id': 'list-1', 'name': 'elements'}
list-1
list-1
{'class': ['list', 'list-small'], 'id': 'list2'}
list2
list2
'''
两个方法:string、get_text()
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for li in soup.select('li'):
print('Get Text:', li.get_text())
print('String:', li.string)
'''
结果:
String: Foo
Get Text: Bar
String: Bar
Get Text: Jay
String: Jay
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
'''
推荐使用lxml解析器,必要时使用html.parser
节点选择筛选功能弱,但是速度快
建议使用find()或者find_all()查询匹配单个结果或者多个结果
如果对CSS选择器熟悉的话,可以使用select()方法选择。
安装pyquery库
需要传入一个HTML文本来初始化一个PyQuery对象。初始化方式有很多种,比如直接传入字符串、传入URL、传入文件名
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
print(doc('li'))
'''
结果:
first item
second item
third item
fourth item
fifth item
'''
首先引入PyQuery对象,然后声明一个长HTML字符串,并将其当做参数传递给PyQuery类,这样就完成了初始化。接下来,将给这个实例传入li节点,这就就可以选择li节点。
初始化的参数还可以传入网页的URL
from pyquery import PyQuery
doc = PyQuery(url="https://www.baidu.com")
print(doc('title'))
PyQuery对象首先会请求这个URL,然后用得到的HTML内容完成初始化,就相当于用网页的源代码以字符串的形式传递给PyQuery类来初始化。
初始化的参数可以传递本地文件名,将参数指定为filename即可:
from pyquery import PyQuery
# test.html内的代码就是字符串初始化中html变量对应的代码
doc = PyQuery(filename="test.html")
print(doc('li'))
'''
结果:
first item
second item
third item
fourth item
fifth item
'''
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
# 选取id为container节点下面的clas为list节点下面的li节点
print(doc('#container .list li'))
'''
# 结果:
first item
second item
third item
fourth item
fifth item
'''
初始化PyQuery对象之后,传入一个CSS选择器’#container .list li’,它的意思是选取id为container节点,然后在选取其内部class为list的节点内部的li节点。然后打印输出。
根据结果发现,它的类型是pyquery.pyquery.PyQuery——PyQuery类型。
需要使用find()方法,传入参数是CSS选择器
子孙节点:find()
子节点:children()
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
# 找到class为list的节点
items = doc.find('.list')
print(type(items))
print(items)
print('-'*10, '分界线', '-'*10)
# 从class为list的节点内部找到li节点
print(type(items.find('li')))
print(items.find('li'))
'''
结果:
- first item
- second item
- third item
- fourth item
- fifth item
---------- 分界线 ----------
first item
second item
third item
fourth item
fifth item
'''
首先,选取class为list的节点,然后调用find()方法,传入CSS选择器,选取内部的li节点。
find()方法,会将符合条件的所有节点全部选择出来。
find()的查找范围是节点的所以子孙节点
如果想查找直接子节点可使用children()方法
from pyquery import PyQuery
doc = PyQuery(html)
items = doc.find('.list')
lis = items.children()
print(type(lis))
print(lis)
'''
结果:
first item
second item
third item
fourth item
fifth item
'''
如果要筛选所有子节点中符合条件的节点,比如筛选出子节点中class为active的节点,可以向children()方法传入CSS选择器.active:
from pyquery import PyQuery
doc = PyQuery(html)
items = doc.find('.list')
lis = items.children('.active')
print(type(lis))
print(lis)
'''
结果:
third item
fourth item
'''
使用parent()方法来获取某节点的父节点。
直接父节点:parent()
祖先节点:parents()
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
items = doc('.list')
container = items.parent()
print(type(container))
print(container)
'''
结果:
- first item
- second item
- third item
- fourth item
- fifth item
'''
首先用.list选取class为list的节点,然后调用parent()方法得到其父节点。其类型依旧是PyQuery类型。
如果要查询祖先节点,使用parents()方法:
from pyquery import PyQuery
doc = PyQuery(html)
items = doc('.list')
container = items.parents()
print(type(container))
print(len(container))
print(container)
'''
结果:
2
- first item
- second item
- third item
- fourth item
- fifth item
- first item
- second item
- third item
- fourth item
- fifth item
'''
输出结果有两个:一个是class为wrap的节点,一个是id为container的节点。也就是说parents()方法会返回一个节点的所有祖先节点。一层层返回。
如果想获取某个祖先节点,可以向parents()里传入CSS选择器:
from pyquery import PyQuery
doc = PyQuery(html)
items = doc('.list')
# 只想要class为wrap的祖先节点
parent = items.parents('.wrap')
print(parent)
'''
结果:
- first item
- second item
- third item
- fourth item
- fifth item
'''
这样输出的结果就只保留了一个,保留了class为wrap的节点。
siblings()
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
items = doc('.list .item-0.active')
# .item-0.active中间没有空格,证明这个是在形容一个节点
print(items.siblings())
'''
结果:
second item
first item
fourth item
fifth item
'''
选择了class为list的节点内的class为item-0和active的节点。然后打印出这个节点的兄弟节点。
根据结果发现,打印出了符合条件节点的上下所有兄弟节点。
如果想筛选出某个节点可以向siblings()方法中传入CSS选择器:
from pyquery import PyQuery
doc = PyQuery(html)
items = doc('.list .item-0.active')
print(items.siblings('.active'))
'''
结果:
fourth item
'''
这里筛选了class为active的节点。
pyquery的选择结果可能是多个节点,也可能是单个节点。类型是PyQuery类型,并没有返回Beautiful Soup那样的列表。
对于单个节点来说,可以直接打印输出,也可以直接转换成字符串。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
li = doc('.item-0.active')
print(li)
print(str(li))
'''
结果:
third item
third item
'''
对于多个节点的结果,我们就需要遍历来获取了。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
lis = doc('li').items()
for li in lis:
print(type(li))
print(li)
'''
结果:
first item
second item
third item
fourth item
fifth item
'''
调用items()方法会得到一个生成器,然后遍历这个生成器就可以得到每一个li节点。它的类型是PyQuery类型。
每个li可以继续调用前面说的方法,比如继续查询子节点、寻找某个祖先节点。
抓取到某个节点是PyQuery类型的后,可以调用attr()方法来获取属性。
attr(‘属性名称’)
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
a = doc('.item-0.active a')
print(type(a))
print(a.attr('href'))
'''
结果:
link3.html
'''
首先选择class为item-0和active的节点中的a节点。这个a节点的类型是PyQuery类型的。然后调用attr()方法,在这里方法中传入属性名称。
如果选中的是多个元素,就需要先遍历对每一个元素调用attr()方法:
from pyquery import PyQuery
doc = PyQuery(html)
a = doc('a')
for i in a.items():
print(i.attr('href'))
'''
结果:
link2.html
link3.html
link4.html
link5.html
'''
多个元素,需要使用items()方法将其转换为生成器然后遍历这个生成器中的每个元素,这时候每个元素都是PyQuery类型的,在对其使用attr()方法,提取属性。
text():忽略标签内的HTML部分,返回纯文本。
html():返回标签内的HTML部分和纯文本部分。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
a = doc('.item-0.active a')
print(a)
print(a.text())
'''
结果:
third item
third item
'''
选中class为item-0和active节点下的a节点后,对其使用text()方法提取文本,该方法会忽略其内部所有HTML代码。只返回纯文本部分。
如果想要返回内部的HTML代码部分,需要使用html()方法:
from pyquery import PyQuery
doc = PyQuery(html)
a = doc('.item-0.active a')
print(a)
print(a.html())
'''
结果:
third item
third item
'''
如果选中的结果是多个节点,那么使用html()和text()会返回什么内容?
html = '''
'''
from pyquery import PyQuery
doc = PyQuery(html)
li = doc('li')
print(li.html())
print(li.text())
print(type(li.text()))
'''
结果:
second item
second item third item fourth item fifth item
'''
在多元素的情况下:
html()方法:返回的是第一个节点中的HTML文本
text()方法:返回所有节点中的纯文本部分,并且用空格隔开,返回类型是str字符串类型。
如果想获取多元素的内部HTML文本需要对其使用遍历,然后在对其使用html()方法提取文本。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
li = doc('.item-0.active')
print(li)
li.removeClass('active')
print(li)
li.addClass('active')
print(li)
'''
结果:
third item
third item
third item
'''
首先选中一个li节点,然后调用removeClass()方法删除active这个class,然后又调用addClass()方法,将active添加回来。
attr()方法对属性进行操作。
text()和html()方法来改变节点内部的内容。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
li = doc('.item-0.active')
print(li)
li.attr('name', 'link')
print(li)
li.text('changed item')
print(li)
li.html('changed item')
print(li)
'''
结果:
third item
third item
changed item
changed item
'''
首先查找到class为item-0和active的节点。
使用attr()方法,添加一个name属性,值是link。
再使用text()方法,传入文本后,将其节点内的文本更改为changed item。
再使用html()方法,传入HTML文本后,将其接节点内的文本改为传入的HTML文本。
attr()方法:传入第一个值是获取属性,传入第二个参数,可以用来修改属性值。
text() 和 html():如果不传参数是获取纯文本和HTML文本。如果传入参数则是修改文本内容。
remove()方法就是移除。
从文本中提取出Hello, World这个字符串,而不要p节点内部的字符串。
html = '''
Hello, World
This is a paragraph.
'''
from pyquery import PyQuery
doc = PyQuery(html)
wrap = doc('.wrap')
print(wrap.text())
'''
结果:
Hello, World
This is a paragraph.
'''
结果还是包含了p节点的内容。这时候使用remove()就可以解决:
from pyquery import PyQuery
doc = PyQuery(html)
# 找到class为wrap的节点,将其赋值给wrap
wrap = doc('.wrap')
# 从wrap中找到p节点,并删除掉
wrap.find('p').remove()
print(wrap.text())
'''
结果:
Hello, World
'''
首先找到p节点,使用remove()方法删除,此时只剩下Hello, World这句话了,在调用text()方法,从wrap中提取出来。
html = '''
- first item
- second item
- third item
- fourth item
- fifth item
'''
from pyquery import PyQuery
doc = PyQuery(html)
# 第一个li节点
li = doc('li:first-child')
print(li)
# 最后一个li节点
li = doc('li:last-child')
print(li)
# 第2个li节点
li = doc('li:nth-child(2)')
print(li)
# 第三个之后的li节点
li = doc('li:gt(2)')
print(li)
# 偶数位置的li节点
li = doc('li:nth-child(2n)')
print(li)
# 文本包含second的li节点
li = doc('li:contains(second)')
print(li)