目录
引言
一、方法选择器
1.1 find_all
1.2 find
二、CSS选择器
三、总结
引言
上一节中我们指出正则表达式在网页数据提取上的不足,引出强大的网页解析工具 Beautiful Soup,强调其能依网页结构与属性解析,简化数据提取工作。并且已经学习了Beautiful Soup的基本使用,并实践了节点选择器的应用,现在继续学习Beautiful Soup后面的部分。
一、方法选择器
前面讲的选择网页元素的办法,都是通过属性来选的。这种方式速度快,可要是遇到复杂一点的选择需求,就会显得麻烦,不够灵活。好在,Beautiful Soup 还给我们准备了一些查询方法,像 find_all 和 find 等。调用这些方法,再传入对应的参数,就能灵活地查询网页元素了。
1.1 find_all
从名字就能大概猜到,find_all 的作用是找出所有符合条件的元素。我们可以给它传入各种属性或者文本内容,从而得到符合条件的元素,功能十分强大。
它的API(应用程序编程接口)是这样的:
find_all(name, attrs, recursive, text, **kwargs)
(1) 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')[0]))
运行结果:
在这个例子里,我们调用了 find_all 方法,给它传入了 name 参数,值设为 ul。这就意味着,我们要查询所有的 ul 节点。最后返回的结果是列表类型,里面有2个元素,而且每个元素都是 bs4.element.Tag 类型。
因为这些元素都是 Tag 类型,所以还能接着进行嵌套查询。还是用刚才那段HTML文本,在查询出所有 ul 节点后,再去查询它们内部的 li 节点:
for ul in soup.find_all(name='ul'):print(ul.find_all(name='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)
运行结果如下:
(2)attrs
除了根据节点名来查询,我们也能传入一些属性信息来进行查询。下面还是通过一个例子来直观感受一下:
html='''
<div class="panel"><div class="panel-heading"><h4>Hello</h4></div><div class="panel-body"><ul class="list" id="list-1" name="elements"><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': 'elements'}))
运行结果:
在这个查询过程中,我们传入的是 attrs 参数,它的类型是字典。比如说,要查询 id 是 list - 1 的节点,就可以传入 attrs={'id': 'list-1'} 这样的查询条件。最终得到的结果是列表形式,里面包含的就是所有符合 id 为 list - 1 的节点。在上面这个例子里,符合条件的元素只有1个,所以结果列表的长度是1。
对于一些常见的属性,比如 id 和 class 等,我们其实不用通过 attrs 来传递参数。例如,要查询 id 为 list - 1 的节点,直接传入 id 这个参数就行。还是用刚才那段HTML文本,换种方式来查询:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))
运行结果如下:
这里直接传入 id='list-1',就能查询到 id 为 list - 1 的节点元素了。而对于 class 这个属性,因为在Python里 class 是关键字,所以要在后面加个下划线,写成 class_='element'。最后返回的结果,依旧是由 Tag 类型元素组成的列表。
(3)string
string 参数主要用来匹配节点里的文本内容。它可以接受字符串形式的参数,也可以接受正则表达式对象作为参数。看个例子:
import re
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>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(string=re.compile('link')))
运行结果:
这里有两个 a 节点,里面都有文本信息。在调用 find_all() 方法的时候,我们传入了 text 参数,并且给它赋值为一个正则表达式对象。最终返回的结果,是一个列表,里面包含了所有能匹配上这个正则表达式的节点文本。
1.2 find
除了 find_all 方法,还有个 find 方法。这两个方法的区别在于,find 方法只会返回单个元素,也就是第一个匹配到的元素;而 find_all 方法返回的是所有匹配元素组成的列表。看个例子:
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(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
运行结果:
从运行结果可以看到,返回的不再是列表形式,而是第一个匹配上的节点元素,并且这个元素的类型依然是 Tag 类型。
另外,还有不少其他的查询方法,它们的用法和前面介绍的 find_all、find 方法差不多,只是查询的范围不太一样。这里简单说一下:
- find_parents 和 find_parent:find_parents 会返回所有祖先节点,find_parent 只返回直接父节点。
- find_next_siblings 和 find_next_sibling:find_next_siblings 返回后面所有的兄弟节点,find_next_sibling 返回后面第一个兄弟节点。
- find_previous_siblings 和 find_previous_sibling:find_previous_siblings 返回前面所有的兄弟节点,find_previous_sibling 返回前面第一个兄弟节点。
- find_all_next 和 find_next:find_all_next 返回节点后面所有符合条件的节点,find_next 返回第一个符合条件的节点。
- find_all_previous 和 find_previous:find_all_previous 返回节点前面所有符合条件的节点,find_previous 返回第一个符合条件的节点。
二、CSS选择器
Beautiful Soup 还提供了一种选择器,就是 CSS 选择器。要是你熟悉Web开发,那对 CSS 选择器肯定也不陌生。要是不熟悉,你可以参考 [http://www.w3school.com.cn/cssref/css_selectors.asp](http://www.w3school.com.cn/cssref/css_selectors.asp) 去了解一下。
使用 CSS 选择器的时候,只需要调用 select 方法,然后把对应的 CSS 选择器传进去就行。通过一个例子来看看:
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.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
运行结果如下:
在这个例子里,我们用了3次 CSS 选择器。每次返回的结果,都是符合 CSS 选择器条件的节点组成的列表。比如说,select('ul li') 这个选择器的意思是,选择所有 ul 节点下面的 li 节点,所以结果就是所有 li 节点组成的列表。
最后一行代码,我们打印输出了列表里元素的类型,可以看到还是 Tag 类型。
(1)嵌套选择
select 方法也支持嵌套选择。比如说,我们先选择所有的 ul 节点,然后再遍历每个 ul 节点,去选择它们里面的 li 节点。例子如下:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for ul in soup.select('ul'):print(ul.select('li'))
运行结果如下:
从结果可以看到,成功输出了遍历每个 ul 节点后,其下面所有 li 节点组成的列表。
(2)获取属性
我们知道,通过选择器得到的节点类型是 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'])
运行结果如下:
从结果能看出,不管是直接用中括号加上属性名,还是通过 attrs 属性来获取属性值,都能成功得到 ul 节点的 id 属性值。
(3)获取文本
要获取节点里的文本内容,当然可以用前面提到的 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)
运行结果:
从运行结果可以看出,这两种方法的效果是一样的,都能获取到节点的文本值。
三、总结
到这里,关于BeautifulSoup的使用介绍基本就结束了。最后简单总结一下:
- 建议优先使用 lxml 解析库,特殊情况需要时再用html.parser。
- 用节点直接选择元素,筛选功能相对弱些,但速度快。
- 推荐使用find、find_all方法来查询匹配单个或多个结果。
- 要是你对CSS选择器比较熟悉,那就可以用select选择法。