网络爬虫之提取

Beautiful Soup库入门

Beautiful Soup库的安装

  1. 同样在shell或者cmd中使用pip install beautifulsoup4就可以完成它的安装,正如其名,这个库的作用是把各种各样的格式的文本像煲汤一样处理好。
  2. 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文本进行分析的,从而完成相应的功能。
而对于标签树,则有三种遍历方式:下行遍历,上行遍历,平行遍历。

  1. 下行遍历:是从父节点开始往子节点进行遍历。
    主要有三个属性:

属性

功能

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的文本,换行符也是它列表元素的一员。

  1. 上行遍历
    上行遍历主要有两个属性

属性

功能

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格式输出

  1. 如何能让HTML文本的内容更好被阅读?
    使用soup.prettify()函数来使得HTML文本更易读。
  2. 编码的问题:
    python中是一直使用utf-8编码的。

信息的组织与提取

信息标记的三种形式

  1. 信息的标记:和信息一样具有重要价值的相关结构。
    标记的信息可以形成信息的组织结构,增加信息的维度;
    标记后的信息可以用于存储、展示;
    标记的结构与信息一样非常重要;
    标记后的信息更利于程序的理解和使用。
  2. HTML(Hyper Text Makeup Language)超文本标记语言
    HTML是WWW的信息组织形式。可以将图像、声音、视频等超文本信息嵌入到文本之中。
    可以通过预定义的标签来组织不同形式的内容。
  3. 信息标记的三种形式:国际公认的有XML,JSON和YAML。
  4. 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:主要应用于配置文件中。

信息提取的一般方法

  1. 完整地解析信息的标记形式,然后提取关键信息:
    需要使用bs4库中的标记解释器,优点是信息解析准确,缺点是提取过程繁琐、速度较慢。
  2. 无视任何信息的标记形式,直接搜索关键信息:
    对信息文本使用查找函数,优点是提取过程简洁迅速,缺点是提取信息的相关性和准确性不一定足够。
  3. 融合方法:结合形式与搜索方法,提取关键信息:
    需要标记解释器和文本查找函数。

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)方法:

  1. 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
  1. 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>]
  1. 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'的节点
[]
  1. 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正则的内容,看来正则的内容将发挥更大的作用啊。