1. 程式人生 > >python網路爬蟲-複雜HTML解析

python網路爬蟲-複雜HTML解析

     如何進行復雜HTML的解析,需要在實施中注意以下幾個方面:

(1)尋找“列印此頁”的連結,或者看看網站狀態有沒有HTML樣式更友好的移動版(把自己的請求頭資訊設定成處於移動裝置的狀態,然後接收網站的移動版);

(2)尋找隱藏在JavaScript檔案裡的資訊。要實現這一點,你可能需要檢視網頁載入的JavaScript檔案。我曾經要把一個網站上的街道地址(以經度和緯度呈現的)整理成格式整潔的陣列時,檢視過內嵌谷歌地圖的JavaScript檔案,裡面有每個地址的標記點。

(3)雖然網站標題通常會用到,但是這個資訊也許可以從網頁的URL連結中獲取。

(4)如果你要找的資訊只存在於一個網站上,別處沒有,那你確實運氣不佳。如果不止限於這個網站,那麼你可以找找其它資料來源。有沒有其它網站也顯示了同樣的資料?網站上顯示的資料是不是從其他網站上抓取後攢起來。

1. 再次使用BeautifulSoup

     在上面的文章中,我們演示了BeautifulSoup的安裝和使用過程,本篇文章中我們將繼續使用BeautifulSoup。對於現代網站頁面,CSS是大多數網站都會使用的,下面的的程式通過css的標籤來進行網路資料的收集:

from urllib.request import urlopen
from bs4 import BeautifulSoup
html=urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObj=BeautifulSoup(html,"html.parser")
通過BeautifulSoup物件,我們可以使用findAll函式抽取只包含在<span class="green"></span>標籤裡的文字,這樣就可以得到一個人物名稱的Python列表(findAll是一個非常靈活的函式,後面會經常使用)
nameList=bsObj.findAll("span",{"class":"green"})
for name in nameList:
       print(name.get_text())
執行結果如下:

成功地完成了指定資訊的爬取。我們呼叫了bsObj.tagName只能獲取頁面中的第一個指定的標籤。我們使用bsObj.findAll(tagName,tagAttributes)可以獲取頁面中所有指定的標籤,不再是第一個了。獲取了人名列表後,程式遍歷列表中所有的名字,然後列印name.get_text(),就可以把標籤中的內容分開顯示了。

      這邊會涉及到一個問題,也就是什麼時候使用get_text(),什麼時候應該保留標籤?

     .get_text()會把你正在處理的HTML文件中的所有的標籤都清除,然後返回一個只包含文字的字串。假如你正在處理一個包含許多超連結、段落和標籤的大段原始碼,那麼,使用,get_text()會把這些超連結、段落和標籤都清除掉,只剩下遺傳不帶標籤的文字。

       用BeautifulSoup物件查詢你想要的資訊,比直接在HTML文本里查詢資訊要簡單地多。通常情況在你準備列印、儲存和操作資料時,應該最後才使用.get_text()。一般情況下,你應該儘可能地保留HTML文件的標籤結構。

2. 區分BeautifulSoup中的find和findAll()

    BeautifulSoup裡的find()和findAll()可能是你最常用的兩個函式,你可以通過標籤的不同屬性輕鬆的過濾HTML頁面,查詢需要的標籤組或單個標籤。這兩個函式非常相似,BeautifulSoup文件裡兩者的定義如下:

findAll(tag,attributes,recursive,text,limit,keywords)
find(tag,attributes,recursive,text,keywords)
     我們一般只會使用前兩個引數tag和attributes。標籤tag前面已經介紹過-你可以傳入一個標籤的名稱或多個標籤名稱組成的Python列表做引數。例如,下面的程式碼將返回一個包含HTML文件中所有標題標籤的列表:
findAll("h1","h2","h3","h4","h5","h6")
      屬性引數attributes是一個用Python字典封裝一個標籤的若干屬性和對應的屬性值。例如,下面這個函式會返回HTML文件裡紅色和綠色兩種顏色的span標籤:
findAll("span",{"class":{"green","red"}})
     遞迴引數recursive是一個布林變數。你想抓取HTML文件結構裡多少層的資訊呢?如果recursive為true,findAll就會根據你的要求去查詢標籤引數的所有子標籤,以及子標籤的子標籤。如果recursive設定為false,findAll就值查詢文件的額一級標籤。findAll預設是支援遞迴查詢的(recursive預設值是true);一般情況下這個引數不需要設定,除非你真正瞭解自己需要哪些訊息,而且抓取速度非常重要,那時你可以設定遞迴函式。

     文字引數text有點不同,它是用標籤的文字內容去匹配,而不是標籤的屬性。假如我們想查詢前面網頁中包含“the price”內容的標籤數量,我們可以把之前的findAll方法換成下面的程式碼:

nameList=bsObj.findAll(text="the prince")
print(len(nameList))
執行結果為7個。

    範圍限制引數limit,顯然只用於findAll方法。find其實等價於findAll的limit等於1的情形。如果你只對網頁中獲取的前x項結果感興趣,就可以設定它。但是要注意,這個引數設定之後,獲得的前幾項結果時按照網頁上的順序排序的,未必是你想要的那前幾項。

     還有一個關鍵詞引數keyword,可以讓你選擇那些具有指定屬性的標籤,例如:

allText=bsObj.findAll(id="text")
print(allText[0].get_text())
     雖然關鍵詞引數keyword在一些場景中很有用,但是,它是BeautifulSoup在技術上做的一個冗餘功能。任何用關鍵詞引數能夠完成的任務,都能適用後續的技術解決。例如,下面兩行程式碼時完全一致的:
bsObj.findAll(id="text")
bsObj.findAll("",{"id":"text"})
另外,用keyword偶爾會出現問題,尤其是在用class屬性查詢標籤的時候,因為class是Python中受保護的關鍵字。也就是說,class是Python語言的保留字,在Python程式裡是不能當做變數或者引數名適用的。假如你執行下面的程式碼,Python就會因為你誤用class關鍵字而產生一個語法錯誤:
bsObj.findAll(class="green")
不過,你可以用BeautifulSoup提供的有點臃腫的方案,在class後面增加一個下劃線:
bsObj.findAll(class_="green")
另外,你也可以用屬性引數把class用引號括起來。
bsObj.findAll("class"="green")

      總結一下,find和findAll中的通過標籤引數tag把標籤列表傳入,其實就是一個“或”關係過濾器,而關鍵詞引數keyword就是讓你增加一個“與”關係的過濾器來簡化工作

3. 其他BeautifulSoup物件

      看到這裡,你已經見過BeautifulSoup庫裡的兩種物件了

(1)BeautifulSoup物件

      前面程式碼示例中的bsObj

(2)標籤Tag物件

      BeautifulSoup物件通過find和findAll,或者直接呼叫子標籤獲取的一列物件或單個物件,就像:

bsObj.div.h1

(3)NavigableString物件

      用來表示標籤裡的文字,不是標籤(有些函式可以操作和生成NavigableString物件,而不是標籤物件)

(4)Comment物件

     用來查詢HTML文件的註釋標籤,<!--   -->