Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml ,在使用Beautiful Soup进行代码解析的时候,往往还会使用lxml配合使用。
安装使用:
导入模块并创建bs4对象:
from bs4 import BeautifulSoup
import xlwt
soup = BeautifulSoup(html_doc,'lxml')
在将一段HTML代码传入 BeautifulSoup的时候,可以直接传入一段代码,一个可以传入一个文件句柄,比如:
soup = BeautifulSoup(open('index.html'),'lxml')
tag有很多的方法和属性,最重要的两个属性是name和atrributes。
name:
每个tag都有自己的名字,通过.name属性来获取,并且可以通过.name属性来修改tag的名字。示例如下:
soup = BeautifulSoup('<b class="boldest">hello,word!!!</b>')
tag = soup.b
print(tag.name) # u'b'
tag.name = 'testTag'
print(tag) #<testTag class="boldest">hello,word!!!</testTag>
Attributes:
一个tag可能有很多个属性. tag <b class="boldest">
有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同,示例如下:
print(tag['class']) # boldest
print(tag.attrs) # 以字典形式输出:{u'class': u'boldest'}
tag['id'] = 1 # 对于没有的属性,则增加,如果已经有,则修改属性值
del tag['id'] # 删除已有的属性
多值属性:
HTML 4定义了一系列可以包含多个值的属性.在HTML5中移除了一些,却增加更多.最常见的多值的属性是 class (一个tag可以有多个CSS的class). 还有一些属性 rel , rev , accept-charset , headers , accesskey . 在Beautiful Soup中多值属性的返回类型是list,示例如下:
soup = BeautifulSoup('<p class="body strikeout"></p>')
print(soup.p['calss']) # ["body", "strikeout"]
soup1 = BeautifulSoup('<p class="body"></p>')
print(soup1.p['calss']) #["body"]
如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回.示例如下:
soup = BeautifulSoup('<p id="is my id"></p>')
print(soup.p['id']) # 'is my id'
如果转换的文档是xml格式,那么就不存在多值属性,属性值会被当做字符串进行处理。
字符串常被包含在tag内.Beautiful Soup用 NavigableString 类来包装tag中的字符串:
soup = BeautifulSoup('<b class="boldest">hello,word!!!</b>')
tag = soup.b
print(tag.string) # u'hello word!!!'
print(type(tag.string)) # <class 'bs4.element.NavigableString'>
一个 NavigableString 字符串与Python中的Unicode字符串相同,并且还支持包含在 遍历文档树 和 搜索文档树中的一些特性. 通过 unicode() 方法可以直接将 NavigableString 对象转换成Unicode字符串:
soup = BeautifulSoup('<b class="boldest">hello,word!!!</b>')
tag = soup.b
print(type(unicode(tag.string))) #<type 'unicode'>
示例代码:获取个人导航页进行解析。
import requests
from bs4 import BeautifulSoup
import xlwt
import os
url = 'http://xx.xx.xx.128'
headers = {
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36'
}
req = requests.get(url, headers=headers)
# 创建一个Beautifulsoup对象
soup = BeautifulSoup(req.content.decode('utf-8'), 'lxml')
一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.
操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取 标签,只要用 soup.head :
soup.head
# <head>
# <title>个性化安全导航</title>
# <meta charset="utf-8"/>
# <link href="./css/back_ground.css" rel="stylesheet" type="text/css"/>
# </head>
soup.title
# <title>个性化安全导航</title>
想要获取文档树下的某一个子标签,可以只当tag的方式,比如获取body下的h2标签;
soup.body.h2
# <h2 class="title">论坛社区</h2>
在子节点中有多个tag的情况下,通过点取属性的方式只能获得当前名字的第一个tag,如果想要获取多个tag的数据就需要使用 Searching the tree 中描述的方法,比如: find_all()
soup.body.find_all('h2')
#[<h2 class="title">论坛社区</h2>, <h2 class="title">杂七杂八</h2>]
tag的 .contents 属性可以将tag的子节点以列表的方式输出:
soup.head.contents
# [<title>个性化安全导航</title>, <meta charset="utf-8"/>, <link href="./css/back_ground.css" rel="stylesheet" type="text/css"/>]
通过tag的 .children 生成器,可以对tag的子节点进行循环:
for children in soup.head.children:
print(children)
# <title>个性化安全导航</title>
# <meta charset="utf-8"/>
# <link href="./css/back_ground.css" rel="stylesheet" type="text/css"/>
.contents 和 .children 属性仅包含tag的直接子节点.例如,标签只有三个个直接子节点
但是
for children in soup.head.descendants:
print(children)
# <title>个性化安全导航</title>
# 个性化安全导航
# <meta charset="utf-8"/>
# <link href="./css/back_ground.css" rel="stylesheet" type="text/css"/>
如果tag中包含多个字符串,可以使用 .strings 来循环获取,但是 .strings会将换行符等最为字符串进行输出,而使用 .stripped_strings 可以去除多余空白内容(全部是空格的行会被忽略掉,段首和段末的空白会被删除):
for children in soup.body.div.find_all('div')[1].stripped_strings:
print(children)
# FreBuff 安全论坛
# 先知安全社区
# 安全客
# CSDN博客
# 百度超级链
通过 .parent 属性来获取某个元素的父节点,比如获取第一个h2标签的父节点:
soup_h2 = soup.h2
print(soup_h2)
#<h2 class="title">论坛社区</h2>
print(soup_h2.parent)
#<div class="title_card"><h2 class="title">论坛社区</h2></div>
通过元素的 .parents 属性可以递归得到元素的所有父辈节点,继续以h2标签为例,通过parents获取所有长辈节点:
soup_h2 = soup.h2
print(soup_h2)
for parent in soup_h2.parents:
print(parent.name)
'''
<h2 class="title">论坛社区</h2>
div
div
body
html
[document]
'''
(1) .previous_sibling
获取前面一个兄弟节点。
soup_span = soup.find(class_ = 'card-title')
print(soup_span)
#<span class="card-title">FreBuff 安全论坛</span>
print(soup_span.previous_sibling)
#<span class="card-icon"><img src="./img/frebuff.ico"/></span>
(2) .previous_siblings
获取前面的所有兄弟节点。
soup_span = soup.find(class_ = 'card-title')
print(soup_span)
#<span class="card-title">FreBuff 安全论坛</span>
for span in soup_span.previous_siblings:
print(span)
'''只有一个兄弟节点,所以只能查找到一个。
<span class="card-icon"><img src="./img/frebuff.ico"/></span>
'''
(1)next_sibling
获取后面一个兄弟节点。
soup_span = soup.find(class_ = 'card-icon')
print(soup_span)
#<span class="card-icon"><img src="./img/frebuff.ico"/></span>
print(soup_span.next_sibling)
#<span class="card-title">FreBuff 安全论坛</span>
(2)next_siblings
获取后面所有的兄弟节点。
soup_span = soup.find(class_ = 'card-icon')
print(soup_span)
#<span class="card-icon"><img src="./img/frebuff.ico"/></span>
for span in soup_span.next_siblings:
print(span)
'''只有一个兄弟节点,所以只输出一个
<span class="card-title">FreBuff 安全论坛</span>
'''
搜索文档树,主要的方法有两个,一个是find,一个是find_all(),find搜索文档中的第一个,而find_all()则是在全局范围进行搜索并返回一个列表。
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,比如查找文档中第一个的标签:
tag = soup.find('img')
print(tag)
#<img src="./img/frebuff.ico"/>
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出文档中第一个以t开头的所有标签:
tag = soup.find(re.compile("^t"))
print(tag)
#<title>个性化安全导航</title>
示例:获取所有h2标签。
tag = soup.find_all('h2')
print(tag)
'''
[<h2 class="title">论坛社区</h2>, <h2 class="title">区 块 链</h2>, <h2 class="title">在线办公</h2>, <h2 class="title">知识
学习</h2>, <h2 class="title">编码解码</h2>, <h2 class="title">杂七杂八</h2>]
'''
示例:获取所有以h开头的标签。
for tag in soup.find_all(re.compile("^h")):
print(tag.name,end=",")
#html,head,h2,h2,h2,h2,h2,h2,
按照文档中的出现顺序进行输出:
for tag in soup.find_all(['body','h2']):
print(tag.name,end=",")
#body,h2,h2,h2,h2,h2,h2,
示例:通过class进行搜索(class 是多值属性,需要加“_")
for tag in soup.find_all(class_ = 'card-title'):
print(tag.text,end=',')
#FreBuff 安全论坛,先知安全社区,安全客 ,CSDN博客,百度超级链 , 巴 比 特 ,金 色 财 经,火 币 网,欧易交易所, 非 小 号 ,Ethplorer交易浏览器......
在指定关键字的同时,也可以使用re模块进行正则匹配,示例如下:
for tag in soup.find_all(class_ = re.compile('^card')):
print(tag,end=',')
#<span class="card-icon"><img src="./img/frebuff.ico"/></span>,<span class="card-title">FreBuff 安全论坛</span>,<span class="card-icon"><img src="./img/xz.ico"/></span>,<span class="card-title">先知安全社区</span>,.......
上面指定了一个关键词,也可以指定多个关键词进行搜索。
for tag in soup.find_all(class_ ="link_tooltip",title="https://www.okx.com/"):
print(tag,end=',')
'''
<a class="link-tooltip" href="https://www.okx.com/" target="_blank" title="https://www.okx.com/">
<span class="card-icon"><img src="./img/ouyi.ico"/></span>
<span class="card-title">欧易交易所</span>
</a>,
'''
find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果.
tag = soup.find_all('span',limit= 4)
print(tag)
#[<span class="card-icon"><img src="./img/frebuff.ico"/></span>, <span class="card-title">FreBuff 安全论坛</span>, <span class="card-icon"><img src="./img/xz.ico"/></span>, <span class="card-title">先知安全社区</span>]