网络爬虫之提取
Beautiful Soup库入门
Beautiful Soup库的安装
- 同样在shell或者cmd中使用pip install beautifulsoup4就可以完成它的安装,正如其名,这个库的作用是把各种各样的格式的文本像煲汤一样处理好。
- beautiful soup库的测试
import requests
from bs4 import BeautifulSoup
r = requests.request('GET', "https://python123.io/ws/demo.html")
demo = r.text
soup = BeautifulSoup(demo, 'html.parser')
print(soup.prettify())
beautiful soup库的基本元素
beautiful soup库是可以解析HTML和XML文件的库。
beautiful soup库是解析、遍历、维护“标签树”的功能库。
标签的基本结构
<p class = "title">...<p>这叫p标签,属性是由键和值构成的,一个标签可以由多种属性。
beautiful soup库的使用
可以直接使用:from bs4 import BeautifulSoup
也可以直接引用:import bs4
BeautifulSoup()类
其实对于对应的HTML文档或者XML文档,他们都和一个标签树等价,同时标签树也可以转化为一个BeautifulSoup类。这三者是等价的。
bs4库中的四种解析器
解析器 | 使用方法 | 条件 |
bs4的HTML解析器 | BeautifulSoup(mk, "html.parser") | 安装bs4库 |
lxml的HTML解析器 | BeautifulSoup(mk, "lxml") | 安装lxml库 |
lxml的XML解析器 | BeautifulSoup(mk, 'xml') | 安装lxml库 |
html5lib的解析器 | BeautifulSoup(mk, 'html5lib') | 安装html5lib库 |
所有的解析器都可以解析HTML和XML文档。
BeautifulSoup类中的基本元素
基本元素 | 说明 |
Tag | 标签,最基本的信息组织单元,分别用<>和<>来表示开头和结尾 |
Name | 标签的名字,可以用<tag>.name来获取标签的名字 |
attributes | 标签的属性,字典形式组织,格式:<tag>.attrs |
navigablestrings | 标签内部非属性的内容,格式:<tag>.string |
comment | 标签内字符串的注释形式 |
获得tag标签的相关方法
直接使用soup.tag就可以获取标签
soup.a
>>><a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
soup.title
>>><title>This is a python demo page</title>
注意:当HTML页面中有多个同类型标签时(如demo中的<a>链接标签),那么soup.a只会返回第一个此类型标签。
获取标签名字的方法
使用soup.tag.name就可以获得它的名字
使用soup.tag.name.parent.name就可以获得它父标签(即它的上一层标签)的名字
>>> soup.a.name
'a'
>>> soup.a.parent.name
'p'
>>> soup.a.parent.parent.name
'body'
获取标签的属性信息
使用soup.tag.attrs来获得。
>>> tag = soup.a
>>> tag.attrs
{'href': 'http://www.icourse163.org/course/BIT-268001', 'class': ['py1'], 'id': 'link1'}
>>> tag.attrs['class'] #获得属性的类型
['py1']
>>> tag.attrs['href'] #获得属性的超链接的内容
'http://www.icourse163.org/course/BIT-268001'
>>> type(tag.attrs) #tag.attrs是一个字典类型
<class 'dict'>
注意:当标签中没有属性类型时,这时候会返回一个空的字典类型。
标签的navigablestring类型
使用soup.tag.string来获取它的信息。
>>> soup.a.string
'Basic Python'
>>> soup.p.string
'The demo python introduces several python courses.'
>>>
注意:<p>标签内还有一个<b>标签,而没有把它打印出来,说明navigablestring是可以跨越多个标签输出的
标签的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)
<class 'bs4.element.Comment'>
>>> newsoup.p.string
'This is not a comment'
>>> type(newsoup.p.string)
<class 'bs4.element.NavigableString'>
注意:判断一段string是否是注释,需要通过type()来判断,输出时注释会自动去除<和!,所以只能通过type()而非字符串输出结果来判断。
基于bs4的HTML文本遍历方法
正如之前提到过的那样,bs4库是通过标签树的结构来对HTML和XML文本进行分析的,从而完成相应的功能。
而对于标签树,则有三种遍历方式:下行遍历,上行遍历,平行遍历。
- 下行遍历:是从父节点开始往子节点进行遍历。
主要有三个属性:
属性 | 功能 |
soup.tag.contens | 是指该节点的全部子节点的列表类型 |
soup.tag.children | 迭代类型,用于循环遍历全体子节点 |
soup.tag.descendants | 迭代类型,用于循环遍历全部子孙节点 |
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
for i in soup.body.children:
print(i)
for i in soup.body.descendants:
print(i)
注意:对于contents列表来说,除了HTML的文本,换行符也是它列表元素的一员。
- 上行遍历
上行遍历主要有两个属性
属性 | 功能 |
soup.tag.parent | 节点的父亲标签 |
soup.tag.parents | 节点所有的父节点的标签 |
soup.body.parent
>>> <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 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>
</body></html>
soup.html.parent
>>> <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 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>
</body></html>
soup.parent
>>>
注意:html标签的父标签就是它本身,而soup标签的父标签则不存在,因而在用.parents进行遍历时,必须排除这一情况!
3. 标签树的平行遍历
共有四种属性:
属性 | 功能 |
soup.tag.next_sibling | 指的是tag节点的下一个平行节点 |
soup.tag.previous_sibling | 指的是tag节点的上一个平行节点 |
soup.tag.next_siblings | 迭代类型,用于遍历tag之后的平行节点 |
soup.tag.previous_siblings | 迭代类型,用于遍历tag之前的平行节点 |
>>> 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.previoud_sibling
>>> 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:\r\n'
>>> soup.a.previous_sibling.previous_sibling
#输出为空,无前序节点
>>> soup.a.parent
<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>
注意:其一,下一个平行节点完全有可能是navigablestring也即字符串类型;其二,平行节点的概念指针对同一父亲节点的各节点;其三,若没有前序节点,则输出为空。
基于bs4库的HTML格式输出
- 如何能让HTML文本的内容更好被阅读?
使用soup.prettify()函数来使得HTML文本更易读。 - 编码的问题:
python中是一直使用utf-8编码的。
信息的组织与提取
信息标记的三种形式
- 信息的标记:和信息一样具有重要价值的相关结构。
标记的信息可以形成信息的组织结构,增加信息的维度;
标记后的信息可以用于存储、展示;
标记的结构与信息一样非常重要;
标记后的信息更利于程序的理解和使用。 - HTML(Hyper Text Makeup Language)超文本标记语言
HTML是WWW的信息组织形式。可以将图像、声音、视频等超文本信息嵌入到文本之中。
可以通过预定义的标签来组织不同形式的内容。 - 信息标记的三种形式:国际公认的有XML,JSON和YAML。
- XML(eXtensive Makeup Language)扩展标记语言
以标签为主来构建信息表达信息的方式。
<img src="china.jpg",size="10">...<img>
<img src="china.jpg",size="10" /> <!--空元素的缩写形式-->
<!--This is a comment--> <!--注释的形式-->
同样存在名字、属性和内容。
XML以标签的形式来组织信息,当标签中有内容时,采用一对标签来组织,没有内容时采用一对尖括号就可以,注释则采用尖括号内加 ”!--“来表示
5. JSON(JavaScript Object Notation)
JSON是指用有类型的键值对来组织信息。
"name" : "北京理工大学"
"name" : ["北京理工大学", "延安自然科学院"] //如果对应多个值采用“[,]”
"name" : {"newname" : "北京理工大学", "oldname" : "延安自然科学院"}
// 如果键值对进行嵌套采用“{,}”
如果值是数字则不要用 “ ”。
对于JavaScript等编程语言可以直接把JSON格式作为程序的一部分,使得编写程序得以简化。
6. YAML(YAML Ain't Makeup Language)
采用的是无类型的键值对来处理信息。不加双引号以及其他各种的类型标记。
通过缩进的方式来表示所属关系。
用“-”来表达并列关系。
用“|”表示整块数据。
用“#”表示注释。
name : 北京理工大学
name :
oldname : 延安理工学院
newname : 北京理工大学
name : #两个名字均对应这个键值
-延安理工学院
-北京理工大学
text : | #竖线可以表示多行数据
1234
1234
123
三种信息标记形式的比较
XML: 用标签表达信息的方式。
JSON:用有类型的键值对标记信息的方式。
YAML:用无类型的键值对标记信息的方式。
XML:最早的通用信息标记语言,可扩展性强,但比较繁琐
<person>
<firstname>Tian</firstname>
<lastname>Song</lastname>
<address>
<streetaddress>中关村南大街5号</streetaddress>
<city>北京市</city>
<zipcode>100081</zipcode>
</assress>
<prof>Computer Science</prof><prof>Security</prof>
</person>
JSON:本身就是基于Javascript实现的对象,信息有类型,适合程序处理。
{
"firstname" : "Tian"
"lastname" : "Song"
"address" : {
"streetaddress" : "中关村南大街5号",
"city" : "北京市",
"zipcode" : "100081"
}
"prof" : ["Computer Science", "Security"]
}
YAML:信息无类型,文本信息比例最高,可读性强。
firstname : Tian
lastname : Song
address :
streetaddress : 中关村南大街5号
city : 北京市
zipcode : 100081
prof :
-Computer Science
-Security
XML:主要用于Internet上的信息交互和传递以及表达。
JSON:主要应用在移动应用云端和节点的信息通信中,一般用在程序对接口处理的地方。JSON数据在传输后可以作为程序的一部分直接运行,比较大的缺陷即无法增加注释。
YAML:主要应用于配置文件中。
信息提取的一般方法
- 完整地解析信息的标记形式,然后提取关键信息:
需要使用bs4库中的标记解释器,优点是信息解析准确,缺点是提取过程繁琐、速度较慢。 - 无视任何信息的标记形式,直接搜索关键信息:
对信息文本使用查找函数,优点是提取过程简洁迅速,缺点是提取信息的相关性和准确性不一定足够。 - 融合方法:结合形式与搜索方法,提取关键信息:
需要标记解释器和文本查找函数。
4.实例:提取所有HTML页面中的
搜索所有的<a>标签
解析<a>标签内容,提取href后的内容。
import bs4
soup = beautifutlsoup.BeautifulSoup(demo, "html.parser")
for link in soup.find_all('a'):
print(linl.get("href"))
>>>http://www.icourse163.org/course/BIT-268001
>>>http://www.icourse163.org/course/BIT-100187000
基于bs4库的HTML内容查找方法
bs4库提供了<>.find_all(name, attributes, recursive, string, **kwags)方法:
- name:对标签名称的检索字符串
>>> soup.find_all('a')
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
>>> soup.find_all(['a','b']) #用列表类型来查找多个标签
[<b>The demo python introduces several python courses.</b>, <a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
>>> for tag in soup.find_all(True): #输入True则返回所有标签的列表
print(tag.name)
html
head
title
body
p
b
p
a
a
>>> import re #通过正则库来返回全部和b相关的结果
>>> for tag in soup.find_all(re.compile('b')):
print(tag.name)
body
b
- attrs: 对标签属性值的检索字符串。
>>> soup.find_all('p','course') #寻找所有p标签中含有course属性值的标签
[<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>]
>>> soup.find_all(id='link1') #查找id属性值为“link1”的
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>]
>>> soup.find_all(id='link') #查找必须准确查找,否则查不到
[]
>>> soup.find_all(id=re.compile('link')) #如果要模糊查找,应该使用正则
[<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>, <a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>]
- recursive:默认为True,是否针对子孙所有节点进行搜索,如果只想搜索子节点,那么就置位为False。
>>> soup.find('a') #返回了所有子孙节点名字为'a'的节点
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
>>> soup.find_all('a',recursive = False) #返回所有子节点名字为'a'的节点
[]
- string:对<>...</>区域中的字符串进行检索。
>>> soup.find_all(string = "Basic Python") #只能进行精准搜索
['Basic Python']
>>> soup.find_all(string = re.compile("python"))
#使用正则搜索全部相关字符串
['This is a python demo page', 'The demo python introduces several python courses.']
>>>
因为在bs4库中,find_all函数非常常用,所以有简写类型:
<tag>.find_all(..) 等价于 <tag>()
扩展方法
方法 | 功能 |
<>.find() | 搜索并且只返回一个值,是字符串类型,参数同<>.find_all() |
<>.find_parent() | 搜索先辈节点并只返回一个值,是字符串类型,参数同<>.find_all() |
<>.find_parents() | 搜索先辈节点并返回一组值,是列表类型,参数同<>.find_all() |
<>.find_next_sibling() | 搜索后续平行节点并只返回一个值,是字符串类型,参数同<>.find_all() |
<>.find_next_siblings() | 搜索后续平行节点并返回一组值,是列表类型,参数同<>.find_all() |
<>.find_previous_sibling() | 搜索前序平行节点并只返回一个值,是字符串类型,参数同<>.find_all() |
<>.find_previous_siblings() | 搜索前序平行节点并返回一组值,是列表类型,参数同<>.find_all() |
对于这些扩展方法,他们同.find_all()方法功能都是一样的,只是检索的范围有所不同
实例二:中国大学排行榜
#CrawUnivRankingB.py
import requests
import re
def getHtmlText(url):
try:
r=requests.get(url,timeout=30)
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
print("getHtmlText err")
def parsePage(ulist,html):
try:
ranking=re.findall(r'\"ranking\"\:\"[\d]*\"',html)
univNameCn=re.findall(r'\"univNameCn\"\:\".*?\"',html)
scores=re.findall(r'\"score\"\:...',html)
for i in range(15):
rank=eval(ranking[i].split(":")[1])
name=eval(univNameCn[i].split(":")[1])
score=eval(scores[i].split(":")[1])
ulist.append([rank,name,score])
except:
print("parsePage err")
def printGoodsList(ulist):
tplt = "{0:^10}{1:{3}^10}{2:^10}"
print(tplt.format("序号", "学校名称", "分数", chr(12288)))
# 中文空格填充,能保证输出对齐
for i in range(len(ulist)):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], chr(12288)))
def main():
url = "https://www.shanghairanking.cn/api/pub/v1/bcur?bcur_type=11&year=2020"
ulist = []
html = getHtmlText(url)
parsePage(ulist,html)
printGoodsList(ulist)
main()
这里贴出来的是助教更新后的源码,这可以得到结果,但是没有使用bs4库,而使用很多还没学到的re正则的内容,看来正则的内容将发挥更大的作用啊。