课程地址:https://www.icourse163.org/course/BIT-1001870001
笔记内容结合课件整理。

这里对北理爬虫课程第二周内容回顾,本周主要介绍了Beautiful Soup库以及信息标记与提取方法。

1.Beautiful Soup库入门

Beautiful Soup库是一个解析网络数据的python库,下面使用下。

import requests
r = requests.get('https://python123.io/ws/demo.html')
r.text
'<html><head><title>This is a python demo page</title></head>\r\n<body>\r\n<p class="title"><b>The demo python introduces several python courses.</b></p>\r\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:\r\n<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>\r\n</body></html>'
demo = r.text
from bs4 import BeautifulSoup
soup = BeautifulSoup(demo,'html.parser')
print(soup.prettify())
<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>

和r.text的结构相比,使用BeautifulSoup库解析之后的数据更加清晰,下面回顾下BeautifulSoup类的基本元素。

BeautifulSoup类的基本元素

<p class=“title”> … </p>
基本元素 说明
Tag 标签,最基本的信息组织单元,分别用<>和标明开头和结尾
Name 标签的名字,<p>…</p>的名字是’p’,格式:<tag>.name
Attributes 标签的属性,字典形式组织,格式:<tag>.attrs
NavigableString 标签内非属性字符串,<>…</>中字符串,格式:<tag>.string
Comment 标签内字符串的注释部分,一种特殊的Comment类型

下面详细看下这些基本元素

Tag 标签

基本元素 说明
Tag 标签,最基本的信息组织单元,分别用<>和标明开头和结尾
from bs4 import BeautifulSoup
soup = BeautifulSoup(demo,'html.parser')
soup.title
<title>This is a python demo page</title>
soup.a
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>

任何存在于HTML语法中的标签都可以用soup.<tag>访问获得
当HTML文档中存在多个相同<tag>对应内容时,soup.<tag>返回第一个

Tag的name(名字)

基本元素 说明
Name 标签的名字,<p>…</p>的名字是’p’,格式:<tag>.name
soup.a.name
'a'
soup.a.parent.name
'p'
soup.a.parent.parent.name
'body'

每个<tag>都有自己的名字,通过<tag>.name获取,字符串类型。 这里的parent是父节点的意思,后面还会具体介绍。

Tag的attrs(属性)

基本元素 说明
Attributes 标签的属性,字典形式组织,格式:<tag>.attrs
tag = soup.a
tag.attrs
{'class': ['py1'],
 'href': 'http://www.icourse163.org/course/BIT-268001',
 'id': 'link1'}
tag.attrs['class']
['py1']
tag.attrs['href']
'http://www.icourse163.org/course/BIT-268001'

来看下属性以及标签的类型

print(type(tag))
print(type(tag.attrs))
<class 'bs4.element.Tag'>
<class 'dict'>

一个可以有0或多个属性,字典类型

Tag的NavigableString

基本元素 说明
NavigableString 标签内非属性字符串,<>…</>中字符串,格式:<tag>.string
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.p.string
'The demo python introduces several python courses.'
type(soup.p.string)
bs4.element.NavigableString

Tag的Comment

基本元素 说明
Comment 标签内字符串的注释部分,一种特殊的Comment类型

这里需要注意注释类型和NavigableString不同

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内容遍历方法

这里回顾下最开始的时候BeautifulSoup库解析之后的结果

print(soup.prettify())
<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>

#### 标签树的下行遍历
属性| 说明
--|--
.contents| 子节点的列表,将<tag>所有儿子节点存入列表
.children| 子节点的迭代类型,与.contents类似,用于循环遍历儿子节点
.descendants| 子孙节点的迭代类型,包含所有子孙节点,用于循环遍历
BeautifulSoup类型是标签树的根节点   
来看下这些基本属性


```python
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)
<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>
#遍历子孙节点
for child in soup.body.descendants:
    print(child)
<p class="title"><b>The demo python introduces several python courses.</b></p>
<b>The demo python introduces several python courses.</b>
The demo python introduces several python courses.


<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>
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>
Basic Python
 and 
<a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>
Advanced Python
.

标签树的上行遍历

属性 说明
.parent 节点的父亲标签
.parents 节点先辈标签的迭代类型,用于循环遍历先辈节点
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]

因为遍历所有先辈节点,包括soup本身,所以要区别判断

标签树的平行遍历

属性 说明
.next_sibling 返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
.next_siblings 迭代类型,返回按照HTML文本顺序的后续所有平行节点标签
.previous_siblings 迭代类型,返回按照HTML文本顺序的前续所有平行节点标签

注意平行遍历发生在同一个父节点下的各节点间

#遍历后续节点
for sibling in soup.a.next_siblings:
    print(sibling)
 and 
<a class="py2" href="http://www.icourse163.org/course/BIT-1001870001" id="link2">Advanced Python</a>
.
#遍历前续节点
for sibling in soup.a.previous_siblings:
    print(sibling)
Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:

2.信息标记与提取方法

这里老师介绍了三种信息标记的方法,分别是XML,JSON以及YAML,特点如下:

标记方式 特点
XML 最早的通用信息标记语言,可扩展性好,但繁琐
JSON 信息有类型,适合程序处理(js),较XML简洁
YAML 信息无类型,文本信息比例最高,可读性好

再来看下信息提取的一般方法

方法一:完整解析信息的标记形式,再提取关键信息
XML JSON YAML
需要标记解析器,例如:bs4库的标签树遍历
优点:信息解析准确
缺点:提取过程繁琐,速度慢

方法二:无视标记形式,直接搜索关键信息
搜索
对信息的文本查找函数即可
优点:提取过程简洁,速度较快
缺点:提取结果准确性与信息内容相关

融合方法:结合形式解析与搜索方法,提取关键信息
XML JSON YAML 搜索
需要标记解析器及文本查找函数

来看一个实例,这里要找HTML中所有的超链接,首先找到a标签,其次搜索关键信息’href’

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方法
<>.find_all(name, attrs, recursive, string, **kwargs)
返回一个列表类型,存储查找的结果

name : 对标签名称的检索字符串

for tag in soup.find_all(True):
    print(tag.name)
html
head
title
body
p
b
p
a
a
import re
for tag in soup.find_all(re.compile('b')):
    print(tag.name)
body
b

这里re是正则表达式库,re.compile(‘b’)的意思是含有’b’的字符串,<>.find_all(re.compile(‘b’))找到了标签名字中含有’b’的的标签

attrs: 对标签属性值的检索字符串,可标注属性检索

soup.find_all('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='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

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',recursive=False)
[]

string: <>…中字符串区域的检索字符串

soup
<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.find_all(string='Basic Python')
['Basic Python']
soup.find_all(string=re.compile('Python'))
['Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:\r\n',
 'Basic Python',
 'Advanced Python']

由于find_all使用的比较多,BS4库提供了简写方法

  • (..) 等价于 .find_all(..)
  • soup(..) 等价于 soup.find_all(..)

最后再总结下find_all的全部参数:
<>.find_all(name, attrs, recursive, string, **kwargs)

  • name : 对标签名称的检索字符串
  • attrs: 对标签属性值的检索字符串,可标注属性检索
  • recursive: 是否对子孙全部检索,默认True
  • string: <>…中字符串区域的检索字符串

除了find_all方法,还有以下一些方法,使用方法和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()参数

3.“中国大学排名定向爬虫”

这里把程序分为三个部分
步骤1:从网络上获取大学排名网页内容 getHTMLText()
步骤2:提取网页内容中信息到合适的数据结构 fillUnivList()
步骤3:利用数据结构展示并输出结果 printUnivList()

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:
        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):
    print("{:^10}\t{:^6}\t{:^10}".format("排名","学校名称","总分"))
    for i in range(num):
        u=ulist[i]
        print("{:^10}\t{:^6}\t{:^10}".format(u[0],u[1],u[2]))
        
def main():
    uinfo=[]
    url="http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html"
    html=getHTMLText(url)
    fillUnivList(uinfo,html)
    printUnivList(uinfo,20)#20个大学

main()
    排名         学校名称         总分    
    1          清华大学        北京市    
    2          北京大学        北京市    
    3          浙江大学        浙江省    
    4         上海交通大学       上海市    
    5          复旦大学        上海市    
    6          南京大学        江苏省    
    7         中国科学技术大学       安徽省    
    8         哈尔滨工业大学       黑龙江省   
    9         华中科技大学       湖北省    
    10         中山大学        广东省    
    11         东南大学        江苏省    
    12         天津大学        天津市    
    13         同济大学        上海市    
    14        北京航空航天大学       北京市    
    15         四川大学        四川省    
    16         武汉大学        湖北省    
    17        西安交通大学       陕西省    
    18         南开大学        天津市    
    19        大连理工大学       辽宁省    
    20         山东大学        山东省    

这里看到格式,输出不整齐,下面改进下

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:
        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)))
        
def main():
    uinfo=[]
    url="http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html"
    html=getHTMLText(url)
    fillUnivList(uinfo,html)
    printUnivList(uinfo,20)#20个大学
    
main()
    排名           学校名称           总分    
    1            清华大学          北京市    
    2            北京大学          北京市    
    3            浙江大学          浙江省    
    4           上海交通大学         上海市    
    5            复旦大学          上海市    
    6            南京大学          江苏省    
    7          中国科学技术大学        安徽省    
    8          哈尔滨工业大学         黑龙江省   
    9           华中科技大学         湖北省    
    10           中山大学          广东省    
    11           东南大学          江苏省    
    12           天津大学          天津市    
    13           同济大学          上海市    
    14         北京航空航天大学        北京市    
    15           四川大学          四川省    
    16           武汉大学          湖北省    
    17          西安交通大学         陕西省    
    18           南开大学          天津市    
    19          大连理工大学         辽宁省    
    20           山东大学          山东省