3.2 BeautifulSoup
和lxml
一样,BeautifulSoup
也是一个HTML/XML的解析器,跟XPath
的功能是一样的。
区别在哪里呢?lxml
只会局部遍历,如果你想提取a
标签的内容,那么我们需要写出相关的xpath
语法,此时lxml
只会对全部的a
标签进行遍历,找出满足条件的a
标签。
而BeautifulSoup
会载入整个文档,将文档内容组织成类似于DOM树的结构,然后我们可以根据它提供的API
来作出相应的操作,此时BeautifulSoup
会遍历整个树,找出满足条件的标签。因此它的开销比较大,速度慢。
使用BeautifulSoup
前需要先安装:pip install bs4
。
那现在来看一下几大解析工具对比:
解析工具 | 解析速度 | 使用难度 |
---|---|---|
BeautifulSoup | 最慢 | 最简单 |
lxml | 快 | 简单 |
正则 | 最快 | 最难 |
简单使用:
# 第一步:从bs4中导入BeautifulSoup
from bs4 import BeautifulSoup
import requests
r = requests.get("http://python123.io/ws/demo.html")
demo = r.text
# 将内容组织成DOM树
soup = BeautifulSoup(demo, 'html.parser') # 第二个参数是指定一个解析器
print(soup.text) # 输出纯文本
print(soup.prettify()) # 用比较美化得方式打印出来
soup = beatifulsoup(text, pattern)
-
参数text 为要提取的内容
-
参数pattern为解析器(可选)
html.parser
(默认)html5lib
(容错性最强)lxml
(平时使用最多)xml
这里值得一提的是,bs4
库将任何读入的html
文件或字符串都转换为utf-8
编码。
提升案例:
from bs4 import BeautifulSoup
import requests
rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")
# 获取30个div标签
trs = soup.find_all('div', limit=30) # 返回的是列表
for ts in trs:
print(ts)
print("-"*30)
# 获取第二个div标签
tr = soup.find_all('div',limit = 3)[1]
print(tr)
# 获取class等于head_wrapper的div标签
# trs = soup.find_all('div',class_='head_wrapper') # 注意这里是class_ ,因为class在python中是关键字
# 上下这两种方式等价
trs = soup.find_all('div',attrs={'class': 'head_wrapper'})
for tr in trs:
print(tr)
# 将所有class=lb,并且name=tj_login的a标签
trs = soup.find_all('a', attrs={'class': 'lb', 'name': 'tj_login'})
for tr in trs:
print(tr)
# 所有a标签的href属性
alist = soup.find_all('a')
for a in alist:
# 获取标签属性的方法
href = a['href']
print(href)
# 获取第一个tr下面的全部文本内容
# infos = tr.strings # 包含空白符
infos = list(tr.stripped_strings) # 去掉空白符
print(infos[0])
# 获取所有的职位信息(假设网址中有这个信息)
# trs = soup.find_all('tr')[1:] # 获取第一个以后的tr标签
# message = [] # 存放职位信息
# for tr in trs:
# mess = {} # 存放单条职位信息
# # 找到tr下面所有的td标签
# tds = tr.find_all('td')
#
# mess['title'] = tds[0].string # 获取第一个td的内容
# mess['category'] = tds[1].string # 获取第二个td的内容
# message.append(mess)
# print(message)
soup.find(tag) 查找 第一个 匹配的tag标签等价于soup.tag,对应的有soup.find_all(tag[,limit]) 查找 所有 tag标签,limit表示提取多少条。
string 获取某个标签下的直接内容。无法获取多行内容。
strings (返回的是生成器,可以转换成list) 获取所有子孙非标签的字符串,有多行时获取不到。
stripped_strings 同上,但会去掉空白文本。
get_text() 获取所有子孙非标签的文本,返回的是字符串。
CSS选择器:select方式
此种方式通过CSS语法
来进行提取特定标签。
import requests
from bs4 import BeautifulSoup
rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")
# 查找p标签 select功能跟find_all一样,就是不能同时筛选类名和id
print(soup.select('p'))
# 查找div下面的所有满足class=‘cp-feedback’的标签
print(soup.select('div .cp-feedback'))
# 查找div下面的所有满足id=‘cp’的标签
print(soup.select('div #cp'))
# 获取class=‘lg’的a标签 注意,中间没有空格
a = soup.select('a.lb')
# a = soup.select('a[class="lb"]') 等价于上面的写法
print(a)
# 查找name="tj_trnews"的a标签
print(soup.select('a[name="tj_trnews"]'))
# 查找div下的子p标签
print(soup.select('div > p'))
小结:
- 中间有空格:表示在该标签下找满足筛选条件的标签
- 中间没空格:表示查找满足筛选条件的该标签。
select
功能跟find_all一样,返回的都是列表,但select
无法同时筛选类名和id>
表示在该标签的子元素中查找
四个常用对象:
Beautiful Soup
将复杂的HTML文档转换成一个复杂的树形结构,每个节点都是python对象,这些对象可以分为4种:
- Tag :就是一个个的HTML标签(拥有name、class等属性)。
- NavigableString:继承自python本身的str类型。代表标签中的内容字符串。
- BeautifulSoup:继承自Tag,本质上就是Tag。所以上面
soup对象
使用的find_all
等方法实际上是Tag类中的方法,与此同时也说明了 为什么标签可以像soup一样直接使用find_all等方法 。它表示的是一个文档的全部内容。 - Comment:特殊的
NavigableString
,表示注释的部分。
案例:
本次案例是用requests库跟bs4结合使用,然后爬取CSDN推荐文章列表的一些信息。
其他要爬取的内容的方式跟这两个方式差不多,这里就不一一截图出来了。。。
下面来看看代码吧。
import requests
from bs4 import BeautifulSoup
url = 'https://blog.csdn.net/nav/python'
# 伪装
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/82.0.4077.0 Safari/537.36'
}
res = requests.get(url, headers=headers)
text = res.text
# 建立文档树
soup = BeautifulSoup(text, 'lxml')
# 提取所有文章的li
lis = soup.find_all('li', attrs={'class': 'clearfix', 'data-type': 'blog'})
# 遍历lis,单独对每个li进行提取
for li in lis:
title_div = li.find('div', attrs={'class': 'title'})
# 获取标题内容,因为stripped_strings返回的是迭代器所以要先转成list
# "".join()的作用是把列表内容变成字符串
title = "".join(list(title_div.find('a').stripped_strings))
# 获取作者名称所在的dd标签
name_dd = li.find('dd', attrs={'class': 'name'})
# 获取作者名称
name = "".join(list(name_dd.find('a').stripped_strings))
# 获取阅读量所在的dd标签
readNum_dd = li.find('dd', attrs={'class': 'read_num'})
# 获取阅读量
readNum = "".join(list(readNum_dd.stripped_strings))
# 获取评论数所在的dd标签
commonNum_dd = li.find('dd', attrs={'class': 'common_num'})
# 获取评论数
# 因为有些文章是没有评论的,所以会缺少此标签
if commonNum_dd != None:
commonNum = "".join(list(commonNum_dd.stripped_strings))
else:
commonNum = 0
print('文章标题:{}\t作者名称:{}\t浏览量:{}\t评论数:{}'
.format(title, name, readNum, commonNum))
当然,你还可以自己获取更多更好玩的内容,冲冲冲。
至于如何爬取每篇文章的内容详情,后续教程中将会讲到,一步一步来。
那这一节就到这里啦~~~