第四章 提取網頁中的信息
4.1 數據的類型
網頁中數據的類型簡單來說可以分成以下三類:
4.1.1結構化數據
可以用統一的結構加以表示的數據。可以使用關係型數據庫表示和存儲,表現為二維形式的數據。一般特點是:數據以行為單位,一行數據表示一個實體的信息,每一行數據的屬性是相同的。
比如MySQL數據庫表中的數據:
id name age gender
aid1 馬化騰 46 male
aid2 馬雲 53 male
aid3 李彥宏 49 male
4.1.2半結構化數據
是結構化數據的一種形式,並不符合關係型數據庫或其他數據表的形式關聯起來的數據模型結構,但包含相關標記,用來分隔語義元素以及對記錄和字段進行分層。因此,它也被稱為
自描述的結構。常見的半結構數據有HTML,XML和JSON等,實際上是以樹或者圖的結構來存儲的。比如,一個簡單的XML表示:
<person>
<name>A/<name>
<class>aid1710/<class>
<gender>female/<gender>
或者
<person>
<name>B/<name>
<gender>male/<gender>
結點中屬性的順序是不重要的,不同的半結構化數據的屬性的個數是不一定一樣的。這樣的數據格式,可以自由地表達很多有用的信息,包括自描述信息(元數據)。所以,半結構化數據的擴展性很好,特別適合於在互聯網中大規模傳播。
4.1.3非結構化數據
就是沒有固定結構的數據。各種文檔、圖片、視頻/音頻等都屬於非結構化數據。對於這類數據,我們一般直接整體進行存儲,而且一般存儲為二進制的數據格式;
除了結構化和半結構數據之外的數據都是非結構化數據。
4.2 關於XML,HTML,DOM和JSON文件
4.2.1 XML, HTML, DOM
XML即Extentsible Markup Language(可擴展標記語言),是用來定義其它語言的一種元語言,其前身是SGML(標準通用標記語言)。它沒有標籤集(tagset),也沒有語法規則(grammatical rule),但是它有句法規則(syntax rule)。任何XML文檔對任何類型的應用以及正確的解析都必須是良構的(well-formed),即每一個打開的標籤都必須有匹配的結束標籤,不得含有次序顛倒的標籤,並且在語句構成上應符合技術規範的要求。XML文檔可以是有效的(valid),但並非一定要求有效。所謂有效文檔是指其符合其文檔類型定義(DTD)的文檔。如果一個文檔符合一個模式(schema)的規定,那麼這個文檔是模式有效的(schema valid)。
HTML(Hyper Text Mark-up Language)即超文本標記語言,是WWW的描述語言。HTML與XML的區別與聯繫:
XML和HTML都是用於操作數據或數據結構,在結構上大致是相同的,但它們在本質上卻存在著明顯的區別。綜合網上的各種資料總結如下。
(一)語法要求不同:
1. 在HTML中不區分大小寫,在XML中嚴格區分。
2. 在HTML中,有時不嚴格,如果上下文清楚地顯示出段落或者列表鍵在何處結尾,那麼你可以省略
或者之類的結束標記。在XML中,是嚴格的樹狀結構,絕對不能省略掉結束標記。3. 在XML中,擁有單個標記而沒有匹配的結束標記的元素必須用一個/ 字符作為結尾。這樣分析器就知道不用查找結束標記了。
4. 在XML中,屬性值必須分裝在引號中。在HTML中,引號是可用可不用的。
5. 在HTML中,可以擁有不帶值的屬性名。在XML中,所有的屬性都必須帶有相應的值。
6. 在XML文檔中,空白部分不會被解析器自動刪除; 但是html是過濾掉空格的。
XML的語法要求比HTML嚴格。
(二)標記不同:
1. HTML使用固有的標記; 而XML沒有固有的標記。
2. HTML標籤是預定義的; XML標籤是免費的、自定義的、可擴展的。
(三)作用不同:
1. HTML是用來顯示數據的; XML是用來描述數據、存放數據的,所以可以作為持久化的介質。HTML將數據和顯示結合在一起,在頁面中把這數據顯示出來;xml則將數據和顯示分開。 XML被設計用來描述數據,其焦點是數據的內容。HTML被設計用來顯示數據,其焦點是數據的外觀。
2. XML不是HTML的替代品,XML和HTML是兩種不同用途的語言。 XML 不是要替換 HTML;實際上XML 可以視作對 HTML 的補充。XML 和HTML 的目標不同HTML 的設計目標是顯示數據並集中於數據外觀,而XML的設計目標是描述數據並集中於數據的內容。
3. 沒有任何行為的XML, 與HTML 相似, XML不進行任何操作(共同點)。
4. 對於XML最好的形容可能是: XML是一種跨平臺的,與軟、硬件無關的,處理與傳輸信息的工具。
5. XML未來將會無所不在,XML將成為最普遍的數據處理和數據傳輸的工具。
關於DOM:
文檔對象模型(Document Object Model,簡稱DOM),是W3C組織推薦的處理可擴展標誌語言的標準編程接口。在網頁上,組織頁面(或文檔)的對象被組織在一個樹形結構中,用來表示文檔中對象的標準模型就稱為DOM。Document Object Model的歷史可以追溯至1990年代後期微軟與Netscape的“瀏覽器大戰”,雙方為了在JavaScript與JScript一決生死,於是大規模的賦予瀏覽器強大的功能。微軟在網頁技術上加入了不少專屬事物,既有VBScript、ActiveX、以及微軟自家的DHTML格式等,使不少網頁使用非微軟平臺及瀏覽器無法正常顯示。DOM即是當時蘊釀出來的傑作。
DOM= Document Object Model,文檔對象模型,DOM可以以一種獨立於平臺和語言的方式訪問和修改一個文檔的內容和結構。換句話說,這是表示和處理一個HTML或XML文檔的常用方法。DOM很重要,DOM的設計是以對象管理組織(OMG)的規約為基礎的,因此可以用於任何編程語言。最初人們把它認為是一種讓JavaScript在瀏覽器間可移植的方法,不過DOM的應用已經遠遠超出這個範圍。DOM技術使得用戶頁面可以動態地變化,如可以動態地顯示或隱藏一個元素,改變它們的屬性,增加一個元素等,DOM技術使得頁面的交互性大大地增強。
DOM實際上是以面向對象方式描述的文檔模型。DOM定義了表示和修改文檔所需的對象、這些對象的行為和屬性以及這些對象之間的關係。可以把DOM認為是頁面上數據和結構的一個樹形表示,不過頁面當然可能並不是以這種樹的方式具體實現。
通過 JavaScript,您可以重構整個 HTML 文檔。您可以添加、移除、改變或重排頁面上的項目。要改變頁面的某個東西,JavaScript 就需要獲得對 HTML 文檔中所有元素進行訪問的入口。這個入口,連同對 HTML 元素進行添加、移動、改變或移除的方法和屬性,都是通過文檔對象模型來獲得的(DOM)。
4.2.2 JSON文件
JSON(JavaScript Object Notation, JS對象標記) 是一種輕量級的數據交換格式。它基於 ECMAScript (w3c制定的JS規範)的一個子集,採用完全獨立於編程語言的文本格式來存儲和表示數據。簡潔和清晰的層次結構使得 JSON 成為理想的數據交換語言。 易於人閱讀和編寫,同時也易於機器解析和生成,並有效地提升網絡傳輸效率。
JSON 語法規則:
在JS 語言中,一切都是對象。因此,任何支持的類型都可以通過 JSON 來表示,例如字符串、數字、對象、數組等。
但是對象和數組是比較特殊且常用的兩種類型:
1.對象表示為鍵值對
2.數據由逗號分隔
3.花括號保存對象
4.方括號保存數組
JSON鍵值對是用來保存 JS 對象的一種方式,和 JS 對象的寫法也大同小異,
鍵/值對組合中的鍵名寫在前面並用雙引號 "" 包裹,使用冒號 : 分隔,然後緊接著值
{"firstName": "Json","class":"aid1710"}
這很容易理解,等價於這條 JavaScript 語句:
{firstName : "Json","class":"aid1710"}
JSON與JS對象的關係:
很多人搞不清楚 JSON 和JS對象的關係,甚至連誰是誰都不清楚。其實,可以這麼理解:JSON是 JS對象的字符串表示法,它使用文本表示一個 JS 對象的信息,本質是一個字符串。
如var obj = {a: 'Hello', b: 'World'}; //這是一個對象,注意鍵名也是可以使用引號包裹的
var json = '{"a": "Hello", "b": "World"}'; //這是一個 JSON 字符串,本質是一個字符串。
Python中關於JSON的操作簡單演示:
SpiderCodes\\ testJson.py
JSON和XML的比較:
1.可讀性:
JSON和XML的可讀性可謂不相上下,一邊是簡易的語法,一邊是規範的標籤形式,很難分出勝負。
2.可擴展性:
XML天生有很好的擴展性,JSON當然也有,沒有什麼是XML可以擴展而JSON卻不能擴展的。不過JSON在Javascript主場作戰,可以存儲Javascript複合對象,有著xml不可比擬的優勢。
3.編碼難度:
XML有豐富的編碼工具,比如Dom4j、JDom等,JSON也有提供的工具。無工具的情況下,相信熟練的開發人員一樣能很快的寫出想要的xml文檔和JSON字符串,不過,xml文檔要多很多結構上的字符。
4.解碼難度
XML的解析方式有兩種:
一是通過文檔模型解析,也就是通過父標籤索引出一組標記。例如:xmlData.getElementsByTagName("tagName"),但是這樣是要在預先知道文檔結構的情況下使用,無法進行通用的封裝。
另外一種方法是遍歷節點(document 以及 childNodes)。這個可以通過遞歸來實現,不過解析出來的數據仍舊是形式各異,往往也不能滿足預先的要求。凡是這樣可擴展的結構數據解析起來一定都很困難。JSON也同樣如此。如果預先知道JSON結構的情況下,使用JSON進行數據傳遞簡直是太美妙了,可以寫出很實用美觀可讀性強的代碼。
如果你是純粹的前臺開發人員,一定會非常喜歡JSON。但是如果你是一個應用開發人員,就不是那麼喜歡了,畢竟xml才是真正的結構化標記語言,用於進行數據傳遞。而如果不知道JSON的結構而去解析JSON的話,那簡直是噩夢。費時費力不說,代碼也會變得冗餘拖沓,得到的結果也不盡人意。
但是這樣也不影響眾多前臺開發人員選擇JSON。因為json.js中的toJSONString()就可以看到JSON的字符串結構。當然不是使用這個字符串,這樣仍舊是噩夢。常用JSON的人看到這個字符串之後,就對JSON的結構很明瞭了,就更容易的操作JSON。以上是在Javascript中僅對於數據傳遞的xml與JSON的解析。
在Javascript地盤內,JSON畢竟是主場作戰,其優勢當然要遠遠優越於xml 。
如果JSON中存儲Javascript複合對象,而且不知道其結構的話,相信很多程序員也一樣是哭著解析JSON的。除了上述之外,JSON和XML還有另外一個很大的區別在於有效數據率。JSON作為數據包格式傳輸的時候具有更高的效率,這是因為JSON不像XML那樣需要有嚴格的閉合標籤,這就讓有效數據量與總數據包比大大提升,從而減少同等數據流量的情況下,網絡的傳輸壓力。
實例比較:
XML和JSON都使用結構化方法來標記數據,下面來做一個簡單的比較。
用XML表示中國部分省市數據如下:
<country>
<name>中國/<name>
<province>
<name>黑龍江/<name>
<cities>
<city>哈爾濱/<city>
<city>大慶/<city>
<province>
<name>廣東/<name>
<cities>
<city>廣州/<city>
<city>深圳/<city>
<city>珠海/<city>
<province>
<name>臺灣/<name>
<cities>
<city>臺北/<city>
<city>高雄/<city>
<province>
<name>新疆/<name>
<cities>
<city>烏魯木齊/<city>
用JSON表示如下:
{
"name": "中國",
"province": [{
"name": "黑龍江",
"cities": {
"city": ["哈爾濱", "大慶"]
}
}, {
"name": "廣東",
"cities": {
"city": ["廣州", "深圳", "珠海"]
}
}, {
"name": "臺灣",
"cities": {
"city": ["臺北", "高雄"]
}
}, {
"name": "新疆",
"cities": {
"city": ["烏魯木齊"]
}
}]
}
可以看到:JSON簡單的語法格式和清晰的層次結構明顯要比XML容易閱讀,並且在數據交換方面,由於JSON所使用的字符要比XML少得多,可以大大得節約傳輸數據所佔用得帶寬。
4.3 怎麼提取網頁中的信息
4.3.1 XPath與lxml
XPath是一門在XML文檔中查找信息的語言,對XPath的理解是很多高級XML應用的基礎,XPath在XML中通過元素和屬性進行導航。
lxml是一個用來處理XML的第三方 Python 庫,它在底層封裝了用 C 語言編寫的 libxml2和libxslt,並以簡單強大的Python API,兼容並加強了著名的Element Tree API。
安裝:pip install lxml
使用:from lxml import etree
1. XPath術語:
在XPath語境中,XML 文檔被視作節點樹,節點樹的根節點也被稱作文檔節點。 XPath 將節點樹中的節點(Node)分為七類:
元素(Element),屬性(Attribute),文本(Text),命名空間(Namespace),處理指令(Processing-instruction),註釋(Comment)和文檔節點(Document nodes)。看一下 XML 文檔例子:
\t
\t<bookstore>
\t<book>
\t <title>Harry Potter/<title>
\t <author>J K. Rowling/<author>
\t <year>2005/<year>
\t <price>29.99/<price>
\t/<book>
\t/<bookstore>
以上的XML文檔中:
<bookstore> (這是一個“根”)/<bookstore>
<author>J K. Rowling/<author> (這是一個“元素”)
lang="en" (這是一個“屬性”)
從另一個視角來看它:
bookstore (根)
book (元素)
title (元素)
lang = en (屬性)
text = Harry Potter (文本)
author (元素)
text = J K. Rowling (文本)
year (元素)
text = 2005 (文本)
price (元素)
text = 29.99 (文本)
2. 節點之間的關係
父(Parent):每個元素都肯定有一個父節點,最頂層的元素父親是根節點。同理每個屬性必然有一個父,它們的父是元素。 上例XML文檔中,根bookstore是元素 book 的父節點,book是元素title, author, year, price 的父節點,title是lang 的父節點。
子(Children):元素可以有零或多個子。上例XML文檔中,title, author, year, price是book的子節點。
同胞(Sibling):父節點相同的節點之間互為同胞,也稱彼此的兄弟節點。上例XM文檔中,title, author, year, price 彼此互為同胞。
先輩(Ancestor):某節點的父節點、父的父,以此類推一直追溯至根節點之間所有節點。上例XM文檔中,title, author, year, price 的先輩就是 book, bookstore。
後代(Descendant):某節點的子節點、子的子,以此類推至最後一個子節點之間所有節點。上例XM文檔中,bookstore 的後代就是 title, author, year, price 。
3. 選取節點
以下為基本路徑的表達方式,記住XPath的路徑表達式都是基於某個節點之上的,例如最初的當前節點一般是根節點,這與Linux下路徑切換原理是一樣的。
表達式描述:
nodename 選取已匹配節點下名為 nodename 的子元素節點
/ 如果以 / 開頭,表示從根節點作為選取起點。
// 在已匹配節點後代中選取節點,不考慮目標節點的位置。
. 選取當前節點。
.. 選取當前節點的父元素節點。
@ 選取屬性。
4. 通配符
* 匹配任何元素。
@* 匹配任何屬性。
node() 匹配任何類型的節點。
5. 預判(Predicates)或 條件選取
預判是用來查找某個特定的節點或者符合某種條件的節點,預判表達式位於方括號中。使用 “|” 運算符,你可以選取符合“或”條件的若干路徑。
具體例子見下面代碼lxmlTest.py。
6. 座標軸
XPath 座標軸:座標軸用於定義當對當前節點的節點集合。
座標軸名稱 含義
ancestor 選取當前節點的所有先輩元素及根節點。
ancestor-or-self 選取當前節點的所有先輩以及當前節點本身。
attibute 選取當前節點的所有屬性。
child 選取當前節點的所有子元素。
descendant 選取當前節點的所有後代元素。
descendant-or-self 選取當前節點的所有後代元素以及當前節點本身。
following 選取文檔中當前節點的結束標籤之後的所有節點。
following-sibling 選取當前節點之後的所有同級節點。
namespace 選取當前節點的所有命名空間節點。
parent 選取當前節點的父節點。
preceding 選取當前節點的開始標籤之前的所有節點。
preceding-sibling 選取當前節點之前的所有同級節點。
self 選取當前節點。
7. 位置路徑的表達式
位置路徑可以是絕對路徑,也可以是相對路徑。絕對路徑以 “/” 開頭。每條路徑包括一個或多個步,每步之間以“/”分隔。
絕對路徑:/step/step/…
相對路徑:step/step/…
每步根據當前節點集合中的節點計算。
步(step)包括三部分:
座標軸(axis): 定義所選節點與當前節點之間的關係。
節點測試(node-test):識別某個座標軸內部的節點。
預判(predicate): 提出預判條件對節點集合進行篩選。
步的語法:座標軸::節點測試[預判]
**代碼例子:SpiderCodes \\lxmlTest.py
4.3.2 BeautifulSoup4
Beautiful Soup是用Python寫的一個HTML/XML的解析器,它可以很好的處理不規範標記並生成剖析樹(parse tree)。 它提供簡單又常用的導航(navigating),搜索以及修改剖析樹的操作。它可以大大節省你的編程時間。
安裝:(sudo) pip install beautifuilsoup4
使用:
在程序中導入 Beautiful Soup庫:
\tfrom BeautifulSoup import BeautifulSoup # For processing HTML
from BeautifulSoup import BeautifulStoneSoup # For processing XML
import BeautifulSoup # To get everything
# 代碼例子
from bs4 import BeautifulSoup
import re
doc = ['
<title>Page title/<title>','
This is paragraph one.',
'
This is paragraph two.',
'']
soup = BeautifulSoup(''.join(doc))
print soup.prettify()
定位某些 soup元素很簡單,比如上例:
soup.contents[0].name
# u'html'
soup.contents[0].contents[0].name
# u'head'
head = soup.contents[0].contents[0]
head.parent.name
# u'html'
head.next
#
<title>Page title/<title>head.nextSibling.name
# u'body'
head.nextSibling.contents[0]
#
This is paragraph one.
head.nextSibling.contents[0].nextSibling
#
This is paragraph two.
也可以利用soup,獲得特定標籤或有著特定屬性的標籤,修改soup也很簡單;
**代碼例子:SpiderCodes \\bs4Test.py
進一步閱讀,可以參考:
https://www.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html
BS4 與 lxml的比較:
lxml C實現,只會局部遍歷,快; 複雜,語法不太友好;
BS4 Python實現,會加載整個文檔,慢; 簡單,API人性化;
4.3.3 正則表達式re
被用來檢索\\替換那些符合某個模式(規則)的文本,對於文本過濾或規則匹配,最強大的就是正則表達式,是python爬蟲裡必不可少的神兵利器。
基本匹配規則:
[0-9] 任意一個數字,等價\\d
[a-z] 任意一個小寫字母
[A-Z]任意一個大寫字母
[^0-9] 匹配非數字,等價\\D
\\w 等價[a-z0-9_],字母數字下劃線
\\W 等價對\\w取非
. 任意字符
[] 匹配內部任意字符或子表達式
[^] 對字符集合取非
* 匹配前面的字符或者子表達式0次或多次
+ 匹配前一個字符至少1次
? 匹配前一個字符0次或多次
^ 匹配字符串開頭
$ 匹配字符串結束
Python使用正則表達式
Python的re模塊
pattern 編譯好的正則表達式
幾個重要的方法:
match: 匹配一次從開頭;
search: 匹配一次,從某位置;
findall: 匹配所有;
split: 分隔;
sub: 替換;
需要注意的兩種模式:
貪婪模式:(.*)
懶惰模式:(.*?)
代碼例子見: SpiderCodes\\greedyRe.py
練習:
1.使用一個正則表達式來匹配url鏈接;如http://www.baidu.com
2.抓取http://example.webscraping.com/sitemap.xml下的數據
見:rePracticep2.py
3. 用正則表達式實現下面的效果:
把 i=d%0A&from=AUTO&to=AUTO&smartresult=dict
轉換成下面的形式:
i:d%0A
from:AUTO
to:AUTO
smartresult:dict
關於正則表達式,更多的細節,可以讀下這篇文章:
https://www.cnblogs.com/deerchao/archive/2006/08/24/zhengzhe30fengzhongjiaocheng.html
總結:正則,BS,lxml的比較
閱讀更多 小悅 的文章