BeautifulSoup是一个可以从HTML或XML文件中提取数据的Python库。
它可以通过你喜欢的转换器实现惯用的文档导航、查找、修改等方式。
我们所使用的文档解析函数如下:
from bs4 import BeautifulSoup
BeautifulSoup(
markup='',
features=None,
builder=None,
parse_only=None,
from_encoding=None,
exclude_encodings=None,
**kwargs,
)
树形结构里,每个节点都是Python对象,所有对象可以归纳为4种:
Tag,NavigableString,BeautifulSoup,Comment
Tag对象与XML或HTML原生文档中的tag相同。
soup=BeautifulSoup(' ')
#获取标签
soup.p
#
Tag有很多属性(遍历文档数和搜索文档树中有详细解释),最重要的属性:name和attributes。
soup.p.name
# 'p'
soup.p.name='q'
soup
#
soup.q['class']
# ['body','language']
soup.q.attrs
# {'class': ['body', 'language']}
soup.q['id']=1
soup
#
soup.q.get('class')
#['body', 'language']
如果某个属性看起来有多个值,但是任何版本的HTML定义中都没有定义过,那么BeautifulSoup会将这个属性作为字符串输出。
如果转换的是XML格式的文档,那么tag中不包含多值属性。
id_soup=BeautifulSoup('')
id_soup.p.attrs
#{'id': 'my id'}
tag.string.replace_with(' ') #tag.string不能为空
BeautifulSoup对象表示的是一个文档的大部分内容, 大部分时候,可以把它当做Tag对象,但是它并不是真正的HTML或XML的Tag,所以它没有name和attributes属性,但是BeautifulSoup对象包含了一个值为’[document]'的特殊属性 .name。(BeautifulSoup对象里面的标签tag有.name属性)
Comment对象是一个特殊类型的NavigableString对象,它是文档的注释部分以及一些特殊字符串。
a=""
soup=BeautifulSoup(a)
comment=soup.b.string
type(comment)
# 'bs4.element.Comment'
下面是《爱丽丝梦游仙境》的一段内容,后面的分析将一直引用该例子:
html_doc = """睡鼠的故事
睡鼠的故事
从前有三位小姐姐,她们的名字是:
埃尔西,
莱斯和
蒂尔莉;
她们住在一个井底下面。
...
"""
一个Tag的子节点可以是其他的Tag或者是多个字符串。
BeautifulSoup提供了很多操作和遍历子节点的属性。
注意:字符串没有属性,因为字符串没有子节点。
for string in soup.strings:
print(repr(string))
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
for string in soup.stripped_strings:
print(repr(string))
每个tag或字符串都有父节点(基本都包含在某个tag中)。
同一层(是同一个元素的子节点)的节点,可以被称为兄弟节点,兄弟节点有相同的缩进级别。
a href="http://example.com/elsie" class="sister" id="link1">Elsie,
a href="http://example.com/lacie" class="sister" id="link2">Lacie and
a href="http://example.com/tillie" class="sister" id="link3">Tillie;
如果以为第一个< a >标签的 .next_sibling 结果是第二个< a >标签,那就错了,真实结果是第一个< a >标签和第二个< a >标签之间的逗号和换行符。(可以自己用 print(soup.prettify()) 输出查看,可以发现’ ,\n’和< a >处于同一层。)
for sibling in soup.a.next_siblings:
print(repr(sibling))
for sibling in soup.find(id="link3").previous_siblings:
print(repr(sibling))
HTML解析器把这段字符串转换成一连串的事件: “打开< html >标签”,”打开一个< head >标签”,”打开一个< title>标签”,”添加一段字符串”,”关闭< title>标签”,”打开< p>标签”,等等。Beautiful Soup提供了重现解析器初始化过程的方法。
Beautiful Soup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() 。其它方法的参数和用法类似。
介绍 find_all() 方法前,先介绍一下过滤器的类型 ,这些过滤器贯穿整个搜索的API。过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中。
soup.find_all('b')
#[睡鼠的故事]
match(pattern, string, flags=0)
Try to apply the pattern at the start of the string(一个字符串的开始部分), returning
a Match object, or None if no match was found.
import re
for tag in soup.find_all(re.compile('b')):
print(tag.name)
#body
#b
soup.find_all(['a','b'])
#[睡鼠的故事,
#埃尔西,
#莱斯,
# 蒂尔莉]
for tag in soup.find_all(True):
print(tag.name)
#html
#head
#title
#body
#p
#b
#p
#a
#a
#a
#p
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False。
下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True:
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
将这个方法作为参数传入 find_all() 方法,将得到所有< p >标签:
soup.find_all(has_class_but_no_id)
# [The Dormouse's story
,
# Once upon a time there were...
,
# ...
]
find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
Extracts a list of Tag objects that match the givencriteria.
find_all() 方法 搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。
soup.find_all(id='link2')
# [Lacie]
搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True 。
soup.find_all(href=re.compile("elsie"))
# [Elsie]
data_soup = BeautifulSoup('foo!')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression
但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag。
data_soup.find_all(attrs={"data-foo": "value"})
# [foo!]
soup.find_all(string="Elsie")
# ['Elsie']
soup.find_all(text="Elsie")
# ['Elsie']
soup.find_all("a", class_="sister")
# [Elsie,
# Lacie,
# Tillie]
class_ 参数同样接受不同类型的 过滤器 ,字符串,正则表达式,方法或 True。
如果tag的class属性是多值属性,按照CSS类名搜索tag时,分别搜索tag中的每个CSS类名,就可以得出搜索全类名的效果(也可以完全匹配,完全匹配时,类名顺序不能乱)。
由于find_all()几乎是BeautifulSoup中最常用的搜索方法,所以我们定义了它的简写方法.。BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:
soup.find_all("a")
soup("a")
soup.title.find_all(string=True)
soup.title(string=True)
find(self, name=None, attrs={}, recursive=True, text=None, **kwargs)
Return only the first child of this Tag matching the given
criteria.
有时我们只想得到一个结果,可以直接使用find()方法,相当于直接使用find_all方法并设置limit=1参数。
唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果。
find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None。
find_parents(self, name=None, attrs={}, limit=None, **kwargs)
Returns the parents of this Tag that match the given
criteria.
find_parent(self, name=None, attrs={}, **kwargs)
Returns the closest parent of this Tag that matches the given
criteria.
记住: find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等.。
find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同。
find_next_sibling(self, name=None, attrs={}, text=None, **kwargs)
Returns the closest sibling to this Tag that matches the
given criteria and appears after this Tag in the document.
find_next_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)
Returns the siblings of this Tag that match the given
criteria and appear after this Tag in the document.
find_previous_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)
Returns the siblings of this Tag that match the given
criteria and appear before this Tag in the document.
find_previous_sibling(self, name=None, attrs={}, text=None, **kwargs)
Returns the closest sibling to this Tag that matches the
given criteria and appears before this Tag in the document.
在Tag 或 BeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag。Beautiful Soup支持大部分的CSS选择器。
soup.select("html head title")
# [The Dormouse's story ]
soup.select("head > title")
# [The Dormouse's story ]
soup.select("#link1 ~ .sister")
# [Lacie,
# Tillie]
soup.select("#link1 + .sister")
# [Lacie]
soup = BeautifulSoup("Foo")
soup.a.append("Bar")
soup
# FooBar
soup.a.contents
# ['Foo', 'Bar']
soup = BeautifulSoup("")
original_tag = soup.b
new_tag = soup.new_tag("a", href="http://www.example.com")
original_tag.append(new_tag)
original_tag
#
tag.insert(1, "but did not endorse ")
soup.i.extract()
i_tag.string.extract()