网页解析:从网页中提取出所需的信息(例如新的url,数据等等)
网页解析常用的方法有:re(正则表达式),BeautifulSoup,lxml,parsel,requests-html
这一篇只讲BeautifulSoup,其后面的以后面发,敬请期待吧。
官方文档:Beautiful Soup 4.4.0 文档 — Beautiful Soup 4.2.0 中文 文档,Beautiful Soup Documentation — Beautiful Soup 4.9.0 documentation (crummy.com)
引入:
Beautiful Soup 是一个可以从HTML(网页)或XML文件中提取数据的Python库。
Beautiful Soup 3 目前已经停止开发,我们推荐在现在的项目中使用Beautiful Soup 4, 移植到BS4(导入的位置)
例子:
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> """ from bs4 import BeautifulSoup # 使用BeautifulSoup解析这段代码, # 能够得到一个 BeautifulSoup 的对象, # 并能按照HTML标准的缩进格式的结构输出,而且会把缺失的标签补齐: soup = BeautifulSoup(html_doc, 'html.parser') print(soup)
运行结果:

需要安装 (参考):Python安装Bs4及使用方法_python_脚本之家 (jb51.net)
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag
, NavigableString
, BeautifulSoup
, Comment
.
一、结点对象
1、Tag(标签)
粗略的讲一下,不一定准确,html的标签 :在html文件中 <name> <\name> 出现一对类似这样的,name就是标签。可以利用它定位并获取相关的信息。
例如:
from bs4 import BeautifulSoup # 获取BeautifulSoup对象, soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser') # 标准html输出 print(soup.prettify()) # 获取对象的标签 tag = soup.b print('------------\n\n') # 数据类型 print(type(tag))
运行结果:

进一步介绍一下Tag中的一些重要属性。
1、Name
获取标签的名字,在<name> <\name>中name就是标签名
用法:标签对象.name
2、Attributes(属性)
标签其它属性。
属性略作解释,在html中 ,<name> <\name> 在<name>中添加赋值的情况,例如:<name class="1">。class就是属性,1就是属性值。
可以自己添加和修改,也可以删除(del 标签对象.['属性'])。
用法:标签.对象['属性'],可以获取属性值。或者标签对象.attrs,可以获取属性与属性值的字典数据
3、对于同一个标签,可能有多个属性值在Beautiful Soup中多值属性的返回类型是list
例子:
from bs4 import BeautifulSoup # 获取BeautifulSoup对象, soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser') # 获取对象的标签 tag = soup.b print('标签名:',tag.name) print('获取属性值: ',tag['class']) print('获取,属性与属性值:',tag.attrs) # 修改属性值 tag['class'] = '修改' # 添加属性 tag['new'] = '新添加的' # 查看效果 print('看看修改和添加的结果:',tag.attrs)
运行结果:

2、 NavigableString
为了获取标签内容<name> Text <\name> Text
实现方法:标签对象.string
对象转换成Unicode字符串: unicode_string = unicode(tag.string)
tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用replace_with() 方法: tag.string.replace_with("修改的内容")
代码:
from bs4 import BeautifulSoup # 获取BeautifulSoup对象, soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser') # 获取对象的标签 tag = soup.b print('内容:',soup.string)
运行结果:

3、BeautifulSoup
BeautifulSoup
对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag
对象.
因为 BeautifulSoup
对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.
但有时查看它的 .name
属性是很方便的,所以 BeautifulSoup
对象包含了一个值为 “[document]” 的特殊属性 .name
方法:soup = BeautifulSoup()
4、Comment
对象是一个特殊类型的 NavigableString
对象:其输出的内容不包括注释符号。
html注释的写法:<!-- 这里是注释内容--> ;注释符号<!---->
text = ''' <b><!--Hey, buddy. Want to buy a used parser?--></b> ''' # 获取BeautifulSoup对象 soup = BeautifulSoup(text,'html.parser') # 获取内容 string = soup.b.string print(string) # 查看一下它的数据类型 print(type(string))
运行结果:

二、遍历文档树
在HTML中Tag可以嵌套,所以就会出现各个结点之间关系的描述属性
1、子节点
tag的子节点: .contents 或 .children
tag的 contents属性可以将tag的子节点以列表的方式输出: soup.contents[0].name,第一个子节点的名字
BeautifulSoup
对象本身一定会包含子节点,也就是说<html>标签也是 BeautifulSoup
对象的子节点:
字符串没有 .contents
属性,因为字符串没有子节点:
通过tag的 .children生成器,可以对tag的子节点进行循环:
for child in tag.children:
print(child)
2、孙子节点:子节点的子节点
.descendants属性可以对所有tag的子孙节点进行递归循环
for child in tag.descendants:
print(child)
2.1、内容问题
.string
如果一个tag仅有一个子节点,那么这个tag也可以使用 .string方法,输出结果与当前唯一子节点的 .string 结果相同:
如果tag包含了多个子节点,tag就无法确定 .string方法应该调用哪个子节点的内容, .string 的输出结果是 None:
所以引出:
.strings 和 stripped_strings
如果tag中包含多个字符串 ,可以使用 .strings来循环获取
for string in soup.strings:
print(repr(string))
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容:
for string in soup.stripped_strings:
print(repr(string))
全部是空格的行会被忽略掉,段首和段末的空白会被删除
3、父节点:
.parent
通过 .parent属性来获取某个元素的父节点.
BeautifulSoup 对象的 .parent是None:
.parents
通过元素的 .parents属性可以递归得到元素的所有父辈节点。递归得到父辈元素的所有节点,返回一个生成器
4、兄弟节点(同一级别的结点的,必须是同一个父节点)
.previous_sibling和.next_sibling(Tag前一个和后一个结点)
通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出:
for sibling in soup.a.next_siblings:
print(repr(sibling))
for sibling in soup.find(id="link3").previous_siblings:
print(repr(sibling))
5、回退和前进
.next_element 和 .previous_element
.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的.
.previous_element 属性刚好与 .next_element 相反,它指向当前被解析的对象的前一个解析对象:
.next_elements 和 .previous_elements
通过.next_element 和 .previous_element 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:
三、方法
1、方法:find_all()
作用: 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.
语法:soup.find_all(name, attrs, recursive, text, limit, **kwargs)
参数解释:
1、name参数:
name:参数可以查找所有名字为 name 的tag。
参数的值可以是:字符串,正则表达式,列表,方法或是 True
如果是Ture的话:可以匹配任何值, 找到所有的tag,但是不会返回字符串节点
2、attrs 参数:
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:
soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'html.parser')
soup.find_all(attrs={"data-foo": "value"})
也可以通过属性与属性值构建字典
例如:attrs={"id": True} ,class(不用class_)同理
3、recursive参数:
调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
默认recursive=True
4、text 参数:
通过text参数可以搜索文档中的字符串内容,与name参数的可选值一样,参数可以是: 字符串,正则表达式,列表,True。
标签内容,<a> text <\a> 这里的text就是要匹配的地方,而且返回也是text。
一般都用组合的方式来定位 :soup.find_all("a", text="Elsie")
5、limit 参数:
可以使用 limit 参数限制返回结果的数量。
例如:soup.find_all("a", limit=2) 如果搜索找到a标签的数量2,则会停止搜索。
6、kwargs参数:
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,
如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性
例如:soup.find_all(id="link1") (如果没有属性值,好像会是全部)
如果传入href 参数,Beautiful Soup会搜索每个tag的”href”属性:
soup.find_all(href=re.compile("elsie"))
如果属性为class不能直接用class(会报错),必须用class_ Beautiful Soup会搜索每个tag的”class”属性:
例如:soup.find_all(class_="story")
id=True的话就是只要有id这个属性都可以,href,class_同理。
使用多个指定名字的参数可以同时过滤tag的多个属性:
例如:soup.find_all(href=True , id=True)
后面的相关函数的参数一样则效果一致
2、像调用 find_all()
一样调用tag
BeautifulSoup
对象和 tag
对象可以被当作一个方法来使用,等价于对象的 find_all()
下面两个等价
soup.find_all("a")
soup("a")
下面两个等价
soup.title.find_all(string=True)
soup.title(string=True)
3、find()
find(name , attrs,recursive , text , **kwargs)
find_all() 方法将返回文档中符合条件的所有tag,数据是列表;而find(),只返回一个结果,不是列表
结果只有一个所以没有limit这个参数。
4、find_parents() 和 find_parent()
pycharm把鼠标放到函数上面,ctrl+Q可以查看函数信息。
find_parent(name , attrs , **kwargs)
find_parents(name, attrs , limit, **kwargs)
记住:find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等.
find_parents()
和 find_parent 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同
5、find_next_siblings() 合 find_next_sibling()
find_next_sibling(name , attrs , text, **kwargs)
find_next_siblings(name , attrs , text , limit , **kwargs)
对当tag的所有后面解析 的兄弟tag节点进行迭代, find_next_siblings 方法返回所有符合条件的后面的兄弟节点,
find_next_sibling 只返回符合条件的后面的第一个tag节点。
6、find_previous_siblings() 和 find_previous_sibling()
find_previous_siblings( name , attrs , text , limit ,**kwargs)
find_previous_sibling(name , attrs , text , **kwargs)
对当前tag的前面解析的兄弟tag节点进行迭代, find_previous_siblings 方法返回所有符合条件的前面的兄弟节点,
find_previous_sibling 方法返回第一个符合条件的前面的兄弟节点:
7、find_all_next() 和 find_next()
find_all_next(name , attrs , text , limit , **kwargs)
find_next(name , attrs , text , **kwargs)
对当前tag的之后的 tag和字符串进行迭代, find_all_next() 方法返回所有符合条件的节点, find_next() 方法返回第一个符合条件的节点:
8、find_all_previous() 和 find_previous()
find_all_previous(name , attrs , text , limit ,**kwargs: Any)
find_previous(name , attrs , text , **kwargs: Any)
对当前节点前面 的tag和字符串进行迭代, find_all_previous() 方法返回所有符合条件的节点, find_previous() 方法返回第一个符合条件的节点.
四、CSS选择器
Beautiful Soup支持大部分的CSS选择器
在 Tag 或 BeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
例如:soup.select("title")
1、通过tag标签逐层查找(空格隔开):
例如:soup.select("body a") ,soup.select("html head title")
2、找到某个Tag标签下的直接子标签(>隔开):
例如:soup.select("head > title")
soup.select("p > a:nth-of-type(3)") 对应的第三个
soup.select("p > #link1") #对应id属性值
3、找到兄弟节点标签( ~ . 隔开):
例如:soup.select("#link1 ~ .sister")
4、通过CSS的类名查找(.属性值):
soup.select(".sister")
soup.select("[class~=sister]")
5、通过tag的id查找:
soup.select("#link1")
soup.select("a#link2")
6、同时用多种CSS选择器查询元素:
soup.select("#link1,#link2")
7、通过是否存在某个属性来查找:
soup.select('a[href]')
8、通过属性的值来查找:
soup.select('a[href="http://example.com/elsie"]')
soup.select('a[href^="http://example.com/"]') 属性值以它为开头
soup.select('a[href$="tillie"]') 属性值以它为结尾
soup.select('a[href*=".com/el"]') 属性值包含
9、通过语言设置来查找:
multilingual_markup="""
<p lang="en">Hello</p>
<p lang="en-us">Howdy, y'all</p>
<p lang="en-gb">Pip-pip, old fruit</p>
<p lang="fr">Bonjour mes amis</p>
"""
multilingual_soup = BeautifulSoup(multilingual_markup)
multilingual_soup.select('p[lang|=en]')
10、返回查找到的元素的第一个
例如:soup.select_one(".sister")
四、修改文档树
1、修改tag的名字和属性
重命名一个tag,改变属性的值,添加或删除属性:
例如:
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','html.parser') tag = soup.b # 修改名字 tag.name = "blockquote" # 修改类名名 tag['class'] = 'verybold' # 添加属性 tag['id'] = 1 # 删除属性 del tag['class'] del tag['id']
2、修改 .string
给tag的 .string 属性赋值,就相当于用当前的内容替代了原来的内容:
例如:tag.string = "New Text."
注意: 如果当前的tag包含了其它tag,那么给它的 .string
属性赋值会覆盖掉原有的所有内容包括子tag
3、append()
Tag.append()方法想tag中添加内容
soup = BeautifulSoup("<a>Foo</a>", 'html.parser') soup.a.append("Bar") print(soup)
运行结果:

4、NavigableString() 和 .new_tag()
from bs4 import BeautifulSoup, NavigableString
soup = BeautifulSoup("<b></b>", 'html.parser') tag = soup.b tag.append("Hello")
new_string = NavigableString(" there") tag.append(new_string) print(tag)
运行结果:

如果想要创建一段注释,或 NavigableString
的任何子类, 只要调用 NavigableString 的构造方法:
在上面的基础上改
new_comment = soup.new_string("Nice to see you.", Comment) tag.append(new_comment) print(tag)
运行结果:

创建一个tag最好的方法是调用工厂方法 BeautifulSoup.new_tag()
:
例如:new_tag = soup.new_tag("a", href="http://www.example.com")
第一个参数作为tag的name,是必填,其它参数选填
5、insert()
Tag.insert() 方法与 Tag.append() 方法类似,区别是不会把新元素添加到父节点 .contents 属性的最后,而是把元素插入到指定的位置.
6、insert_before() 和 insert_after()
在当前tag或文本节点 前 / 后插入内容:
7、clear()
Tag.clear() 方法移除当前tag的内容:
8、extract()
PageElement.extract() 方法将当前tag移除文档树,并作为方法结果返回:
9、decompose()
Tag.decompose() 方法将当前节点移除文档树并完全销毁:
10、replace_with()
PageElement.replace_with() 方法移除文档树中的某段内容,并用新tag或文本节点替代它:
11、wrap()
PageElement.wrap() 方法可以对指定的tag元素进行包装 ,并返回包装后的结果:
12、unwrap()
Tag.unwrap() 方法与 wrap() 方法相反.将移除tag内的所有tag标签,该方法常被用来进行标记的解包:
五、输出
1、格式化输出
prettify() 方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行
BeautifulSoup 对象和它的tag节点都可以调用 prettify() 方法:
例如:print(soup.prettify())
2、压缩输出
如果只想得到结果字符串,不重视格式,那么可以对一个 BeautifulSoup 对象或 Tag 对象使用Python的 unicode() 或 str() 方法:
3、输出格式
Beautiful Soup输出是会将HTML中的特殊字符转换成Unicode,比如“&lquot;”:
如果将文档转换成字符串,Unicode编码会被编码成UTF-8.这样就无法正确显示HTML特殊字符了:
4、get_text()
只得到tag中包含的文本内容, 这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回:
可以通过参数指定tag的文本内容的分隔符:
例如:soup.get_text("|")
还可以去除获得文本内容的前后空白:
例如:soup.get_text("|", strip=True)
六、文档解析器
- 要解析的文档是什么类型: 目前支持, “html”, “xml”, 和 “html5”
- 指定使用哪种解析器: 目前支持, “lxml”, “html5lib”, 和 “html.parser”