Loading

BeautifulSoup模块

BeautifulSoup

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。Beautiful Soup会帮你节省数小时甚至数天的工作时间。

中文文档

1 安装模块

$ apt-get install Python-bs4 # Debain或ubuntu
pip install beautifulsoup4 # pip

2 安装解析器

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是lxml。根据操作系统不同,可以选择下列方法来安装lxml:

$ apt-get install Python-lxml # Debain或ubuntu
$ pip install lxml

另一个可供选择的解析器是纯Python实现的html5lib,html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:

$ apt-get install Python-html5lib
$ pip install html5lib

下表列出了主要的解析器,以及它们的优缺点:

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, "html.parser") Python的内置标准库执行速度适中文档容错能力强 Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, "lxml") 速度快文档容错能力强 需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, ["lxml", "xml"])``BeautifulSoup(markup, "xml") 速度快唯一支持XML的解析器 需要安装C语言库
html5lib BeautifulSoup(markup, "html5lib") 最好的容错性以浏览器的方式解析文档生成HTML5格式的文档 速度慢不依赖外部扩展

3 获得文档对象

将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象,也可以传入一段字符串或一个文件句柄。

from bs4 import BeautifulSoup
html_doc = "<!DOCTYPE html><html><head>........"  # 一段html文档
# 第一个参数为文档内容,第二个参数是解析器(如果不指定解析器则自动选择)
soup=BeautifulSoup(html_doc,'lxml') # 实例化得到对象,第一个参数是要解析的文档,第二个是解析器。解析具有容错功能
res=soup.prettify() #处理好缩进,结构化显示
print(res)

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象。

所有对象可以归纳为4种TagNavigableStringBeautifulSoupComment

下面的一段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>
"""

# 容错处理:文档的容错能力指的是在html代码不完整的情况下,使用该模块可以识别该错误。使用BeautifulSoup解析上述代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出

3.1 tag

Tag 对象与XML或HTML原生文档中的tag(标签)相同:

soup = BeautifulSoup(html_doc,'lxml')
tag = soup.p # 取出一个p标签
print(tag)

# <p class="title"><b>The Dormouse's story</b></p>

tag中最重要的属性是nameattributes

Name
print(tag.name) # p
tag.name='div' # 将p修改为div
print(tag)
# 如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档,源文档html_doc也会变
Attributes

一个tag可能有很多个属性。tag的属性的操作方法与字典相同:

print(tag['class']) # 中括号取
print(tag.attrs) # 点取属性
print(tag.get('class')) # get方法取

再说一次,tag的属性操作方法与字典一样。意味着tag的属性可以被添加、删除或修改。

tag['class'] = 'abc' # 赋值
del tag['class'] # 删除键
print(tag)

关于HTML5中的多值属性,比如标签中的class可以有多值,bs4会将其返回为列表类型。

css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]

css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]

如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回

id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id'

在赋值时,多值属性会被转换回字符串

rel_soup = BeautifulSoup('<p>Back to the <a rel="index">homepage</a></p>')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
# <p>Back to the <a rel="index contents">homepage</a></p>

3.2 NavigableString

字符串常被包含在tag内,bs4使用NavigableString(可以遍历的字符串)类来包装tag中的字符串(标签包裹的字符串)。

print(tag.string) # string直接获取内部字符串
# The Dormouse's story

tag中包含的字符串不能编辑,但是可以被替换成其它的字符串。

tag.string.replace_with("No longer bold") # 用replace_with方法替换
print(tag)
# <blockquote>No longer bold</blockquote>

NavigableString对象支持遍历文档树和搜索文档树中定义的大部分属性。

3.3 BeautifulSoup

BeautifulSoup对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象,它支持遍历文档树和搜索文档树中描述的大部分的方法。但是它并不是真正的HTML或XML的tag,所以没有name和attribute属性

3.4 Comment

上面三种对象几乎覆盖了html和xml中的所有内容,但是有些内容不包括,比如文档的注释部分,Comment(注释及特殊字符串)用于处理这些内容。实际上它是一个特殊类型的NavigableString对象

markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>

4 遍历文档树

类似于js进行DOM操作一样,一个tag标签可能包含多个字符串或其它的tag,这些都是它的子节点。bs4提供了许多操作和遍历子节点的属性,但如果存在多个相同的标签则只返回第一个。

4.1 子节点

# 根据名字获取tag
print(tag.p) #存在多个相同的标签则只返回第一个
print(tag.a) #存在多个相同的标签则只返回第一个
# 获取tag的名字
print(tag.p.name)
# 获取标签的属性
print(tag.p.attrs)
# 小窍门:在文档树中多次调用这个方法进行嵌套选择(类似于链式操作)
print(tag.body.b) # body下的b标签(存在多个则返回一个,没有则返回None)
# 想要获得所有(不止一个)的tag,使用find_all方法
print(tag.find_all('a'))
# 获取标签包裹的内容
print(tag.p.string) # p下的文本只有一个时,取到,否则为None
print(tag.p.strings) #拿到一个生成器对象, 取到p下所有的文本内容
print(tag.p.text) # 取到p下所有的文本内容,直接用这个获取子孙内容
for line in tag.stripped_strings: #去掉空白
    print(line)
    
# contents属性可以将tag的子节点以列表的方式输出。字符串没有contents属性,因为字符串没有子节点
print(tag)  # <p class="title"><b>The Dormouse's story</b></p>
print(tag.contents) # [<b>The Dormouse's story</b>]
print(tag.contents[0].contents) # ["The Dormouse's story"]
# 通过tag的children属性获得生成器,可以对tag的子节点进行循环
for t in tag.children:
    print(t) # <b>The Dormouse's story</b>

# contents和children都是获取子节点,descendants是一个生成器,遍历可以获取所有子孙节点
for child in tag.descendants:
    print(child) 
    	# 输出结果:
    	# <b>The Dormouse's story</b>
		# The Dormouse's story
        
# 如果tag只有一个NavigableString类型子节点(或者仅有一个子节点),那么这个tag可以使用string:
print(tag.string)
# 如果tag包含了多个子节点,tag就无法确定string方法应该调用哪个子节点的内容,string的输出结果是 None
# 此时可以使用strings 拿到一个生成器对象,for循环取到tag下所有的文本内容
# 输出的字符串中可能包含了很多空格或空行,使用stripped_strings可以去除多余空白内容
for string in soup.strings:
    print(string)
for string in soup.stripped_strings:
    print(string)

4.2 父节点

# parent获取父节点,文档的顶层(比如html)的父节点是BeautifulSoup对象;BeautifulSoup对象的parent是None:
print(tag.parent)
# parents找到所有的祖先节点
print(tag.parents)

4.3 兄弟节点

# next_sibling下一个兄弟
print(tag.next_sibling)
# previous_sibling上一个兄弟
print(tag.previous_sibling)
# 同理可以通过next_siblings和previous_siblings属性可以对当前节点的兄弟节点迭代输出

5 搜索文档树

Beautiful Soup定义了很多搜索方法,这里主要是两个,find和find_all,其它方法的参数和用法类似。

5.1 五个过滤器

在介绍方法之前,首先了解一下过滤器,这些过滤器贯穿整个搜索的API。一共有五种过滤器:

# 
#1、字符串:最简单的过滤器是字符串,在搜索方法中传入一个字符串参数,会查找与字符串完整匹配的内容,比如查找b标签
print(soup.find_all('b'))

#2、通过正则表达式匹配内容
import re
print(soup.find_all(re.compile('^b'))) #找出b开头的标签,结果有body和b标签

#3、列表:如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回,下面代码找到文档中所有<a>标签和<b>标签:
print(soup.find_all(['a','b']))

#4、True:可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
print(soup.find_all(True))
for tag in soup.find_all(True):
    print(tag.name)

#5、方法:如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False
def has_class_but_no_id(tag):
    return tag.has_attr('class') and not tag.has_attr('id')

print(soup.find_all(has_class_but_no_id))

5.2 find_all

def find_all( name , attrs , recursive , text , **kwargs )
# 参数说明:
# 1.name参数可以查找所有名字为name的tag,字符串对象会被自动忽略掉
# 	name参数的值可以使任一类型的过滤器,字符串,正则表达式,列表,方法,True
soup.find_all("title")
# 2.按CSS搜索,但标识CSS类名的关键字`class`在Python中是保留字,所以使用`class_`加下划线代替
soup.find_all("a", class_="sister") # 完全匹配
soup.find_all(class_=re.compile("itl")) # 使用re模糊匹配
# 3.attrs参数,字典,class_也属于attr参数,除了class也可以查找自定义的标签属性
soup.find_all('p',attrs={'class':'story'})
# 4.keyword参数,如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,比如搜索href
soup.find_all(href=re.compile("elsie"))
# 5.text,搜索文档中的字符串内容
soup.find_all(text='Elsie')
# 6.limit 限制搜索的数量,比如设置为2表示最多搜索2个
soup.find_all(string='Elsie',limit=2)
# 7.recursive,调用find_all方法时,会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数recursive=False
soup.html.find_all('a',recursive=False)

5.3 find

def find( name , attrs , recursive , string , **kwargs)
# 只搜索一个结果,比如文档中只有一个<body>标签,用find_all就不合适
# 使用find_all方法并设置limit=1参数不如直接使用find方法,下面两行代码是等价的
soup.find_all('title', limit=1)
soup.find('title')
# 唯一的区别是find_all方法的返回结果是值包含一个元素的列表,而find方法直接返回结果
# find_all方法没有找到目标是返回空列表,find方法找不到目标时,返回None

5.4 get_text

# 如果只想得到tag中包含的文本内容,那么可以调用 get_text()方法。
# 这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup)
soup.get_text()
soup.i.get_text()

# 可以通过参数指定tag的文本内容的分隔符:
soup.get_text("|")

# 还可以去除获得文本内容的前后空白:
soup.get_text("|", strip=True)

# 或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:
[text for text in soup.stripped_strings]

5.5 CSS选择器

可以直接使用select方法,对于熟悉CSS选择器语法的人来说这是个非常方便的方法。

# 在 Tag 或 BeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag
soup.select("body a")
soup.select("head > title")
soup.select("p > a")
soup.select("#link1 ~ .sister")
soup.select("#link1")

关于更多,比如修改文档树,请查阅官方文档

posted @ 2021-11-12 18:45  yyyz  阅读(69)  评论(0编辑  收藏  举报