【MOOC】Python網路爬蟲與資訊提取-北京理工大學-part 2
【第二週】 網路爬蟲之提取
Beautiful Soup庫入門
Beautiful 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 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>
測試程式碼和對應的部分輸出:
注: prettify函式的作用是:列印一下 soup 物件的內容,進行格式化輸出,可以看到上面的該函式的輸出的形式很適合我們直接閱讀。另外,該函式使用得比較多,因此要多留意一下。更多的細節下面的“基於bs4庫的HTML格式輸出
常用使用方法:
Beautiful Soup庫基本元素
Beautiful Soup庫,也叫beautifulsoup4或 bs4,約定引用方式如下,即主要是用BeautifulSoup類:
from bs4 import BeautifulSoup
對庫的理解:
其他解析器:
對標籤的理解:
對標籤的進一步說明:
以之前的demo.html為例子:
在瀏覽器上顯示為:
使用requests庫爬取的效果:
具體分析demo的基本元素:
注意:
1.soup.a是指將soup中的名字為a的標籤(在HMTL5中 代表 連結標籤)提取出來
2.當HTML文件中存在多個相同對應內容時,soup.返回第一個
3.上面的tag的輸出是將原先的tag的屬性按照屬性的字母序重新排列得到的。
原先的是:
<a href="http://www.icourse163.org/course/BIT-268001" class="py1" id="link1">Basic Python</a>
輸出的是:
<a class="py1" href="http://www.icourse163.org/course/BIT-268001" id="link1">Basic Python</a>
注:簡單來說,Tag中的字串即為NavigableString物件。
注:Commet這個元素我們一般用得比較少。而判斷輸出的string變數是Commet的,還是NavigableString的則要使用type()函式來判斷。
基於bs4庫的HTML內容遍歷方法
掌握HTML內容遍歷方法可幫助我們從一個節點出發到其他節點的資訊。其遍歷方法是我們提取HTML內容的重要手段。
仍舊以demo為例,其原始碼為:
將其視為樹(文件樹)的形式,則有:
下面將介紹文件樹的下行遍歷、上行遍歷、平行遍歷。這3種遍歷的形式如圖:
標籤樹的下行遍歷
屬性說明
例項輸出
注意:對於標籤節點的兒子節點,其除了是標籤節點外還可能是字串節點,比如上面的soup.body.contents的輸出便是如此。可用len()函式獲取兒子節點的數目。
用法總結
標籤樹的上行遍歷
屬性說明
例項輸出1
注意:上面的程式碼中,輸出soup.parent的值是沒有任何輸出,這說明soup沒有父親節點。即BeautifulSoup型別是標籤樹的根節點,其沒有父親節點。
用法總結
注:BeautifulSoup型別是標籤樹的根節點。所以這裡要區別判斷。
標籤樹的平行遍歷
**注意點:平行遍歷發生在同一個父節點下的各節點間。
屬性說明
例項輸出
注:注意上面第二行程式碼的輸出,a標籤的平行節點是一個字串’and’。對應的解釋為:在標籤樹中,儘管樹形結構採用的是標籤的形式來組織,但是標籤之間的NavigableString也構成了節點,因此任何一個節點的父親、孩子、平行節點都可能是NavigableString型別的。
這也提示了我們在編寫程式碼的時候需要判斷節點的平行節點是否是標籤節點(以免將NavigableString型別節點誤判為標籤節點)。
用法總結
三種遍歷的總結示意圖
基於bs4庫的HTML格式輸出
在實際編寫爬蟲程式的時候,我們會遇到一個問題:如何讓HTML的內容更加“ 友好”地輸出?
這裡的“友好”包含兩方面的意思:
1.讓我們(編寫程式碼的人)更加方便地閱讀HTML的內容
2.讓程式更加方便地處理HTML的內容
通常,我們使用bs4庫的prettify()方法來解決這個問題。
bs4庫的prettify()方法
仍舊以demo為例:
若直接輸出demo,則其顯示效果不怎麼適合我們閱讀,如下:
若使用bs4庫的prettify()方法,則其顯示效果很不錯,如下:
同時,.prettify()也可用於標籤,方法:<tag>.prettify()
,舉例:
關於bs4庫的編碼
單元小結
資訊組織與提取方法
資訊標記的三種形式
資訊標記的三種形式分別為:HTML、JSON、YAML。世界上所有型別的資訊都可以通過這3種資訊進行組織和標記,使得資訊發揮更大的作用。
那麼什麼是資訊標記呢?以下圖為例,如果給你一個資訊(見下圖左側),比如“北京理工大學”,那麼你可以簡單明瞭地理解它。但若是給你一組資訊(見下圖中側),哪怕這一組資訊都是與某個概念相關,我們可能都要仔細想一想。所以我們需要對資訊進行一定的標記,使得我們能夠理解資訊的真實含義。比如,給“北京理工大學”標記為’name’,代表名字;給“北京市海淀區中關村”標記為‘addr’,代表地址(見下圖右側)。
資訊標記後的好處:
HTML的資訊標記(舉例說明資訊標記)
XML
XML可理解為是HTML的拓展。其簡要介紹如下:
當標籤內有內容時,我們用一對標籤來表達資訊:
當標籤內沒有內容時,我們可以使用一對尖括號來表達資訊:
註釋的形式:
JSON
簡要介紹:
一個key對應多個值時:
巢狀使用方法:
上述內容的簡要總結:
對於Javascript這種語言,JSON格式的程式碼可直接作為程式的一部分,為編寫程式帶來了簡化。
YAML
YAML也使用鍵值對來儲存資訊,但是相比JSON,其使用的是無型別的鍵值對來組織資訊。
使用縮排表達所屬關係:
使用“-”表達並列關係:
使用“| ”表達整塊資料 、“#” 表示註釋:
上述內容的簡要總結:
三種資訊標記形式的比較
下面先給出3個例項,然後再給出這三者的比較。
XML例項
JSON 例項
YAML 例項
資訊標記形式 | 語法 | 比較 | 應用場景 |
---|---|---|---|
XML | 使用尖括號、標籤標記資訊 | 最早的通用資訊標記語言,可擴充套件性好,但繁瑣 | Internet上的資訊互動與傳遞 |
JSON | 使用有型別的鍵值對標記資訊 | 資訊有型別,適合程式處理(js),較XML簡潔 | 移動應用雲端和節點的資訊通訊,無註釋 |
YAML | 使用無型別的鍵值對標記資訊 | 資訊無型別,文字資訊比例最高,可讀性好 | 各類系統的配置檔案,有註釋易讀 |
注意:JSON格式的檔案中不能添加註釋。
資訊提取的一般方法
資訊提取的一般方法:
注:第二種方式就相當於在word文件上搜索某些資訊一樣,我們根本不關心文章的標題是怎樣的。
在實際使用中我們大多是使用以上這兩種方法的融合。
例項
注:a標籤就是帶有連結的標籤。
基於bs4庫的HTML內容查詢方法
這裡仍舊以demo為例:
這裡主要介紹的是bs4庫中的find_all函式,具體如下:
注:
1.find_all函式的輸入引數為True時,函式會返回所有的tag
2.re模組是正則表示式模組,re.compile()將正則表示式的字串形式編譯為Pattern例項。find_all(re.compile(‘b’))會返回所有以b開頭的標籤。
注:第二行程式碼的輸出是一個空列表,這表明從根節點開始,沒有找到標籤為a的孩子節點。
注:
<tag>(..) 等價於 <tag>.find_all(..)
soup(..) 等價於 soup.find_all(..)
拓展方法
單元小結
例項1-中國大學排名爬蟲
例項介紹
功能描述
爬蟲程式終的顯示效果應為:
定向爬蟲可行性
在編寫爬蟲程式之前,需要了解網頁的原始碼的特點。檢視原始碼之後,發現我們需要的大學排名資訊都已經“嵌入”到原始碼之中了。
下一步,查詢robots協議:
在瀏覽器上輸入http://www.zuihaodaxue.cn/robots.txt,頁面返回的是“404 Not Found”資訊,因此沒有robots協議檔案,我們可以任意地爬取頁面資訊。
程式的結構設計
注:由於爬取的排名資料是二維的,因此我們可用列表來實現。
例項編寫
程式碼主要框架:
注:這種程式設計方式很值得借鑑,在編寫一個程式的時候,先將程式的功能模組化,先定義模組,然後編寫main函式的呼叫程式碼,從而在完成程式碼主要框架之後再進行模組程式碼的詳細編寫。這樣有助於我們理清思路和加快編碼速度。
全部程式碼:
#CrawUnivRankingA.py
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")
#檢視網頁原始碼後發現 排名資訊 在tbody標籤 中的 tr標籤
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag):#過濾掉非標籤型別
tds = tr('td') #取出tr標籤的td標籤,由於這一行程式碼的存在,因此需要import bs4
ulist.append([tds[0].string, tds[1].string, tds[3].string])
def printUnivList(ulist, num):
print("{:^10}\t{:^6}\t{:^10}".format("排名","學校名稱","總分"))#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
main()
例項優化
上面的程式碼執行結果為:
觀察發現,中間的大學名字的輸出沒有居中對齊(程式碼中的^符號就是居中對齊的意思),而根據上面的這段程式碼:
print("{:^10}\t{:^6}\t{:^10}".format(u[0],u[1],u[2]))
理應是居中對齊輸出的,那麼問題在哪裡呢?而觀察旁邊排名和總分資訊,發現這兩者的輸出是居中對齊輸出的。
中文對齊問題的原因
下面是format的一些常用使用語法:
原因便是:當中文字元寬度不夠時,採用西文字元填充;而中西文字元佔用寬度不同。
其解決方法就是:採用中文字元的空格填充 chr(12288)
因此將原來的printUnivList函式修改為:
def printUnivList(ulist, num):
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format("排名","學校名稱","總分",chr(12288)))#chr(12288)為中文字元的空格
for i in range(num):
u=ulist[i]
print(tplt.format(u[0],u[1],u[2],chr(12288)))
修正後的輸出: