1. 程式人生 > 實用技巧 >爬蟲入門二 beautifulsoup

爬蟲入門二 beautifulsoup


title: 爬蟲入門二 beautifulsoup
date: 2020-03-12 14:43:00
categories: python
tags: crawler

使用beautifulsoup解析資料

1 beautifulsoup簡介

BeautifulSoup 是一個可以從HTML或XML檔案中提取資料的Python庫.它能夠通過轉換器實現文件導航、查詢、修改。
pip install beautifulsoup4
http://beautifulsoup.readthedocs.io/zh_CN/latest/

2 前端知識

http://www.w3school.com.cn/

HTTP:HyperText Markup Language 超文字標記語言
CSS:Cascading Style Sheets 層疊樣式表
JAVASCRIPT:一種指令碼語言,其原始碼在發往客戶端執行之前不需經過編譯,而是將文字格式的字元程式碼傳送給瀏覽器由瀏覽器解釋執行
XML:Extensible Markup Language可擴充套件標記語言

XML是被設計用來描述資料的,HTML是被設計用來顯示資料的。與HTML相比,XML支援動態更新,標準性更強

3 beautifulsoup解析器

比如BeautifulSoup(demo, 'html.parser') 中的html.parser

4 html結構

<></>構成所屬關係。樹形結構。下行,上行,平行遍歷。

5 使用beautifulsoup解析HTML

注意B和S大寫,Python對大小寫敏感

from bs4 import BeautifulSoup
import requests

def html():
    # 得到未解析的HTML網頁內容
    r = requests.get("https://whu.edu.cn/coremail/common/index_cm40.jsp")
    print(r.text)
    # 得到解析的HTML網頁內容
    demo = r.text
    soup = BeautifulSoup(demo, 'html.parser')
    print(soup.prettify())

6 BeautifulSoup 類的基本元素與操作

Tag,標籤<></>開始結束
name,標籤名,

的name是p。.name
attributes,標籤屬性(字典形式組織)。.attr
navigablestring,標籤內非屬性字串..string
comment,標籤內字串的註釋部分

下面是例子:
可以看到,web中有很多a標籤,,而這裡只返回了第一個

from bs4 import BeautifulSoup
import requests

def html():
    # 得到未解析的HTML網頁內容
    r = requests.get("https://whu.edu.cn/coremail/common/index_cm40.jsp")
    print(r.text)
    # 得到解析的HTML網頁內容
    demo = r.text
    soup = BeautifulSoup(demo, 'html.parser')
    #print(soup.prettify())
    print(soup.title)
    print(soup.a)

#<title>武漢大學郵件系統</title>
#<a class="MTLinks">設為首頁</a>

HTML中a錨文字A超連結標籤格式: 被連結內容

6.1 BeautifulSoup標籤操作

#檢視標籤a的屬性
tag=soup.a
tag.attrs

#檢視標籤a的屬性中class的值
tag.attrs['class']

#檢視標籤a的屬性 的型別
type(tag.attrs)

#檢視標籤a的非屬性字串
soup.a.string

#檢視標籤a的非屬性字串屬性 的型別
type(tag.a.string)

#返回值為bs4.element.NavigableString  (可以遍歷的字串)
from bs4 import BeautifulSoup
import requests

def html():
    # 得到未解析的HTML網頁內容
    r = requests.get("https://whu.edu.cn/coremail/common/index_cm40.jsp")
    print(r.text)
    # 得到解析的HTML網頁內容
    demo = r.text
    soup = BeautifulSoup(demo, 'html.parser')
    #print(soup.prettify())
    print(soup.title)
    print(soup.a)
    # 檢視標籤a的屬性
    tag = soup.a
    print(tag.attrs)
    # 檢視標籤a的屬性中class的值
    tag.attrs['class']
    # 檢視標籤a的屬性 的型別
    print(type(tag.attrs))
    # 檢視標籤a的非屬性字串
    print(tag.string)
    # 檢視標籤a的非屬性字串屬性 的型別
    print(type(tag.string))
    # 返回值為bs4.element.NavigableString  (可以遍歷的字串)

7 html遍歷

上行,下行,平行。

7.1 標籤樹下行遍歷 .content .children .descendants

7.1.1 .content

.contents 子節點的列表,將所有兒子節點存入列表

soup.body.contents

#獲得孩子節點的個數
len(soup.body.contents)
#分別輸出各個子節點
soup.body.contents[0]
soup.body.contents[2]
#標號 由0開始

7.1.2 .children/.descendants

.contents 和 .children 屬性僅包含tag的直接子節點,.descendants 屬性可以對所有tag的子孫節點進行遞迴迴圈
需要遍歷獲取其中的內容。

for child in soup.body.children:
    print(child)
for child in soup.body.descendants:
    print(child)

def htmlergodic():
    # 得到未解析的HTML網頁內容
    r = requests.get("https://whu.edu.cn/coremail/common/index_cm40.jsp")
    #print(r.text)
    # 得到解析的HTML網頁內容
    demo = r.text
    soup = BeautifulSoup(demo, 'html.parser')
    # 子節點的列表,將<tag>所有兒子節點存入列表
    #print(soup.body.contents)
    # 獲得孩子節點的個數
    print(len(soup.body.contents))
    # 分別輸出各個子節點
    print(soup.body.contents[0])
    print(soup.body.contents[2])
    # 標號 由0開始

    for child in soup.body.children:
        print(child)
    for child in soup.body.descendants:
        print(child)

7.2 標籤樹上行遍歷 .parent/.parents

# a的父節點
soup.a.parent.name

# 遍歷
for parent in soup.a.parents:   #注意 s
	print(parent.name)

7.3 標籤樹平行遍歷 next_sibling/Previous_sibling

#獲得a節點的上一個節點和下一個節點
soup.a.next_sibling
soup.a.previous_sibling

for sibling in soup.a.next_siblings: #注意s
    print(sibling)

8 bs4的庫的prettify()方法

BeautifulSoup 是bs4庫的類,prettify()是方法。
.prettify()為HTML文字<>及其內容增加換行符
可以用於整個HTML文字,也可以用於單個標籤
方法:
.prettify()

bs4庫將任何HTML輸入都變成utf‐8編碼 Python 3.x預設支援編碼是utf‐8,解析無障礙

print(soup.body.prettify())

9 BeautifulSoup資訊檢索 注意find標籤名要加''表示字串

9.1 .find_all() 搜尋並返回全部結果

<>.find_all(name,attrs,recursive,string,**kwargs)

def jiansuo():
    r = requests.get("http://www.baidu.com/")
    r.encoding = r.apparent_encoding
    demo = r.text
    soup = BeautifulSoup(demo, 'html.parser')
    print(soup.find_all('a'))

9.2 引數

name: 對標籤名稱的檢索字串
attrs: 對標籤屬性值的檢索字串,可標註屬性檢索
recursive: 是否對子孫全部檢索,預設True
string: <>…</>中字串區域的檢索字串

#搜尋 class=“mnav” 的全部a標籤
print(soup.find_all('a','mnav'))
# 搜尋字串為新聞的全部標籤
print(soup.find_all(string='新聞'))

如果要部分匹配,則需要匯入正則表示式庫

9.3 擴充套件方法

<>.find()
<>.find_parents()
<>.find_parent()
<>.find_next_sibling()
<>.find_next_siblings()
<>.find_previous_sibling()
<>.find_previous_siblings()

10 例項

10.1 爬取武漢大學官方網站,含'櫻'的新聞連結標題

匯入requests庫、BeautifulSoup類、re庫
使用for迴圈,查詢條件string=re.compile('櫻'))
搜尋型別為新聞連結,因此型別為‘a’
輸出滿足條件的tag的字串

import re
import requests
from bs4 import BeautifulSoup
def sakura():
    r = requests.get("http://www.whu.edu.cn/")
    r.encoding = r.apparent_encoding
    demo = r.text
    soup = BeautifulSoup(demo,'html.parser')
    for tag in soup.find_all('a', string=re.compile('櫻')):
        print(tag.string)

10.2 爬取 百度網際網路熱門人物排行

訪問百度搜索風雲榜 
人物--網際網路人物
http://top.baidu.com/buzz?b=257&c=9&fr=topcategory_c9

右鍵點選 “馬雲”--檢查,檢視對應的HTML程式碼

<a class="list-title" target="_blank" href="http://www.baidu.com/baidu?cl=3&amp;tn=SE_baiduhomet8_jmjb7mjw&amp;rsv_dl=fyb_top&amp;fr=top1000&amp;wd=%C2%ED%D4%C6" href_top="./detail?b=257&amp;c=9&amp;w=%C2%ED%D4%C6">馬雲</a>

分析可知人物連線標籤名為a,class為list-title
使用soup.find_all(‘a’, ‘list-title’ ) 輸出class為list-title
的a標籤的字串,即可書序得到排行榜的人物名單。
同時在前面加上他們索引序號index(tag.string)+1
即可得到今日網際網路人物排行榜

def fyrw():
    r=requests.get("http://top.baidu.com/buzz?b=257&fr=topboards")
    r.encoding=r.apparent_encoding
    demo=r.text
    soup=BeautifulSoup(demo, 'html.parser')
    ulist = []
    for tag in soup.find_all('a', 'list-title' ):
        ulist.append(tag.string)
        print(ulist.index(tag.string)+1,ulist[ulist.index(tag.string)])

10.3 爬取 中國大學排行榜2016

從網路上獲取大學排名網頁內容
getHTMLText()
提取網頁內容中資訊到合適的資料結構
fillUnivList()
利用資料結構展示並輸出結果
printUnivList()
http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html
檢查網頁程式碼看到結構是

<tbody>
    <tr>
        <td> 排位
        <td> 大學名...
    <tr>
    ...    

這樣就遍歷標籤的每個標籤,提取前三個標籤的string儲存即可

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: #提取tbody的每個tr標籤
        if isinstance(tr,bs4.element.Tag): #判斷tr是否是tag型別
            tds = tr('td')                 #tds[0] <td>1</td>.簡寫,等價於下一行程式碼
            #tds = tr.find_all('td')
            # ,然後用.string取string
            ulist.append([tds[0].string, tds[1].string, tds[3].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 univs

上面的問題是並沒有對齊。
原因是當中文字元寬度不夠時,採用西文字元填充;中西文字元佔用寬度不同。
解決是採用中文字元的空格填充chr(12288)

def printUnivList(ulist, num):
    tplt = "{0:^4}\t{1:{3}^12}\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)))

tplt為定義的輸出格式模板變數,^代表居中,4/12/10代表輸出寬度(當輸出資料超過該數字時,以實際輸出為準),
{3}代表列印輸出時,我們使用format中的第3個變數(由0起始),也就是 chr(12288)
chr(12288)代表全形Unicode空格(即中文空格)

# :是引導符號
#  : <填充><對齊><寬度>,<精度><型別>  這裡的 ','是千位分隔符
# 拿上面的程式碼舉例
#tply:0,1,2對應輸出的第0,1,2個數據。^表示居中,然後第0個數據寬4.中間{3}表示填充用format第3個變數也就是chr(12288)

10.4 最好大學2017.遇坑

同樣的程式碼爬
http://www.zuihaodaxue.cn/zuihaodaxuepaiming2017.html
報錯
TypeError: unsupported format string passed to NoneType.format

#2016
<tr class="alt"><td>1</td>
				<td><div align="left">清華大學</div></td>

#2017
 <tr class="alt"><td>1<td><div align="left">清華大學</div></

注意這裡有坑,chrome檢查,在console看到是沒區別的(2017也是1)

檢查頁面原始碼發現和2016比不同之處在於子節點“1”所對應的地方並非是一個完整的子節點,“1”並沒有被一對完整的標籤所包圍,所以tds[0]實際上是被第一個標籤所包圍的所有內容,而這相當於把後續所有內容全給裝進去了。
由於字元1仍是第一的標籤下的第一個子節點,通過bs4庫的contents方法來獲得這個排名,由於是第一個子節點,tds[0].contents[0]就排名。
重新修改的程式碼如下

def fillUnivList(ulist, html):
    soup = BeautifulSoup(html, "html.parser")
    for tr in soup.find('tbody').children: #提取tbody的每個tr標籤
        if isinstance(tr,bs4.element.Tag): #判斷tr是否是tag型別
            tds = tr('td')                 #tds[0] <td>1</td>.簡寫,等價於下一行程式碼
            #tds = tr.find_all('td')
            # ,然後用.string取string
            ulist.append([tds[0].contents[0].string, tds[1].string, tds[3].string])

10.5 2016世界大學排名,遇坑

http://www.zuihaodaxue.cn/ARWU2016.html

先用了2016中國排名的方法,然後報錯,然後看原始碼,沒看出來不同。
就把爬的內容列印一下
發現大學名字列印的是none
然後對比2016中國和2016大學.

<td class="align-left">
					<a href="World-University-Rankings/Harvard-University.html" target="_blank">哈佛大學</a>

					</td>

<td><div align="left">清華大學</div></td>

不明白就去查,
https://blog.csdn.net/github_36669230/article/details/66973617
用 .string 屬性來提取標籤裡的內容時,該標籤應該是隻有單個節點的。比如上面的 1 標籤那樣。也就是說世界大學的標籤影響了
那就直接提取
.string就行了

def fillUnivList(ulist, html):
    soup = BeautifulSoup(html, "html.parser")
    for tr in soup.find('tbody').children: #提取tbody的每個tr標籤
        if isinstance(tr,bs4.element.Tag): #判斷tr是否是tag型別
            tds = tr('td')                 #tds[0] <td>1</td>.簡寫,等價於下一行程式碼
            #tds = tr.find_all('td')
            # ,然後用.string取string
            tmp=tds[1].find('a')
            #print(tds[0].string, tmp.string, tds[3].string)
            ulist.append([tds[0].string, tmp.string, tds[3].string])

11 相關網站

0.北理工課程
https://www.bilibili.com/video/av9784617?from=search&seid=12715531491861423376

1.廖雪峰官方網站
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

2.Python 知識庫
http://lib.csdn.net/python/node/68

  1. Python網路爬蟲與資訊提取(MOOC)
    http://www.icourse163.org/course/BIT-1001870001#/info

4.W3School 網頁前端學習及線上編輯
http://www.w3school.com.cn/h.asp

5.BeautifulSoup 官方文件
https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/