入门
Soup的生成
# 导包
from bs4 import BeautifulSoup
# 传入文件句柄
with open("index.html") as fp:
soup = BeautifulSoup(fp, 'html.parser')
# 字符串
soup = BeautifulSoup("<html>a web page</html>", 'html.parser')
对象树中的四种对象
BeautifulSoup将复杂的HTML文档转换为复杂的Python对象树。
但只有四种对象我们关心。
Tag
这里的标签指的就是 html 或者 xml 里面的标签,如 body、a、h 等等。
我们关心的是名称和属性。
# tag指某一具体标签
soup.tag
# 链式访问
soup.tag1.tag2.tag3
# 不过只能获取第一个 a 标签(可以使用后面的 find_all获取所有的a标签)
soup.a
名称
每个标签都有一个名称,可以通过.name以下方式访问:
# 名称
soup.tag.name
属性
标签可以具有任意数量的属性。
你可以通过将标签视为字典来访问标签的属性。
tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
# python的字典访问
tag['id']
# 'boldest'
# .attrs返回字典
tag.attrs
# {'id': 'boldest'}
# 判断是否拥有某个属性
tag.has_attr("id")
其实还可以修改它,但这里就不说了。
多值属性
有些属性可以有多个值。如class。
Beautiful Soup将多值属性的值显示为列表。
css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
css_soup.p['class']
# ['body', 'strikeout']
NavigableString
其实就是在标签里面写的,我们能看到的字符串。
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
tag = soup.b
tag.string
# 'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>
BeautifulSoup
该BeautifulSoup对象代表了整个解析后的文档。对于大多数目的,您可以将其视为Tag 对象。
如 soup 就是对象树的根部tag。
Comment
似乎就是那些注释?maybe
总之,它不重要。
文档对象树的遍历
必须要说的,只有tag才能迭代遍历,string不可以,因为string没有“孩子”。
向下走
使用标签名称导航
就这么简单,直接访问就好。
# tag指某一具体标签
soup.tag
# 链式访问
soup.tag1.tag2.tag3
.contents 和 .children
一个标签的孩子列表的获取方法。
两种方法区别不大。
print(type(soup.html.children))
print(type(soup.html.contents))
# <class 'list_iterator'>
# <class 'list'>
字符串没有.contents。
.descendants
递归地获取孩子、孩子的孩子。
自己测试一下,就知道它的解析逻辑了。
.string 和 .strings、stripped_strings
-
如果标签只有一个孩子,而该孩子是一个NavigableString,则该孩子可以使用.string。
-
使用.strings生成器获取文档中所有的可见字符串。
-
使用stripped_strings去掉生成的很多的换行。
向上走
.parent
您可以使用.parent属性访问元素的父级。
.parents
您可以使用遍历元素的所有父对象 .parents。此示例用于.parents从埋在文档深处的标记移动到文档的最顶部。
link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
for parent in link.parents:
print(parent.name)
# p
# body
# html
# [document]
向一边走
同一标记的直接子代。我们称他们为兄弟姐妹。
.next_sibling和.previous_sibling
要小心,在实际文档中,标签的.next_sibling或.previous_sibling通常通常是包含空格的字符串。
# <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
# <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
# <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
link = soup.a
link.next_sibling.next_sibling
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
.next_siblings和.previous_siblings
您可以使用.next_siblings或.previous_siblings遍历标签的同级元素。
文档对象树的搜索
对于爬虫者来说,搜索应该让人更感兴趣。
演示用的html
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
过滤器
过滤器其实就是你指定如何去搜索指定的东西。
在详细讨论find_all()和类似方法之前,我想展示可以传入这些方法的不同过滤器的示例。这些过滤器会在整个搜索API中一次又一次地显示。您可以使用它们根据标签的名称,属性,字符串文本或它们的某种组合进行过滤。
字符串
最简单的过滤器是字符串。将字符串传递给搜索方法,Beautiful Soup将对该字符串进行匹配。此代码查找文档中的所有的b标签:
soup.find_all('b')
#[<b>The Dormouse's story</b>]
正则表达式
如果传入正则表达式对象,Beautiful Soup将使用其search()方法针对该正则表达式进行过滤。此代码查找名称以字母“ b”开头的所有标签;在这种情况下,标签和标签:
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
此代码查找名称中包含字母“ t”的所有标签:
for tag in soup.find_all(re.compile("t")):
print(tag.name)
# html
# title
列表
如果您传递列表,Beautiful Soup将允许对该列表中的任何项目进行字符串匹配(或的关系)。此代码查找所有a标签 和所有b标签。
soup.find_all(["a", "b"])
# [<b>The Dormouse's story</b>,
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
True
该值True匹配所有可能的值。此代码查找文档中的所有标签,忽略到任何文本字符串。
函数
你可以传入一个函数,就像一个谓词一样。
- 函数的参数是Tag
默认情况下,谓词函数的参数是 tag 对象
# 只要那些有class属性,而没有id属性的标签,
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
# <p class="story">Once upon a time there were…bottom of a well.</p>,
# <p class="story">...</p>]
- 函数的参数是Tag的属性
如果您传入一个函数以过滤特定属性(例如) href,则传递给该函数的参数将是属性值,而不是整个标记。
import re
def not_lacie(href):
return href and not re.compile("lacie").search(href)
soup.find_all(href=not_lacie)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
find_all
函数原型,返回值
find_all(name, attrs, recursive, string, limit, **kwargs)
该find_all()方法浏览标签的后代,并检索与过滤器匹配的所有后代。
举例
soup.find_all("title")
# [<title>The Dormouse's story</title>]
soup.find_all("p", "title")
# [<p class="title"><b>The Dormouse's story</b></p>]
soup.find_all("a")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.find_all(id="link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
import re
soup.find(string=re.compile("sisters"))
# 'Once upon a time there were three little sisters; and their names were\n'
下面函数的参数做出解释。
因为它find_all()是Beautiful Soup搜索API中最受欢迎的方法,所以可以为其使用快捷方式。如果将 BeautifulSoup对象或Tag对象视为函数,则与调用find_all()该对象相同。这两行代码是等效的:
# 这也是等效的
soup.find_all("a")
soup("a")
# 这也是等效的
soup.title.find_all(string=True)
soup.title(string=True)
name
只考虑标签的名字进行搜索,忽略属性、可见字符串。
soup.find_all("title")
# [<title>The Dormouse's story</title>]
attrs
# 可以直接指定
soup.find_all(id='xxx')
# 同时指定多个:
soup.find_all(id='xxx',href=href_filter);
# 通过字典传入
soup.find_all(attrs = {'id' : 'xxx', 'name':'form_name'})
- CSS class
如果是对于class属性的话,即可以使用attrs传入。
soup.find_all("a", attrs={"class": "sister"})
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
不过还有更简洁的做法:
使用class_
css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>]
css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]
string
根据 可见字符串 进行匹配。
与 name和参数关键字一样,您可以传入字符串,正则表达式,列表,函数或值True。
soup.find_all(string = "Elsie")
# ['Elsie']
注意这时返回的是匹配的字符串列表。
如果你要根据string匹配并且返回tag列表,你必须显式加上name
soup.find_all(name = True,string = "Elsie")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
limit
find_all()返回与您的过滤器匹配的所有标记和字符串。如果文档较大,则可能需要一段时间。如果您不需要所有结果,可以输入的数字limit。就像SQL中的LIMIT关键字一样工作。它告诉Beautiful Soup在找到一定数量后停止收集结果。
soup.find_all("a", limit=2)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
recursive
您只希望soup考虑直接的孩子,则可以传递recursive=False
。
默认是True。
find家族
除了前面长篇大论说的 find_all,
还有其他很实用的find方法。
find()
相当于设置find_all的limit为1 ;
唯一的区别是,find_all()返回包含单个结果的列表,而find()仅返回结果。
如果find_all()找不到任何内容,则返回一个空列表。如果 find()找不到任何内容,则返回None。
find_parents()和find_parent()
前面的 find find_all都是往”后代找“。
find_parents()和find_parent()是往树上面找。
看函数的名字就知道,一个一直往上搜索,另一个仅仅直系parent。
find_next_siblings()和find_next_sibling()
这些方法使用.next_siblings遍历树中元素的其余同级。该 find_next_siblings()方法返回所有匹配的兄弟姐妹,并且find_next_sibling()仅返回第一个。
find_previous_siblings()和find_previous_sibling()
这些方法使用.previous_siblings遍历树中位于其之前的元素的同级元素。该find_previous_siblings() 方法返回所有匹配的兄弟姐妹,并且 find_previous_sibling()仅返回第一个。
find_all_next()和find_next()
这些方法使用.next_elements遍历文档中紧随其后的所有标签和字符串。该find_all_next()方法返回所有匹配项,并且 find_next()仅返回第一个匹配项。
find_all_previous()和find_previous()
这些方法使用.previous_elements遍历文档中位于其之前的标签和字符串。该find_all_previous()方法返回所有匹配项,并且 find_previous()仅返回第一个匹配项。
CSS选择器
- 通过CSS类查找标签
soup.select(".sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.select("[class~=sister]")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
- 按ID查找标签
soup.select("#link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
soup.select("a#link2")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
- 测试属性的存在
soup.select('a[href]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]