上半部分內容鏈接 : https://www.cnblogs.com/lowmanisbusy/p/9069330.html
四.json和jsonpath的使用
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,它使得人們很容易的進行閱讀和編寫。同時也方便了機器進行解析和生成。適用於進行數據交互的場景,比如網站前台與后台之間的數據交互。
JSON和XML的比較可謂不相上下。
Python 2.7中自帶了JSON模塊,直接import json
就可以使用了。
官方文檔:http://docs.python.org/library/json.html
Json在線解析網站:http://www.json.cn/#
JSON
json簡單說就是javascript中的對象和數組,所以這兩種結構就是對象和數組兩種結構,通過這兩種結構可以表示各種復雜的結構
對象:對象在js中表示為
{ }
括起來的內容,數據結構為{ key:value, key:value, ... }
的鍵值對的結構,在面向對象的語言中,key為對象的屬性,value為對應的屬性值,所以很容易理解,取值方法為 對象.key 獲取屬性值,這個屬性值的類型可以是數字、字符串、數組、對象這幾種。數組:數組在js中是中括號
[ ]
括起來的內容,數據結構為["Python", "javascript", "C++", ...]
,取值方式和所有語言中一樣,使用索引獲取,字段值的類型可以是 數字、字符串、數組、對象幾種。
import json
json模塊提供了四個功能:dumps
、dump
、loads
、load
,用於字符串 和 python數據類型間進行轉換。
1. json.loads()
把Json格式字符串解碼轉換成Python對象 從json到python的類型轉化對照如下:
# json_loads.py import json strList = '[1, 2, 3, 4]' strDict = '{"city": "北京", "name": "大貓"}' json.loads(strList) # [1, 2, 3, 4] json.loads(strDict) # json數據自動按Unicode存儲 # {u'city': u'\u5317\u4eac', u'name': u'\u5927\u732b'}
2. json.dumps()
實現python類型轉化為json字符串,返回一個str對象 把一個Python對象編碼轉換成Json字符串
從python原始類型向json類型的轉化對照如下:
# json_dumps.py import json import chardet listStr = [1, 2, 3, 4] tupleStr = (1, 2, 3, 4) dictStr = {"city": "北京", "name": "大貓"} json.dumps(listStr) # '[1, 2, 3, 4]' json.dumps(tupleStr) # '[1, 2, 3, 4]' # 注意:json.dumps() 序列化時默認使用的ascii編碼 # 添加參數 ensure_ascii=False 禁用ascii編碼,按utf-8編碼 # chardet.detect()返回字典, 其中confidence是檢測精確度 json.dumps(dictStr) # '{"city": "\\u5317\\u4eac", "name": "\\u5927\\u5218"}' chardet.detect(json.dumps(dictStr)) # {'confidence': 1.0, 'encoding': 'ascii'} print json.dumps(dictStr, ensure_ascii=False) # {"city": "北京", "name": "大劉"} chardet.detect(json.dumps(dictStr, ensure_ascii=False)) # {'confidence': 0.99, 'encoding': 'utf-8'}
chardet是一個非常優秀的編碼識別模塊,可通過pip安裝
3. json.dump()
將Python內置類型序列化為json對象后寫入文件
# json_dump.py import json listStr = [{"city": "北京"}, {"name": "大劉"}] json.dump(listStr, open("listStr.json","w"), ensure_ascii=False) dictStr = {"city": "北京", "name": "大劉"} json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)
4. json.load()
讀取文件中json形式的字符串元素 轉化成python類型
# json_load.py import json strList = json.load(open("listStr.json")) print strList # [{u'city': u'\u5317\u4eac'}, {u'name': u'\u5927\u5218'}] strDict = json.load(open("dictStr.json")) print strDict # {u'city': u'\u5317\u4eac', u'name': u'\u5927\u5218'}
JsonPath
JsonPath 是一種信息抽取類庫,是從JSON文檔中抽取指定信息的工具,提供多種語言實現版本,包括:Javascript, Python, PHP 和 Java。
JsonPath 對於 JSON 來說,相當於 XPATH 對於 XML。
下載地址:https://pypi.python.org/pypi/jsonpath
安裝方法:點擊
Download URL
鏈接下載jsonpath,解壓之后執行python setup.py install
JsonPath與XPath語法對比:
Json結構清晰,可讀性高,復雜度低,非常容易匹配,下表中對應了XPath的用法。
XPath | JSONPath | 描述 |
---|---|---|
/ |
$ |
根節點 |
. |
@ |
現行節點 |
/ |
. or[] |
取子節點 |
.. |
n/a | 取父節點,Jsonpath未支持 |
// |
.. |
就是不管位置,選擇所有符合條件的條件 |
* |
* |
匹配所有元素節點 |
@ |
n/a | 根據屬性訪問,Json不支持,因為Json是個Key-value遞歸結構,不需要。 |
[] |
[] |
迭代器標示(可以在里邊做簡單的迭代操作,如數組下標,根據內容選值等) |
| | [,] |
支持迭代器中做多選。 |
[] |
?() |
支持過濾操作. |
n/a | () |
支持表達式計算 |
() |
n/a | 分組,JsonPath不支持 |
示例:
我們以拉勾網城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 為例,獲取所有城市。
# jsonpath_lagou.py import urllib2 import jsonpath import json import chardet url = 'http://www.lagou.com/lbs/getAllCitySearchLabels.json' request =urllib2.Request(url) response = urllib2.urlopen(request) html = response.read() # 把json格式字符串轉換成python對象 jsonobj = json.loads(html) # 從根節點開始,匹配name節點 citylist = jsonpath.jsonpath(jsonobj,'$..name') print citylist print type(citylist) fp = open('city.json','w') content = json.dumps(citylist, ensure_ascii=False) print content fp.write(content.encode('utf-8')) fp.close()
注意事項:
json.loads() 是把 Json格式字符串解碼轉換成Python對象,如果在json.loads的時候出錯,要注意被解碼的Json字符的編碼。
如果傳入的字符串的編碼不是UTF-8的話,需要指定字符編碼的參數 encoding
dataDict = json.loads(jsonStrGBK);
-
dataJsonStr是JSON字符串,假設其編碼本身是非UTF-8的話而是GBK 的,那么上述代碼會導致出錯,改為對應的:
dataDict = json.loads(jsonStrGBK, encoding="GBK");
-
如果 dataJsonStr通過encoding指定了合適的編碼,但是其中又包含了其他編碼的字符,則需要先去將dataJsonStr轉換為Unicode,然后再指定編碼格式調用json.loads()
``` python
dataJsonStrUni = dataJsonStr.decode("GB2312"); dataDict = json.loads(dataJsonStrUni, encoding="GB2312");
##字符串編碼轉換
這是中國程序員最苦逼的地方,什么亂碼之類的幾乎都是由漢字引起的。
其實編碼問題很好搞定,只要記住一點:
####任何平台的任何編碼 都能和 Unicode 互相轉換
UTF-8 與 GBK 互相轉換,那就先把UTF-8轉換成Unicode,再從Unicode轉換成GBK,反之同理。
``` python
# 這是一個 UTF-8 編碼的字符串
utf8Str = "你好地球"
# 1. 將 UTF-8 編碼的字符串 轉換成 Unicode 編碼
unicodeStr = utf8Str.decode("UTF-8")
# 2. 再將 Unicode 編碼格式字符串 轉換成 GBK 編碼
gbkData = unicodeStr.encode("GBK")
# 1. 再將 GBK 編碼格式字符串 轉化成 Unicode
unicodeStr = gbkData.decode("gbk")
# 2. 再將 Unicode 編碼格式字符串轉換成 UTF-8
utf8Str = unicodeStr.encode("UTF-8")
decode
的作用是將其他編碼的字符串轉換成 Unicode 編碼
encode
的作用是將 Unicode 編碼轉換成其他編碼的字符串
一句話:UTF-8是對Unicode字符集進行編碼的一種編碼方式
<<<<<<<<<<<<<<<<<<<邪惡的分割線>>>>>>>>>>>>>>>>>>>>
四.使用xpath對頁面數據進行解析
xpath是最常用的一種對頁面數據進行解析提取的一種方式,如果打算從事爬蟲方向的程序員,建議學好xpath解析式
我們可以先將 HTML文件 轉換成 XML文檔,然后用 XPath語法 查找 HTML 節點或元素。
什么是XML
- XML 指可擴展標記語言(EXtensible Markup Language)
- XML 是一種標記語言,很類似 HTML
- XML 的設計宗旨是傳輸數據,而非顯示數據
- XML 的標簽需要我們自行定義。
- XML 被設計為具有自我描述性XML 和 HTML 的區別
XML 和 HTML 的區別
數據格式 | 描述 | 設計目標 |
---|---|---|
XML | Extensible Markup Language (可擴展標記語言) |
被設計為傳輸和存儲數據,其焦點是數據的內容。 |
HTML | HyperText Markup Language (超文本標記語言) |
顯示數據以及如何更好顯示數據。 |
HTML DOM | Document Object Model for HTML (文檔對象模型) |
通過 HTML DOM,可以訪問所有的 HTML 元素,連同它們所包含的文本和屬性。可以對其中的內容進行修改和刪除,同時也可以創建新的元素。 |
XML文檔示例
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book category="cooking"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis</author> <year>2005</year> <price>30.00</price> </book> <book category="children"> <title lang="en">Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> <book category="web"> <title lang="en">XQuery Kick Start</title> <author>James McGovern</author> <author>Per Bothner</author> <author>Kurt Cagle</author> <author>James Linn</author> <author>Vaidyanathan Nagarajan</author> <year>2003</year> <price>49.99</price> </book> <book category="web" cover="paperback"> <title lang="en">Learning XML</title> <author>Erik T. Ray</author> <year>2003</year> <price>39.95</price> </book> </bookstore>
HTML DOM 模型示例
HTML DOM 定義了訪問和操作 HTML 文檔的標准方法,以樹結構方式表達 HTML 文檔。
XML的節點關系
1. 父(Parent)
每個元素以及屬性都有一個父。
下面是一個簡單的XML例子中,book 元素是 title、author、year 以及 price 元素的父:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>
2. 子(Children)
元素節點可有零個、一個或多個子。
在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>
3. 同胞(Sibling)
擁有相同的父的節點
在下面的例子中,title、author、year 以及 price 元素都是同胞:
<?xml version="1.0" encoding="utf-8"?> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book>
4. 先輩(Ancestor)
某節點的父、父的父,等等。
在下面的例子中,title 元素的先輩是 book 元素和 bookstore 元素:
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore>
5. 后代(Descendant)
某個節點的子,子的子,等等。
在下面的例子中,bookstore 的后代是 book、title、author、year 以及 price 元素:
<?xml version="1.0" encoding="utf-8"?> <bookstore> <book> <title>Harry Potter</title> <author>J K. Rowling</author> <year>2005</year> <price>29.99</price> </book> </bookstore>
什么是XPath?
XPath (XML Path Language) 是一門在 XML 文檔中查找信息的語言,可用來在 XML 文檔中對元素和屬性進行遍歷。
W3School官方文檔:http://www.w3school.com.cn/xpath/index.asp
選取節點
XPath 使用路徑表達式來選取 XML 文檔中的節點或者節點集。這些路徑表達式和我們在常規的電腦文件系統中看到的表達式非常相似。
下面列出了最常用的路徑表達式:
表達式 | 描述 |
---|---|
nodename | 選取此節點的所有子節點。 |
/ | 從根節點選取。 |
// | 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置。 |
. | 選取當前節點。 |
.. | 選取當前節點的父節點。 |
@ | 選取屬性。 |
在下面的表格中,我們已列出了一些路徑表達式以及表達式的結果:
路徑表達式 | 結果 |
---|---|
bookstore | 選取 bookstore 元素的所有子節點。 |
/bookstore | 選取根元素 bookstore。注釋:假如路徑起始於正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! |
bookstore/book | 選取屬於 bookstore 的子元素的所有 book 元素。 |
//book | 選取所有 book 子元素,而不管它們在文檔中的位置。 |
bookstore//book | 選擇屬於 bookstore 元素的后代的所有 book 元素,而不管它們位於 bookstore 之下的什么位置。 |
//@lang | 選取名為 lang 的所有屬性。 |
謂語(Predicates)
謂語用來查找某個特定的節點或者包含某個指定的值的節點,被嵌在方括號中。
在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:
路徑表達式 | 結果 |
---|---|
/bookstore/book[1] | 選取屬於 bookstore 子元素的第一個 book 元素。 |
/bookstore/book[last()] | 選取屬於 bookstore 子元素的最后一個 book 元素。 |
/bookstore/book[last()-1] | 選取屬於 bookstore 子元素的倒數第二個 book 元素。 |
/bookstore/book[position()<3] | 選取最前面的兩個屬於 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 選取所有擁有名為 lang 的屬性的 title 元素。 |
//title[@lang=’eng’] | 選取所有 title 元素,且這些元素擁有值為 eng 的 lang 屬性。 |
/bookstore/book[price>35.00] | 選取 bookstore 元素的所有 book 元素,且其中的 price 元素的值須大於 35.00。 |
/bookstore/book[price>35.00]/title | 選取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值須大於 35.00。 |
選取未知節點
XPath 通配符可用來選取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素節點。 |
@* | 匹配任何屬性節點。 |
node() | 匹配任何類型的節點。 |
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
路徑表達式 | 結果 |
---|---|
/bookstore/* | 選取 bookstore 元素的所有子元素。 |
//* | 選取文檔中的所有元素。 |
//title[@*] | 選取所有帶有屬性的 title 元素。 |
選取若干路徑
通過在路徑表達式中使用“|”運算符,您可以選取若干個路徑。
實例
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
路徑表達式 | 結果 |
---|---|
//book/title | //book/price | 選取 book 元素的所有 title 和 price 元素。 |
//title | //price | 選取文檔中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 選取屬於 bookstore 元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素。 |
XPath的運算符
下面列出了可用在 XPath 表達式中的運算符:
這些就是XPath的語法內容,在運用到Python抓取時要先轉換為xml。
lxml 是 一個HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 數據。
lxml和正則一樣,也是用 C 實現的,是一款高性能的 Python HTML/XML 解析器,我們可以利用之前學習的XPath語法,來快速的定位特定元素以及節點信息。
lxml python 官方文檔:http://lxml.de/index.html
需要安裝C語言庫,可使用 pip 安裝:pip install lxml
(或通過wheel方式安裝)
初步使用
我們利用它來解析 HTML 代碼,簡單示例:
# lxml_test.py # 使用 lxml 的 etree 庫 from lxml import etree text = ''' <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此處缺少一個 </li> 閉合標簽 </ul> </div> '''
#利用etree.HTML,將字符串解析為HTML文檔 html = etree.HTML(text) # 按字符串序列化HTML文檔 result = etree.tostring(html) print(result)
輸出結果
<html><body> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> </body></html>
lxml 可以自動修正 html 代碼,例子里不僅補全了 li 標簽,還添加了 body,html 標簽。
文件讀取:
除了直接讀取字符串,lxml還支持從文件里讀取內容。我們新建一個hello.html文件:
<!-- hello.html --> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div>
再利用 etree.parse() 方法來讀取文件。
# lxml_parse.py
from lxml import etree
# 讀取外部文件 hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
輸出結果與之前相同:
<html><body> <div> <ul> <li class="item-0"><a href="link1.html">first item</a></li> <li class="item-1"><a href="link2.html">second item</a></li> <li class="item-inactive"><a href="link3.html">third item</a></li> <li class="item-1"><a href="link4.html">fourth item</a></li> <li class="item-0"><a href="link5.html">fifth item</a></li> </ul> </div> </body></html>
XPath實例測試
1. 獲取所有的 <li>
標簽
# xpath_li.py from lxml import etree html = etree.parse('hello.html') print type(html) # 顯示etree.parse() 返回類型 result = html.xpath('//li') print result # 打印<li>標簽的元素集合 print len(result) print type(result) print type(result[0])
輸出結果:
<type 'lxml.etree._ElementTree'> [<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>] 5 <type 'list'> <type 'lxml.etree._Element'>
2. 繼續獲取<li>
標簽的所有 class
屬性
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/@class') print result
輸出結果:
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
3. 繼續獲取<li>
標簽下href屬性
為 link1.html
的 <a>
標簽
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a[@href="link1.html"]') print result
運行結果
[<Element a at 0x10ffaae18>]
4. 獲取<li>
標簽下的所有 <span>
標簽
# xpath_li.py from lxml import etree html = etree.parse('hello.html') #result = html.xpath('//li/span') #注意這么寫是不對的: #因為 / 是用來獲取子元素的,而 <span> 並不是 <li> 的子元素,所以,要用雙斜杠 result = html.xpath('//li//span') print result
運行結果
[<Element span at 0x10d698e18>]
5. 獲取 <li>
標簽下的<a>
標簽里的所有 class
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li/a//@class') print result
運行結果
['blod']
6. 獲取最后一個 <li>
的 <a>
的 href
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//li[last()]/a/@href') # 謂語 [last()] 可以找到最后一個元素 print result
運行結果
['link5.html']
8. 獲取 class
值為 bold
的標簽名
# xpath_li.py from lxml import etree html = etree.parse('hello.html') result = html.xpath('//*[@class="bold"]') # tag方法可以獲取標簽名 print result[0].tag
運行結果
span
案例:使用XPath的的爬蟲
現在我們用的XPath來做一個簡單的爬蟲,我們嘗試爬取某個貼吧里的所有帖子,並且將該這個帖子里每個樓層發布的圖片下載到本地。
# tieba_xpath.py #!/usr/bin/env python # -*- coding:utf-8 -*- import os import urllib import urllib2 from lxml import etree class Spider: def __init__(self): self.tiebaName = raw_input("請需要訪問的貼吧:") self.beginPage = int(raw_input("請輸入起始頁:")) self.endPage = int(raw_input("請輸入終止頁:")) self.url = 'http://tieba.baidu.com/f' self.ua_header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1 Trident/5.0;"} # 圖片編號 self.userName = 1 def tiebaSpider(self): for page in range(self.beginPage, self.endPage + 1): pn = (page - 1) * 50 # page number word = {'pn' : pn, 'kw': self.tiebaName} word = urllib.urlencode(word) #轉換成url編碼格式(字符串) myUrl = self.url + "?" + word # 示例:http://tieba.baidu.com/f? kw=%E7%BE%8E%E5%A5%B3 & pn=50 # 調用 頁面處理函數 load_Page # 並且獲取頁面所有帖子鏈接, links = self.loadPage(myUrl) # urllib2_test3.py # 讀取頁面內容 def loadPage(self, url): req = urllib2.Request(url, headers = self.ua_header) html = urllib2.urlopen(req).read() # 解析html 為 HTML 文檔 selector=etree.HTML(html) #抓取當前頁面的所有帖子的url的后半部分,也就是帖子編號 # http://tieba.baidu.com/p/4884069807里的 “p/4884069807” links = selector.xpath('//div[@class="threadlist_lz clearfix"]/div/a/@href') # links 類型為 etreeElementString 列表 # 遍歷列表,並且合並成一個帖子地址,調用 圖片處理函數 loadImage for link in links: link = "http://tieba.baidu.com" + link self.loadImages(link) # 獲取圖片 def loadImages(self, link): req = urllib2.Request(link, headers = self.ua_header) html = urllib2.urlopen(req).read() selector = etree.HTML(html) # 獲取這個帖子里所有圖片的src路徑 imagesLinks = selector.xpath('//img[@class="BDE_Image"]/@src') # 依次取出圖片路徑,下載保存 for imagesLink in imagesLinks: self.writeImages(imagesLink) # 保存頁面內容 def writeImages(self, imagesLink): ''' 將 images 里的二進制內容存入到 userNname 文件中 ''' print imagesLink print "正在存儲文件 %d ..." % self.userName # 1. 打開文件,返回一個文件對象 file = open('./images/' + str(self.userName) + '.png', 'wb') # 2. 獲取圖片里的內容 images = urllib2.urlopen(imagesLink).read() # 3. 調用文件對象write() 方法,將page_html的內容寫入到文件里 file.write(images) # 4. 最后關閉文件 file.close() # 計數器自增1 self.userName += 1 # 模擬 main 函數 if __name__ == "__main__": # 首先創建爬蟲對象 mySpider = Spider() # 調用爬蟲對象的方法,開始工作 mySpider.tiebaSpider()
xpath解析式的使用的簡單學習就到這里,學好xpath解析式,就能快速的在頁面數據中獲取到自己想要的數據,節省開發時間,在爬蟲的開發過程中,數據解析式的確定往往才是最耗費時間的