[网络爬虫基础] 2. Beautiful Soup库
文章目录
一、Beautiful Soup库的理解
Beautiful Soup库是解析、遍历、维护“标签树”的功能库。
<p class=“title”> ... </p>
中的 p 就是标签Tag,标签总是成对出现,标签 p 的属性Attributes为 class,属性有0个或多个。
Beautiful Soup库,也叫beautifulsoup4 或 bs4,有两种引用方式 ① from bs4 import BeautifulSoup
② import bs4
。
1. Beautiful Soup库解析器
from bs4 import BeautifulSoup
soup = BeautifulSoup(‘<html>data</html>’,’html.parser’)
soup2 = BeautifulSoup(‘open("D://demo.html")’,’html.parser’)
解析器 | 使用方法 | 条件 |
---|---|---|
bs4的HTML解析器 | BeautifulSoup(mk,’html.parser’ ) | 安装bs4库 |
lxml的HTML解析器 | BeautifulSoup(mk,’lxml’ ) | pip install lxml |
lxml的XML解析器 | BeautifulSoup(mk,’xml’ ) | pip install lxml |
html5lib的解析器 | BeautifulSoup(mk,’html5lib’ ) | pip install html5lib |
2. BeautifulSoup类的基本元素
基本元素 | 说明 |
---|---|
Tag | 标签,最基本的信息组织单元,分别用<> 和</> 标明开头和结尾 |
Name | 标签的名字,<p>…</p> 的名字是’p’,格式:<tag>.name |
Attributes | 标签的属性,字典形式组织,格式:<tag>.attrs |
NavigableString | 标签内非属性字符串,<>…</> 中字符串,格式:<tag>.string |
Comment | 标签内字符串的注释部分,一种特殊的Comment类型 |
Demo.html
>>> import requests
>>> r = requests.get("http://python123.io/ws/demo.html")
>>> demo = r.text
>>> print(demo)
<html><head><title>This is a python demo page</title></head>
<body>
<p class="title"><b>The demo python introduces several python courses.</b></p>
<p class="course">Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
<a href="http://www.icourse163.org/course/BIT-268001" class="py1" id="link1">Basic Python</a> and <a href="http://www.icourse163.org/course/BIT-1001870001" class="py2" id="link2">Advanced Python</a>.</p>
</body></html>
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
#任何存在于HTML语法中的标签都可以用soup.<tag>访问获得
#当HTML文档中存在多个相同`<tag>对应内容时,soup.<tag>返回第一个
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(demo,'html.parser')
>>> soup.title
<title>This is a python demo page</title>
>>> tag = soup.a
>>> tag
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
#<tag>.name返回标签名,为字符串格式
>>> soup.a.name
'a'
>>> soup.a.parent.name
'p'
>>> soup.a.parent.parent.name
'body'
#一个<tag>可以有多个属性,字典类型
>>> tag = soup.a
>>> tag.attrs
{'href': 'http://www.icourse163.org/course/BIT-268001', 'class': ['py1'], 'id': 'link1'}
>>> tag.attrs['class']
['py1']
>>> type(tag.attrs)
<class 'dict'>
>>> type(tag)
<class 'bs4.element.Tag'>
#格式<tag>.string,NavigableString可以跨越多个层次
>>> soup.a
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
>>> soup.a.string
Basic Python
>>> soup.p
<p class="title"><b>The demo python introduces several python courses.</b></p>
>>> soup.string
The demo python introduces several python courses.
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>
#Tag中的Comment是一种特殊类型
>>> newsoup = BeautifulSoup("<b><!--This is a comment--></b><p>This is not a comment</p>","html.parser")
>>> newsoup.b.string
'This is a comment'
>>> type(newsoup.b.string)
bs4.element.Comment
>>> newsoup.p.string
'This is not a comment'
>>> type(newsoup.p.string)
bs4.element.NavigableString
二、基于bs4库的HTML内容遍历方法
1. HTML基本格式
以Demo.html为例
<>...</>
构成了所属关系,形成了标签的树形结构
2. 标签树的下行遍历
属性 | 说明 |
---|---|
.contents | 子节点的列表,将<tag> 所有儿子节点存入列表 |
.children | 子节点的迭代类型,与.contents类似,用于循环遍历儿子节点 |
.descendants | 子孙节点的迭代类型,包含所有子孙节点,用于循环遍历 |
BeautifulSoup类型是标签树的根节点
>>> soup = BeautifulSoup(demo,"html.parser")
>>> soup.head
<head><title>This is a python demo page</title></head>
>>> soup.head.contents
[<title>This is a python demo page</title>]
>>> soup.body.contents
['\n',
<p class="title"><b>The demo python introduces several python courses.</b></p>,
'\n',
<p class="course">Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a> and <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>.</p>,
'\n']
>>> len(soup.body.contents)
5
>>> soup.body.contents[1]
<p class="title"><b>The demo python introduces several python courses.</b></p>
标签树的下行遍历有两种,分别为遍历儿子节点以及遍历子孙节点。
#遍历儿子节点
for child in soup.body.children:
print(child)
#遍历子孙节点
for child in soup.body.descendants:
print(child)
3. 标签树的上行遍历
属性 | 说明 |
---|---|
.parent | 节点的父亲标签 |
.parents | 节点先辈标签的迭代类型,用于循环遍历先辈节点 |
.parents
会遍历所有先辈节点,包括soup本身,所以要区别判断。
>>> soup=BeautifulSoup(demo,"html.parser")
>>> for parent in soup.a.parents:
if parent is None:
print(parent)
else:
print(parent.name)
p
body
html
[document]
4. 标签树的平行遍历
属性 | 说明 |
---|---|
.next_sibling | 返回按照HTML文本顺序的下一个平行节点标签 |
.previous_sibling | 返回按照HTML文本顺序的上一个平行节点标签 |
.next_siblings | 迭代类型,返回按照HTML文本顺序的后续所有平行节点标签 |
.previous_siblings | 迭代类型,返回按照HTML文本顺序的前续所有平行节点标签 |
平行遍历必须发生在同一个父节点下的各节点间,title 与 p 就不是平行遍历
>>> soup=BeautifulSoup(demo,"html.parser")
>>> soup.a.next_sibling
' and '
>>> soup.a.next_sibling.next_sibling
<a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>
>>> soup.a.previous_sibling
Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:
>>> soup.a.previous_sibling.previous_sibling #节点不存在,返回空
#遍历后续节点
for sibling in soup.a.next_sibling:
print(sibling)
#遍历前续节点
for sibling in soup.a.previous_sibling:
print(sibling)
三、基于bs4库的HTML格式输出
让html页面更加友好的输出
bs4库的prettify()
方法:
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(demo,'html.parser')
>>> print(soup.prettify())
.prettify()
为HTML文本<>及其内容增加更加’\n’
.prettify()
可用于标签,方法:<tag>.prettify()
bs4库的编码:
- bs4库将任何HTML输入都变成utf‐8编码
- Python 3.x默认支持编码是utf‐8,解析无障碍
四、信息标记的三种形式
1. XML,JSON和YAML
三种信息标记的方法,分别是XML,JSON以及YAML
1.1 XML
XML叫扩展标记语言,全称为eXtensible Markup Language,采用了以标签为主来构建信息,表达信息的方式。
其格式为<img src=“china.jpg” size=“10”> ... </img>
,img为名字(Name),src=“china.jpg” size=“10” 为属性(Attribute)。
如果我们标签中没有内容,可以用相关的缩写形式 ,只用一对尖括号:<img src=“china.jpg” size=“10” />
在XML中也可以嵌入注释:<!‐‐ This is a comment, very useful ‐‐>
1.2 JSON
JSON 全称 JavaScript Object Notation,即 JavsScript 中对面向对象信息的一种表达形式。
简单的说JSON是指有类型的键值对构建的信息表达方式 key:value
。对信息name的定义是key,对信息内容的定义是value。
注意在JSON中无论是键还是值都需要增加双引号来表达它是字符串的形式,如果值不是字符串二是数字则直接写数字不需要加双引号。这种形式反映它是一个有数据类型的键值对,这样对于 JavsScript 等编程语言可以直接将JSON格式变为程序的一部分,大大简化程序的编写。
“key” : “value”
# 多值用[,]组织
“key” : [“value1”, “value2”]
# 键值对嵌套用{}
“key” : {
“subkey” : “subvalue”,
“subkey” : “subvalue”
}
1.3 YAML
① 使用无类型键值对 key:value 来组织信息,在键值中不增加双引号。
② 缩进表达所属关系
③ ‐
表达并列关系
④ |
表达整块数据 #
表示注释
key : value
key : #Comment
‐value1
‐value2
key :
subkey : subvalue
1.4 比较
标记方式 | 特点 | 应用情形 |
---|---|---|
XML | 最早的通用信息标记语言,可扩展性好,但繁琐 | Internet上的信息交互与传递 |
JSON | 信息有类型,适合程序处理(js),较XML简洁 | 移动应用云端和节点的信息通信,无注释 |
YAML | 信息无类型,文本信息比例最高,可读性好 | 各类系统的配置文件,有注释易读 |
HTML
<person>
<firstName>Tian</firstName>
<lastName>Song</lastName>
<address>
<streetAddr>中关村南大街5号</streetAddr>
<city>北京市</city>
<zipcode>100081</zipcode>
</address>
<prof>Computer System</prof><prof>Security</prof>
</person>
JSON
{
“firstName” : “Tian” ,
“lastName” : “Song” ,
“address” : {
“streetAddr” : “中关村南大街5号” ,
“city” : “北京市” ,
“zipcode” : “100081”
}
“prof” : [ “Computer System” , “Security” ]
}
YAML
firstName : Tian
lastName : Song
address :
streetAddr : 中关村南大街5号
city : 北京市
zipcode : 100081
prof :
‐Computer System
‐Security
五、信息提取的一般方法
方法一:完整解析信息的标记形式,再提取关键信息。需要标记解析器,例如bs4库的标签树遍历
- 优点:信息解析准确
- 缺点:提取过程繁琐,速度慢
方法二:无视标记形式,直接搜索关键信息 对信息的文本查找函数即可
- 优点:提取过程简洁,速度较快
- 缺点:提取结果准确性与信息内容相关
融合方法:结合形式解析与搜索方法,提取关键信息。需要标记解析器及文本查找函数
基于bs4库的HTML内容查找方法
还是之前的Demo.html,我们现在提取HTML中所有URL链接。首先搜索到所有<a>
标签,然后解析<a>
标签格式,提取href后的链接内容。
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(demo,'html.parser')
>>> for link in soup.find_all('a'):
print(link.get('href'))
http://www.icourse163.org/course/BIT-268001
http://www.icourse163.org/course/BIT-1001870001
<>.find_all(name, attrs, recursive, string, **kwargs)
返回一个列表类型,存储查找的结果
-
name
: 对标签名称的检索字符串import re from bs4 import BeautifulSoup soup = BeautifulSoup(demo,'html.parser') soup.find_all('a') # 返回a标签的内容 soup.find_all(['a','b']) #返回a标签和b标签的内容 soup.find_all(True) #显示当前所有标签内容 #只显示以b开头的标签名字 for tag in soup.find_all(re.compile('^b')): print(tag.name)
-
attrs
: 对标签属性值的检索字符串,可标注属性检索soup.find_all('p','course') #检索属性为course的p标签 soup.find_all(id='link') #返回空列表 soup.find_all(id=re.compile('link')) #返回含有link的id
-
recursive
: 是否对子孙全部检索,默认Truesoup.find_all('a') #返回两个孙结点 soup.find_all('a',recursive=False) #返回空列表
-
string
: <>…</>中字符串区域的检索字符串soup.find_all(string='Basic Python') soup.find_all(string=re.compile('Python')) #返回含有Python的字符串
<tag>(..)
等价于 <tag>.find_all(..)
soup(..)
等价于 soup.find_all(..)
扩展方法 | 说明 |
---|---|
<>.find() | 搜索且只返回一个结果,同.find_all()参数 |
<>.find_parents() | 在先辈节点中搜索,返回列表类型,同.find_all()参数 |
<>.find_parent() | 在先辈节点中返回一个结果,同.find()参数 |
<>.find_next_siblings() | 在后续平行节点中搜索,返回列表类型,同.find_all()参数 |
<>.find_next_sibling() | 在后续平行节点中返回一个结果,同.find()参数 |
<>.find_previous_siblings() | 在前序平行节点中搜索,返回列表类型,同.find_all()参数 |
<>.find_previous_sibling() | 在前序平行节点中返回一个结果,同.find()参数 |
六、实战:中国大学排名定向爬虫
爬取对象:最好大学网
功能描述:
- 输入:大学排名URL链接
- 输出:大学排名信息的屏幕输出(排名,大学名称,总分)
- 技术路线:requests‐bs4
- 定向爬虫:仅对输入URL进行爬取,不扩展爬取
程序的结构设计
- 步骤1:从网络上获取大学排名网页内容
- 步骤2:提取网页内容中信息到合适的数据结构
- 步骤3:利用数据结构展示并输出结果
第一步:写框架
# 用get方法获取HTML信息
def getHTMLText(url):
return" "
# 将获取到的html放在自己定义的ulist中
def fillUnivList(ulist, html):
pass
# 将获取到的ulist信息打印出来,num在这里指要排名多少学校,可以自有填写
def printUnivList(ulist, num):
print("Suc" + str(num))
第二步:完善函数
1. main函数
def main():
# 将大学信息写进列表中,定义列表名uinfo
uinfo = []
# 给出url地址信息
url = "http://www.zuihaodaxue.com/zuihaodaxuepaiming2018.html"
# 用requests库中的get方法,将url信息转换成HTML信息
html = getHTMLText(url)
# 将提取的信息放在uinfo变量中
fillUnivList(uinfo, html)
# 打印大学信息,我们给出的排名信息是前30位
printUnivList(uinfo, 30)
main()
2. getHTMLText函数
def getHTMLText(url):
# 是用try...except 完成结构框架,方便调试
try:
r = requests.get(url, timeout=30)
# 获取状态码
r.raise_for_status()
# 因为有中文,所以需要转换编码
r.encoding = r.apparent_encoding
# 返回给程序
return r.text
except:
print("产生异常")
return
3. fillUnivList函数
使用beautifullsoup提取html信息中关键的数据,并添加到列表中
通过查看网页源码发现,所有的信息被封装在<tbody>
标签里,每一个大学信息又被封装在<tr>
标签里,<tr>
标签包含了当前大学的数据信息,每个大学的<tr>
又被<td>
所包围。
- 先找到
<tbody>
标签,获取所有大学信息 - 再找到
<tbody>
标签里的<tr>
标签,获取每一个大学的信息 - 再找到
<tr>
标签里的<td>
标签,将大学所有属性写到ulist
里 - 用isinstace函数做判断来过滤其他信息
# 将获取到的html放在自己定义的ulist列表中
def fillUnivList(ulist, html):
# 使用bs4库解析网页
soup = BeautifulSoup(html, "html.parser")
# 使用for语句遍历查找tbody下的孩子标签,
for tr in soup.find('tbody').children:
# 检测ts标签,如果tr标签不是bs4定义的tag类型,将过滤掉
if isinstance(tr, bs4.element.Tag):
# tr标签查询完后,需要查询td标签,并将td标签存在tds里,
tds = tr('td')
# ulist 里添加我们需要的信息,分别是大学名称,大学排名,大学评分
ulist.append([tds[0].string, tds[1].string, tds[2].string])
4. printUnivList函数
中文对齐问题:
当中文字符宽度不够时,采用西文字符填充;中西文字符占用宽度不同
解决:采用中文字符的空格填充 chr(12288)
def printUnivList(ulist, num):
# 定义输出模板的变量,并增加宽度设定
# {3}表示打印输出时,需要填充时使用format函数的第三个变量进行填充,也就是使用中文空格进行填充
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
# 定义输出格式,如果看不懂可以去搜下format函数,学习下
print(tplt.format("排名", "学校名称", "评分", chr(12288)))
# 将dr信息用简短的变量u来代替,并用for...in 将每所学校信息打印出来
for i in range(num):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], chr(12288)))
print("Suc" + str(num))
第三步:run
import requests
from bs4 import BeautifulSoup
import bs4
def getHTMLText(url):
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print("产生异常")
return ""
def fillUnivList(ulist, html):
soup = BeautifulSoup(html, "html.parser")
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):
tds = tr('td')
ulist.append([tds[0].string, tds[1].string, tds[2].string])
def printUnivList(ulist, num):
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format("排名", "学校名称", "评分", chr(12288)))
for i in range(num):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], chr(12288)))
print("Suc" + str(num))
def main():
uinfo = []
url = "http://www.zuihaodaxue.com/zuihaodaxuepaiming2019.html"
html = getHTMLText(url)
fillUnivList(uinfo, html)
printUnivList(uinfo, 30)
main()
输出结果
排名 学校名称 评分
1 清华大学 北京
2 北京大学 北京
3 浙江大学 浙江
4 上海交通大学 上海
5 复旦大学 上海
6 中国科学技术大学 安徽
7 华中科技大学 湖北
7 南京大学 江苏
9 中山大学 广东
10 哈尔滨工业大学 黑龙江
11 北京航空航天大学 北京
12 武汉大学 湖北
13 同济大学 上海
14 西安交通大学 陕西
15 四川大学 四川
16 北京理工大学 北京
17 东南大学 江苏
18 南开大学 天津
19 天津大学 天津
20 华南理工大学 广东
21 中南大学 湖南
22 北京师范大学 北京
23 山东大学 山东
23 厦门大学 福建
25 吉林大学 吉林
26 大连理工大学 辽宁
27 电子科技大学 四川
28 湖南大学 湖南
29 苏州大学 江苏
30 西北工业大学 陕西
Suc30
注意到第一行没有对齐,我是将“排名”改为“rank”解决的
rank 学校名称 评分
1 清华大学 北京
2 北京大学 北京
3 浙江大学 浙江
4 上海交通大学 上海
5 复旦大学 上海
6 中国科学技术大学 安徽
7 华中科技大学 湖北
7 南京大学 江苏
9 中山大学 广东
10 哈尔滨工业大学 黑龙江
11 北京航空航天大学 北京
12 武汉大学 湖北
13 同济大学 上海
14 西安交通大学 陕西
15 四川大学 四川
16 北京理工大学 北京
17 东南大学 江苏
18 南开大学 天津
19 天津大学 天津
20 华南理工大学 广东
21 中南大学 湖南
22 北京师范大学 北京
23 山东大学 山东
23 厦门大学 福建
25 吉林大学 吉林
26 大连理工大学 辽宁
27 电子科技大学 四川
28 湖南大学 湖南
29 苏州大学 江苏
30 西北工业大学 陕西
Suc30