BeautifulSoup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。换句话说,它是我们解析网页的利器
BeautifulSoup3 目前已经停止开发,今天学习的是BeautifulSoup4
1.简单入手
我们以豆瓣网为例,编辑下面这段代码
from bs4 import BeautifulSoup
import requests
if __name__ == "__main__":
req = requests.get("https://www.douban.com/") #获取豆瓣网址
html = req.text #获得网页源代码
soup = BeautifulSoup(html)
print(soup.prettify())
首先通过一个requests.get()
来获得目标网址的信息,再通过req.text
获得网页源代码,之后就可以利用BeautifulSoup来对源码进行解析。这里进行操作后,你可能会有这样的疑问,print(html)
和print(soup.prettify())
的输出结果不是一样吗?确实看输出结果是这样的,好像没什么区别,其实两者是不一样的,前者只是单纯的输出网页源码,后者是将网页源码解析后模块化输出,仔细看一下输出结果你会发现些许差别。没发现也没有关系,下面我们来对解析后的网页进行操作。
接着上面的代码块操作
print(soup.title) #输出<title>标签
print(soup.title.name) #输出title的name
print(soup.title.string) #输出title的内容
print(soup.title.parent.name) #输出title父节点的name
print(soup.p) #输出<p>标签
print(soup.p['class']) #输出<p>标签的类
print(soup.a) #输出<a>标签
print(soup.find_all('a')) #找到所有的<a>标签
print(soup.find(id="anony-time")) #找到所有的id为anony-time标签
print(soup.find_all('p',class_="app-title")) #找到所有class为app-title的<a>标签
自己操作实现一下,你就能对BeautifulSoup的功能有着更深入的了解。其实不限于此,BeautifulSoup能做的还有更多,比如它可以提取网页中的链接
#提取网页所有<a>标签里的链接
for link in soup.find_all('a'):
print(link.get('href'))
对这段代码稍加限定条件就可以提取到指定<a>
标签的链接,比如soup.find_all('a',class_='lnk-book')
就可以查找所有类别为lnk-book
的<a>
标签
我们也可以通过下面的操作来获得网页所有的文字内容
print(soup.get_text())
2.BeautifulSoup——解析器
在前面的代码块中有一行代码是这样的soup = BeautifulSoup(html)
这行代码其实不是很规范,一般会在后面补充一个解析器,变成这样soup = BeautifulSoup(html,'lxml')
。BeautifulSoup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,它一共有这么几种
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, "html.parser") |
Python的内置标准库 执行速度适 文档容错能力强 |
Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
速度快 唯一支持XML的解析器 |
需要安装C语言库 |
lxml XML 解析器 |
BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml")
|
速度快 唯一支持XML的解析器 |
需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") |
最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 |
速度慢 不依赖外部扩展 |
推荐使用lxml
作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml
或html5lib
, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定。
3.对象
BeautifulSoup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag
, NavigableString
, BeautifulSoup
,Comment
.
3.1Tag
在前面的操作中,实际上我们已经见识了解析后的tag,解析后的网页都是由一个个的tag组成的。
soup = BeautifulSoup(html,'lxml')
print(soup.title) #输出了<title>这个tag
每一个tag都有自己的name,我们可以查看
print(soup.title.name) #输出title的name
每一个tag都有自己的属性,比如<li> <a target="_blank" class="lnk-book" href="[https://book.douban.com](https://book.douban.com/)">豆瓣读书</a></li>
,我们可以查看
soup = BeautifulSoup('<li> <a target="_blank" class="lnk-book" href="
[https://book.douban.com](https://book.douban.com/)">豆瓣读书</a></li>','lxml')
print(soup.a['target']) #查看target属性
print(soup.a['class']) #查看class属性
print(soup.a['href']) #查看href属性
这里也就是我们上面获取<a>
标签里面链接的原理,也可以直接查看它的所有属性
print(soup.a.attrs)
另外,tag里面的属性也是可以操作的,可以删除和修改,和字典操作是一样的
soup.a['target'] = 'white' #修改
del soup.a['class'] #删除
print(soup.a['target'])
print(bool(soup.a['class'])) #这一步会报错,因为已经被删掉了
熟悉html的童鞋还会知道另外一个问题,就是有些html会有多值属性,比如<a class="lnk-book ink-book">豆瓣读书</a>
soup = BeautifulSoup('<a class="lnk-book ink-book">豆瓣读书</a>','lxml')
print(soup.a['class'])
#['lnk-book', 'ink-book']
会发现,BeautifulSoup会自动的区分开多值属性,并以list的形式返回,但是当你将tag转换为string字符串时,它就会自动将他们的多值属性合并到一起。
3.2NavigableString
NavigableString是一个类,用来包装tag中的字符串,一个 NavigableString 字符串与Python中的Unicode字符串相同。tag中包含的字符串不能编辑,但是可以被替换成其它的字符串。
soup = BeautifulSoup('<a class="lnk-book ink-book">豆瓣读书</a>','lxml')
soup.a.string.replace_with('walt white')
print(soup.a)
#<a class="lnk-book ink-book">walt white</a>
3.3BeautifulSoup
BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象。上一段代码中的soup
就是一个BeautifulSoup对象。
3.4Comment
comment其实顾名思义就是注释的意思,它是NavigableString一个特殊子类,用来表示html中的注释
soup = BeautifulSoup('<b><!--Hey, how you\'re doing ?--></b>','lxml')
comment = soup.b.string
print(comment)
注意这段代码,输出结果是Hey, how you're doing ?
,而不是html的代码中的``,已经自动过滤了注释标签了
4.遍历文档树
这里用一段官网给出的文档来做例子
from bs4 import BeautifulSoup
if __name__ == "__main__":
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<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>
"""
soup = BeautifulSoup(html_doc,'lxml')
4.1子节点
我们可以通过这样的方法来获取节点中的子节点
print(soup.body.b)
这样通过点取的方法只能获取第一个元素,如果需要获取全部的标签,则需要通过另外一种方法
print(soup.find_all('a'))
它可以通过列表的形式输出所有的标签
4.2.contents
利用.contents
可以来将子节点以列表的形式输出
soup = BeautifulSoup(html_doc,'lxml')
head_tag = soup.head
title_tag = head_tag.contents[0]
print(title_tag)
print(title_tag.contents)
通过结果可以看到.contents
输出了了当前节点的子节点,并用列表的形式输出,直到到最后一个字符串节点,无法再往下遍历
4.3.children
利用.children
操作也可以遍历子节点
soup = BeautifulSoup(html_doc,'lxml')
title_tag = soup.head.contents[0]
for child in title_tag.children:
print(child)
4.4.descendants
利用.descendants
操作也可以遍历子孙节点
for child in title_tag.descendants:
print(child)
4.5.string
如果tag有且仅有一个子节点,利用.string
可以得到子节点的内容,但是tag如果有多个子节点,那么会输出none
print(title_tag.string)
print(soup.string)
#The Dormouse's story
#None
4.6.strings 和 stripped_strings
如果tag中包含多个字符串 ,可以使用 .strings
来循环获取
for string in soup.strings:
print(repr(string))
#"The Dormouse's story"
#'\n'
#"The Dormouse's story"
#'\n'
#'Once upon a time there were three little sisters; and their names were\n '
#'Elsie'
#',\n '
#'Lacie'
#' and\n '
#'Tillie'
#';\n and they lived at the bottom of a well.'
#'\n'
#'...'
#'\n'
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings
可以去除多余空白内容:
for string in soup..stripped_strings:
print(repr(string))
#"The Dormouse's story"
#"The Dormouse's story"
#'Once upon a time there were three little sisters; and their names were'
#'Elsie'
#','
#'Lacie'
#'and'
#'Tillie'
#';\n and they lived at the bottom of a well.'
#'...'
今天的学习就到这里了,BeautifulSoup篇学习还没有结束,后面还会继续
欢迎关注公众号:老白和他的爬虫
网友评论