方法选择器
前面讲的选择方法都是基于属性来选择的,这种方法快,但是进行一些比较复杂的选择时,会变得比较繁琐,不够灵活,好在Beautiful Soup还为我们提供了一些查询方法,例如find_all和find等,调用这些方法再传入相应的参数,就可以灵活查询了。
find_all
顾名思义就是查询所有符合条件的元素,可以给他传入一些属性或文本来得到符合条件的元素,功能非常强大,具体API如下:
find_all(name,attrs,recursive,text,**kwargs)
name
我们可以根据name参数来查询元素,下面用实例来感受一下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')))
运行结果如下:[<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
<class 'bs4.element.ResultSet'>
这里调用了find_all方法,向其中传入name参数,其参数值为ul,意思是查询所有ul节点。返回的结果是列表类型,长度为2,说明一共有两个ul节点,列表中的每个元素依然都是'bs4.element.ResultSet'类型
因为都是Tag类型,所以依然可以进行嵌套查询,下面我们就先查询所有ul节点,查出后再查询其内部的li节点
soup = BeautifulSoup(html,'lxml')
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
运行结果:[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
返回的结果是列表类型,列表中的每个属性依旧是Tag类型。
接着我们就可以表里每个li节点,就可以得到他们的内容了。
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
attrs
除了根据节点名查询,我们也可以传入一些属性进行查询。下面用实例感受一下:
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1" name='element'>
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(attrs={'id':'list-1'}))
print(soup.find_all(attrs={'name':'element'}))
运行结果如下:[<ul class="list" id="list-1" name="element">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<ul class="list" id="list-1" name="element">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
这里查询的时候传入的是attrs参数,其属于字典类型,例如要查询id为list-1的节点,就可以传入{'id':'list-1'}作为查询条件,得到的结果是列表形式,列表中的内容就是符合id为list-1这一条件的所有节点,在上面的实例中,符合条件的元素个数是1,所以返回结果的长度是为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'))
运行结果如下:[<ul class="list" id="list-1" name="element">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
这里直接传入id='list-1',就可以查询id为list-1的节点元素了。而对于class来说,由于class在Python里是一个关键字,所以需要在后面加一个下划线_,即class_='element',返回结果依然是Tag对象组成的列表。
text
text参数可以用来匹配节点的文本,其传入的形式可以是字符串,也可以是正则表达式,示例如下:
html='''
<div class="panel">
<div class="panel-body">
<a>Hello, this is a link</a>
<a>Hello, this is a link, too</a>
</div>
</div>
'''
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']
这里有两个a节点,其中内部包含文本信息,这里在find_all方法中传入text参数,该参数为正则表达式,返回结果是由所有与正则表达式相匹配的节点文本组成的列表。
find
除find_all方法外,还有find方法也可以查询符合条件的对象,只不过find返回的结果只有单个元素,也就是第一个匹配的元素,由于用法类似就不展开细说了。
css选择器
Beautiful Soup 还提供了另外一种选择器——css选择器,如果有学习或者前端工作的小伙伴那么肯定对css选择器不陌生。
使用css选择器,只需要调用select方法,传入相应的CSS选择器即可。我们用一个实例感受一下:
html='''<div class="panel">
<div class="panel-heading">
<h4>Hell0</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-samll" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>'''
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(type(soup.select('ul')[0]))
运行结果:
[<div class="panel-heading">
<h4>Hell0</h4>
</div>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
<class 'bs4.element.Tag'>
这里我们用了三次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'))
运行结果:[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
可以看到,正常输出了每个ul节点下的所有li节点组成的列表。
获取属性
既然知道节点是Tag类型。于是获取属性依然可以使用原来的方法。还是基于上面的HTML文本,这里尝试获取每个ul节点的id属性。
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
for ul in soup.select('ul'):
print(ul['id'])
print(ul.attrs['id'])
运行结果:
list-1
list-1
list-2
list-2
可以看到,直接将属性名传入中括号和通过attrs属性获取属性值,都是可以成功获取属性的。
获取文本
要获取文本,可以使用之前前面说到的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)
运行结果如下:
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
Get Text: Jay
String: Jay
Get Text: Foo
String: Foo
Get Text: Bar
String: Bar
二者的效果是完全一致的,都可以获取节点的文本值。
总结
至此,Beautiful Soup的介绍基本就结束了,最后做一下简单的总结。
- 推荐使用LXML解析库,必要时使用html.parser。
- 节点选择器筛选能力弱,但是速度快。
- 建议使用find,find_all方法查询匹配的单个结果或者多个结果。
- 如果对CSS选择器熟悉,则可以使用select选择法