第5章數據的檢索、加工與存儲node
現實中,各類形式的數據隨處可見。咱們不只能夠從網絡、電子郵件和FTP中取得數據,也可經過實驗研究或者市場調查來得到數據。要想全面總結不一樣格式數據的獲取方法,恐怕要佔用大量的篇幅,不是幾頁就能講全的。大部分狀況下,數據在分析以前或以後咱們都須要將其存儲起來。關於數據的存儲問題,本章也有討論。第8章將講解各類數據庫(關係數據庫和NoSQL數據庫)及其API的有關知識。本章探討的主題以下。python
5.1利用NumPy和pandas對CSV文件進行寫操做git
前幾章咱們學過讀取CSV文件的內容,其實,對CSV文件進行寫操做一樣也很簡單,只不過使用的函數和方法不一樣罷了。首先,生成一些數據,未來它們會以CSV格式保存。下面的代碼給隨機數生成器指定種子並生成一個3×4的NumPy數組。web
咱們將一個數組元素的值設爲NaN。正則表達式
1 import numpy as np 2 import pandas as pd 3 4 np.random.seed(42) 5 6 a = np.random.randn(3, 4) 7 a[2][2] = np.nan 8 print(a)
上述代碼打印輸出的數組以下。數據庫
[[ 0.49671415 -0.1382643 0.64768854 1.52302986] [-0.23415337 -0.23413696 1.57921282 0.76743473] [-0.46947439 0.54256004 nan -0.46572975]]
NumPy的savetxt()函數是與loadtxt()相對應的一個函數,它能以諸如CSV之類的區隔型文件格式保存數組。下面的代碼可用來保存剛建立的那個數組。編程
1 np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header=" #1, #2, #3, #4")
上面的函數調用中,咱們規定了用以保存數組的文件的名稱、數組、可選格式、間隔符(默認爲空格符)和一個可選的標題。json
Tips:有關格式參數的詳細說明,請參考Python的官方文檔。api
經過編輯器,例如在Windows系統上的Notepad或者cat命令,即cat np.csv,咱們能夠查看剛纔所建的np.csv文件的具體內容,具體以下。
利用隨機數組來建立Pandas DataFrame,代碼以下。
1 df = pd.DataFrame(a) 2 print(df)
就像你所看到的那樣,Pandas會自動替咱們給數據取好列名。
0 1 2 3
0 0.496714 -0.138264 0.647689 1.523030
1 -0.234153 -0.234137 1.579213 0.767435
2 -0.469474 0.542560 NaN -0.465730
利用Pandas的to_csv()方法能夠爲CSV文件生成一個DataFrame,代碼以下。
1 df.to_csv('pd.csv', float_format='%.2f', na_rep="NAN!")
對於這個方法,咱們須要提供文件名、相似於NumPy的savetxt()函數的格式化參數的可選格式串和一個表示NaN的可選字符串。Pd.csv 文件的內容以下。
下面的代碼引自本書代碼包中的ch-05.ipynb文件。
1 import numpy as np 2 import pandas as pd 3 4 np.random.seed(42) 5 6 a = np.random.randn(3, 4) 7 a[2][2] = np.nan 8 print(a) 9 np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header=" #1, #2, #3, #4") 10 df = pd.DataFrame(a) 11 print(df) 12 df.to_csv('pd.csv', float_format='%.2f', na_rep="NAN!")
5.2二進制.npy與pickle格式
大部分狀況下,用CSV格式來保存文件是一個不錯的主意,由於大部分程序設計語言和應用程序都能處理這種格式,交流起來很是方便。然而這種格式有一個缺陷,就是它的存儲效率不是很高,緣由是CSV及其餘純文本格式中含有大量空白符。然後來發明的一些文件格式,如zip、bzip和gzip等,壓縮率則有了顯著提高。
如下代碼引自本書代碼包中的ch-05.ipynb文件,它對各類格式的存儲利用狀況進行了比較。
1 import numpy as np 2 import pandas as pd 3 from tempfile import NamedTemporaryFile 4 from os.path import getsize 5 6 np.random.seed(42) 7 a = np.random.randn(365, 4) 8 9 tmpf = NamedTemporaryFile() 10 np.savetxt(tmpf, a, delimiter=',') 11 print("Size CSV file", getsize(tmpf.name)) 12 13 tmpf = NamedTemporaryFile() 14 np.save(tmpf, a) 15 tmpf.seek(0) 16 loaded = np.load(tmpf) 17 print("Shape", loaded.shape) 18 print("Size .npy file", getsize(tmpf.name)) 19 20 df = pd.DataFrame(a) 21 df.to_pickle(tmpf.name) 22 print("Size pickled dataframe", getsize(tmpf.name)) 23 print("DF from pickle\n", pd.read_pickle(tmpf.name))
NumPy爲本身提供了一種專用的格式,稱爲.npy,能夠用於存儲NumPy數組。在進一步說明這種格式以前,咱們先來生成一個365×4的NumPy數組並給各個元素填充上隨機值。這個數組能夠當作是一年中4個隨機變量的每日觀測值的模擬,如一個氣象站內傳感器測到的溫度、溼度、降雨量和睦壓讀數。這裏,咱們將使用Python標準的NamedTemporaryFile來存儲數據,這些臨時文件隨後會自動刪除。
下面將該數組存入一個CSV文件並檢查其大小,代碼以下。
1 tmpf = NamedTemporaryFile() 2 np.savetxt(tmpf, a, delimiter=',') 3 print("Size CSV file", getsize(tmpf.name))
這個CSV文件的大小以下。
Size CSV file 32616
首先以NumPy.npy格式來保存該數組,隨後載入內存並檢查數組的形狀以及該.npy文件的大小,具體代碼以下。
1 tmpf = NamedTemporaryFile() 2 np.save(tmpf, a) 3 tmpf.seek(0) 4 loaded = np.load(tmpf) 5 print("Shape", loaded.shape) 6 print("Size .npy file", getsize(tmpf.name))
爲了模擬該臨時文件的關閉與從新打開過程,咱們在上述代碼中調用了seek()函數。數組的形狀以及文件大小以下。
Shape (365, 4)
Size .npy file 11808
不出所料,.npy文件的大小隻有CSV文件的1/3左右。實際上,利用Python能夠存儲任意複雜的數據結構,也能夠用序列化格式來存儲Pandas的DataFrame或者Series數據結構。
Tips:在Python中,pickle是將Python對象存儲到磁盤或其餘介質時採用的一種格式,這個格式化的過程叫做序列化(pickling)。以後咱們能夠從存儲器中重建該Python對象,這個逆過程稱爲反序列化(unpickling),詳情請參考Python官方文檔。序列化技術通過多年的發展,已經出現了多種pickle協議。固然,並不是全部的Python對象都可以序列化;不過,藉助諸如dill之類的模塊,能夠將更多種類的Python對象序列化。若有可能,最好使用cPickle模塊(標準Python發行版中都含有此模塊),由於它是由C語言編寫的,因此運行起來會更快一些。
首先用前面生成的NumPy數組建立一個DataFrame,接着用to_pickle()方法將其寫入一個pickle對象中,而後用read_pickle()函數從這個pickle對象中檢索該DataFrame。
1 df = pd.DataFrame(a) 2 df.to_pickle(tmpf.name) 3 print("Size pickled dataframe", getsize(tmpf.name)) 4 print("DF from pickle\n", pd.read_pickle(tmpf.name))
該DataFrame通過序列化後,尺寸略大於.npy文件,這一點咱們經過下列代碼進行確認。
Size pickled dataframe 12244 DF from pickle 0 1 2 3 0 0.496714 -0.138264 0.647689 1.523030 1 [TRUNCATED] 59 -2.025143 0.186454 -0.661786 0.852433 … … … … [365 rows x 4 columns]
注意:若是沒有temp權限的讀寫權限,會報錯:PermissionError: [Errno 13] Permission denied: 'd:\\Temp\\tmpbjsbk4qw',若是要避免這個錯誤,能夠不使用temp路徑,參見下例。
5.3使用PyTables存儲數據
層次數據格式(Hierarchical Data Format,HDF)是一種存儲大型數值數據的技術規範,起源於超級計算社區,目前已經成爲一種開放的標準。本書將使用HDF的最新版本,也就是HDF5,該版本僅僅經過組(group)和數據集(dataset)這兩種基本結構來組織數據。數據集能夠是同類型的多維數組,而組能夠用來存放其餘組或者數據集。也許讀者已經發現了,這裏的「組」跟層次式文件系統中的「目錄」很是像。
HDF5最多見的兩個主要Python程序庫以下。
本例中使用的是PyTables。不過,這個程序庫須要用到的一些依賴項以下。
Tips:若是使用HDF5的並行版本,則須要安裝MPI。HDF5能夠從網站下載,而後運行下列命令進行安裝。這個安裝過程可能須要幾分鐘的時間。具體命令以下。
1 $ gunzip < hdf5-X.Y.Z.tar.gz | tar xf – 2 $ cd hdf5-X.Y.Z 3 $ make 4 $ make install
通常狀況下,咱們使用的程序包管理器都會提供HDF5,不過咱們最好仍是選擇目前最新的穩定版本(stable version)。截至編寫本書期間爲止,最新的版本號是1.8.12。
據Numexpr自稱,它在某些方面的運算速度要比NumPy快得多,由於它支持多線程,並且本身的虛擬機是C語言實現的。此外,PyPi也提供了Numexpr和PyTables,因此咱們能夠利用pip命令來安裝它們,具體以下。
pip install Numexpr
pip install numexpr tables
pip install h5py
此外,咱們須要生成一些隨機數並用它們來給一個NumPy數組賦值。下面咱們建立一個HDF5文件並把這個NumPy數組掛載到根節點上,代碼以下。
1 tmpf = "pytable_demo.h5" 2 h5file = tables.open_file(tmpf, mode='w') 3 root = h5file.root 4 h5file.create_array(root, "array", a) 5 h5file.close()
讀取這個HDF5文件並顯示文件大小,代碼以下。
1 h5file = tables.open_file(tmpf, "r") 2 print(getsize(tmpf))
咱們看到,文件大小爲13 824。在讀取一個HDF5文件並得到該文件的句柄後,就能夠經過常規方式來遍歷文件,從而找到咱們所需的數據。由於這裏只有一個數據集,因此遍歷起來很是簡單。下面的代碼將經過iterNodes()和read()方法取回NumPy數組。
1 for node in h5file.root: 2 b = node.read() 3 print(type(b), b.shape) 4 5 h5file.close()
該數據集的形狀和類型果真不出咱們所料,顯示以下。
<class 'numpy.ndarray'> (365, 4)
如下代碼取自本書代碼包中的ch-05.ipynb文件。
1 import numpy as np 2 import tables 3 from os.path import getsize 4 import uuid 5 6 np.random.seed(42) 7 a = np.random.randn(365, 4) 8 9 tmpf = "pytable_demo.h5" 10 h5file = tables.open_file(tmpf, mode='w') 11 root = h5file.root 12 h5file.create_array(root, "array", a) 13 h5file.close() 14 15 # h5file = tables.open_file(tmpf.name, "r") 16 h5file = tables.open_file(tmpf, "r") 17 print(getsize(tmpf)) 18 19 for node in h5file.root: 20 b = node.read() 21 print(type(b), b.shape) 22 23 h5file.close()
5.4Pandas DataFrame與HDF5倉庫之間的讀寫操做
HDFStore類能夠看做是Pandas中負責HDF5數據處理部分的一種抽象。藉助一些隨機數據和臨時文件,咱們能夠很好地展現這個類的功能特性,具體步驟以下。
咱們將臨時文件的路徑傳遞給HDFStore的構造函數,而後建立一個倉庫。
1 tmpf ="pytable_df_demo2.h5" 2 store = pd.io.pytables.HDFStore(tmpf) 3 print(store)
上述代碼將打印輸出該倉庫的文件路徑及其內容,不過此刻它尚未任何內容。
<class 'pandas.io.pytables.HDFStore'> File path: pytable_df_demo2.h5
Empty
HDFStore提供了一個相似字典類型的接口,例如咱們能夠經過Pandas中DataFrame的查詢鍵來存儲數值。爲了將包含隨機數據的一個DataFrame存儲到HDFStore中,可使用下列代碼。
1 df = pd.DataFrame(a) 2 store['df'] = df 3 print(store)
如今,該倉庫存放了以下數據。
<class ‘pandas.io.pytables.HDFStore’> File path: pytable_df_demo.h5 Frame (shape->[365,4])
咱們能夠經過3種方式來訪問DataFrame,分別是使用get()方法訪問數據,利用相似字典的查詢鍵訪問數據,或者使用點運算符號來訪問數據。下面咱們分別進行演示。
1 print("Get", store.get('df').shape) 2 print("Lookup", store['df'].shape) 3 print( "Dotted", store.df.shape)
該DataFrame的形狀一樣也能夠經過3種不一樣的方式進行訪問。
Get (365, 4) Lookup (365, 4) Dotted (365, 4)
爲了刪除倉庫中的數據,咱們既可使用remove()方法,也可使用del運算符。固然,每一個數據項只能刪除一次。下面咱們從倉庫刪除DataFrame,具體代碼以下。
1 del store['df'] 2 print("After del\n", store)
這個倉庫再次變空。
After del <class ‘pandas.io.pytables.HDFStore’> File path: pytable_df_demo.h5 Empty
屬性is_open的做用是指出倉庫是否處於打開狀態。爲了關閉一個倉庫,能夠調用close()方法。下面代碼展現了關閉倉庫的方法並針對倉庫的狀態進行了相應的檢查。
1 print("Before close", store.is_open) 2 store.close() 3 print("After close", store.is_open)
一旦關閉,該倉庫就會退出打開狀態,顯示以下。
Before close True
After close False
爲讀寫HDF數據,Pandas還提供了兩種方法,一種是DataFrame的to_hdf()方法,另外一種是頂級的read_hdf()函數。下面示例代碼展現了調用to_hdf()方法讀取數據的過程。
1 df.to_hdf('test.h5', 'data', format='table') 2 print(pd.read_hdf('test.h5', 'data', where=['index>363']))
用於讀寫操做的應用程序接口的參數包括文件路徑、倉庫中組的標識符以及可選的格式串。這裏的格式有兩種,一種是固定格式,另外一種是表格格式。固定格式的優勢是速度要更快一些,缺點是沒法追加數據,也不能進行搜索。表格格式至關於PyTables的Table結構,能夠對數據進行搜索和選擇操做。下面是經過查詢DataFrame獲得的數據。
0 1 2 3 364 0.753342 0.381158 1.289753 0.673181
如下代碼引自本書代碼包中的ch-05.ipynb文件。
1 import numpy as np 2 import pandas as pd 3 4 np.random.seed(42) 5 a = np.random.randn(365, 4) 6 7 tmpf ="pytable_df_demo2.h5" 8 store = pd.io.pytables.HDFStore(tmpf) 9 print(store) 10 11 df = pd.DataFrame(a) 12 store['df'] = df 13 print(store) 14 15 print("Get", store.get('df').shape) 16 print("Lookup", store['df'].shape) 17 print( "Dotted", store.df.shape) 18 19 del store['df'] 20 print("After del\n", store) 21 22 print("Before close", store.is_open) 23 store.close() 24 print("After close", store.is_open) 25 26 df.to_hdf('test.h5', 'data', format='table') 27 print(pd.read_hdf('test.h5', 'data', where=['index>363']))
5.5使用Pandas讀寫Excel文件
現實生活中,許多重要數據都是以Excel文件的形式存放的。固然,若是須要,咱們也能夠將其轉換爲可移植性更高的諸如CSV之類的格式。不過,利用Python來操做Excel文件會更加方便。在Python的世界裏,爲實現同一目標的項目一般不止一個,如提供Excel I/O操做功能的項目就是如此。只要安裝了這些模塊,咱們就能讓Pandas具有讀寫Excel文件的能力。只是這些方面的說明文檔不是很完備,其緣由是Pandas依賴的這些項目每每各自爲戰而且發展極爲迅猛。這些Pandas程序包對於Excel文件也很挑剔,要求這些文件的後綴必須是.xls或者.xlsx;不然就會報錯。
ValueError: No engine for filetype: ‘’
好在這個問題很是容易解決,舉例來講,當建立一個臨時文件時,只提供合適的後綴便可。若是須要的多個模塊一個都沒有安裝的話,就會收到以下的錯誤信息。
ImportError: No module named openpyxl.workbook
只要用下面的命令安裝openpyxl,就能夠杜絕這樣的錯誤提示,具體命令以下。
$ pip3 install openpyxl xlsxwriter xlrd
模塊openpyxl源於PHPExcel,它提供了針對.xlsx文件的讀寫功能。
此外,模塊xlsxwriter也須要讀取.xlsx文件,而模塊xlrd能用來析取.xls和.xlsx文件中的數據。
Tips:關於padas讀取excel,能夠參考這裏http://www.javashuo.com/article/p-gtvbizaf-c.html
官方:https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_excel.html
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_excel.html#pandas.read_excel
下面咱們先來生成用於填充Pandas中DataFrame的隨機數,而後用這個DataFrame建立一個Excel文件,接着再用Excel文件重建DataFrame並經過mean()方法來計算其平均值。對於Excel文件的工做表,咱們既能夠爲其指定一個從0開始計數的索引,也能夠爲其規定一個名稱。如下代碼引自本書代碼包中的ch-05.ipynb文件。
1 import numpy as np 2 import pandas as pd 3 # from tempfile import NamedTemporaryFile 4 5 np.random.seed(42) 6 a = np.random.randn(365, 4) 7 8 tmpf = "excel_demo2.xlsx" 9 #NamedTemporaryFile(suffix='.xlsx') 10 11 df = pd.DataFrame(a) 12 print(tmpf) 13 print(df) 14 # df.to_excel(tmpf, sheet_name='Random Data',index=False) 15 with pd.ExcelWriter(tmpf) as writer: 16 df.to_excel(writer, sheet_name='Random_Data',index=False) 17 print("Means\n", pd.read_excel(tmpf, 'Random_Data').mean())
咱們經過to_excel()方法建立Excel文件,具體以下。
with pd.ExcelWriter(tmpf) as writer: df.to_excel(writer, sheet_name='Random_Data',index=False)
下面咱們使用頂級read_excel()函數來重建DataFrame,代碼以下。
1 print("Means\n", pd.read_excel(tmpf, 'Random_Data').mean())
下面咱們輸出平均值。
excel_demo2.xlsx ... [365 rows x 4 columns] Means 0 0.037860 1 0.024483 2 0.059836 3 0.058417 dtype: float64
5.6使用REST Web服務和JSON
表述性狀態轉移(Representational State Transfer,REST)Web服務採用的是REST架構風格。對於HTTP(S)來講,可使用GET、POST、PUT和DELETE方法,這些方法對應數據項的建立、請求、更新及刪除操做。
使用REST風格的API時,數據項是經過統一資源標識符(URI)進行標識的。雖然REST並不是官方標準,可是其應用極爲普遍,因此咱們必須對它進行深刻了解。Web服務常用JavaScript對象表示法(JavaScript Object Notation,JSON)來交換數據。使用這種格式時,數據會按照JavaScript表示法的要求進行加工。這種表示法相似於Python的列表和字典的語法。利用JSON,經過組合列表和字典,能夠定義任意複雜的數據。爲了解釋這一點,咱們將使用一個至關於字典類型的JSON字符串爲例進行說明,這個字符串用來提供某IP地址的地理位置信息。
{「country」:」Netherlands」,」dma_code」:」0」,」timezone」:」Europe\/Amsterdam 「,」area_code」:」0」,」ip」:」46.19.37.108」,」asn」:」AS196752」,」continent_cod E」:」EU」,」isp」:」Tilaa V.O.F.」,」longitude」:5.75,」latitude」:52.5,」country_ code」:」NL」,」country_code3」:」NLD」}
如下是引自ch-5.ipynb文件中的代碼。
1 import json 2 3 json_str = '{"country":"Netherlands","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75,"latitude":52.5,"country_code":"NL","country_code3":"NLD"}' 4 5 data = json.loads(json_str) 6 print("Country", data["country"]) 7 data["country"] = "Brazil" 8 print(json.dumps(data))
Python爲咱們提供了一個簡單易用的標準JSON API。下面咱們用loads()函數來解析JSON字符串。
1 data = json.loads(json_str)
下列代碼能夠用來訪問變量country的值。
1 Print 「Country」, data[「country」]
上述代碼的輸出結果以下。
Country Netherlands
咱們修改變量country的取值並利用該新JSON數據來建立一個字符串。
1 Data[「country」] = 「Brazil」 2 Printjson.dumps(data)
獲得的這個JSON的country變量具備一個新值。與字典相似,這裏數據項之間的順序是任意的。
Country Netherlands {"country": "Brazil", "dma_code": "0", "timezone": "Europe/Amsterdam", "area_code": "0", "ip": "46.19.37.108", "asn": "AS196752", "continent_code": "EU", "isp": "Tilaa V.O.F.", "longitude": 5.75, "latitude": 52.5, "country_code": "NL", "country_code3": "NLD"}
5.7使用Pandas讀寫JSON
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_json.html
利用上面例子中的JSON字符串,能夠垂手可得地建立一個pandas Series。這個pandas提供的read_json()函數,能夠用來建立pandas Series或者pandas DataFrame數據結構。
如下示例代碼引自本書代碼包中的ch-05.ipynb文件。
1 import pandas as pd 2 3 json_str = '{"country":"Netherlands","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75,"latitude":52.5,"country_code":"NL","country_code3":"NLD"}' 4 5 data = pd.read_json(json_str, typ='series') 6 print("Series\n", data) 7 8 data["country"] = "Brazil" 9 print("New Series\n", data.to_json())
調用read_json()函數時,既能夠向其傳遞一個JSON字符串,也能夠爲其指定一個JSON文件的路徑。上面的例子中,咱們是利用JSON字符串來建立pandas Series的。
1 Data = pd.read_json(json_str, typ=’series’) 2 Print(「Series\n」, data)
在獲得的Series中,鍵是按字母順序排列的。
Series country Netherlands dma_code 0 timezone Europe/Amsterdam area_code 0 ip 46.19.37.108 asn AS196752 continent_code EU isp Tilaa V.O.F. longitude 5.75 latitude 52.5 country_code NL country_code3 NLD dtype: object
咱們再次修改country的值並用to_json()方法將其從pandas Series轉換成JSON字符串。
1 Data[「country」] = 「Brazil」 2 Print(「New Series\n」, data.to_json())
在這個新JSON字符串中,鍵的順序被保留了下來,不過,country的值卻變了。
New Series {"country":"Brazil","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75,"latitude":52.5,"country_code":"NL","country_code3":"NLD"}
5.8解析RSS和Atom訂閱
簡易信息聚合(Really Simple Syndication,RSS)和Atom訂閱經常使用於訂閱博客和新聞。雖然這兩種訂閱的類型不一樣,卻都遵循發佈訂閱模式。好比,Packt出版公司網站就提供了書籍和文章方面的訂閱功能,只要用戶訂閱後,就能與網站內容的最新更新保持同步。在Python的feedparser模塊的幫助下,咱們無需瞭解相關的技術細節,就能處理RSS和Atom訂閱。要想安裝這個feedparser模塊,可使用下列pip命令。
$ pip3 install feedparser
解析完RSS文件後,就能夠經過句點來訪問基礎數據了。如下代碼將解析Packt Publishing網站的訂閱源並打印內容數量。
1 import feedparser as fp 2 3 # rss = fp.parse("http://www.packtpub.com/rss.xml") 4 rss = fp.parse("http://feed.cnblogs.com/blog/u/22045/rss/") 5 6 print("# Entries", len(rss.entries))
內容的數量以下,不過,每次運行程序時該數字均可能發生變化。
# Entries 10
這裏能夠顯示含有Python這個單詞的條目的標題和摘要,代碼以下。
1 for i, entry in enumerate(rss.entries): 2 if "數據分析" in entry.summary: 3 print(i, entry.title) 4 print(entry.summary)
就本次運行而言,結果以下。須要注意的是,運行該代碼時,內容會有所不一樣,若是過濾條件限制太嚴格,還可能一條符合要求的內容也沒有。
5.9使用Beautiful Soup解析HTML
超文本標記語言(Hypertext Markup Language,HTML)是用於建立網頁文檔的一種基礎性技術。HTML由各類元素組成,而這些元素由尖括號中的所謂標籤組成,如<html>。標籤一般是成對出現的,標籤對中的第1個標籤是開始標籤,第2個標籤是結束標籤,這些標籤以樹狀結構組織在一塊兒。HTML的相關規範草案於1991年由Berners-Lee公佈,當時僅包括18個HTML元素。HTML的正式定義出如今1993年,是由國際互聯網工程任務組(Internet Engineering Task Force,IETF)頒佈的。1995年,IETF發佈HTML 2.0標準;2013年,又發行了最新的HTML版本,即HTML5。與XHTML和XML比較,HTML不是一個很是嚴格的標準。
咱們知道,現代瀏覽器容錯性已經有了長足進步,另外一方面,這也爲Web頁面中不符合標準的非結構化數據的滋生提供了溫牀。咱們不只能夠將HTML視爲一個碩大的字符串,並且能夠運用正則表達式對其執行各類字符串操做。不過,這種方法僅適用於比較簡單的項目。
工做中,筆者曾經接觸過專業級別的網絡信息蒐集項目,經驗證實咱們須要更加先進的方法。在現實世界中,咱們有時須要以編程的方式來提交HTML表單,尤爲是在登陸、切換頁面和處理Cookie時。當咱們從網頁上抓取數據時,常見的問題是咱們沒法徹底控制所抓取的頁面,所以常常須要修改本身的代碼。此外,有些網站的全部者不喜歡別人以編程的方式訪問其內容,因此他們會處心積慮地設置各類障礙,有的甚至直接禁用這種訪問方式。考慮到這些因素,咱們應該優先考慮諸如REST API之類的信息蒐集方法。
若是結果只能經過抓取頁面的方式來蒐集信息,建議使用Python的Beautiful Soup API。這個應用程序接口不只能夠從HTML文件中抽取數據,同時還支持XML文件。對於新的項目來講,建議使用Beautiful Soup 4,由於Beautiful Soup 3目前已經中止開發了。咱們可使用以下命令來安裝Beautiful Soup 4。Easy_install命令的用法與此相似。
1 $ pip3 install beautifulsoup4 lxml
若是這種方法行不通,還能夠把本身的代碼跟Beautiful Soup直接封裝到一塊兒。爲了演示如何解析HTML,本書的代碼包中提供了一個名爲loremIpsum.html的頁面文件,該文件是網站http://loripsum.net/的生成程序製做的。此後,咱們對這個文件進行了一些修改。文件的內容取自公元前 1 世紀西塞羅的拉丁文做品,這是建立網站模型的一種慣用形式。圖5-1中顯示的是網頁的上面部分。
本例中用的工具是Beautiful Soup 4和Python常規的正則表達式程序庫。
如下代碼的做用是導入程序庫。
1 from bs4 import BeautifulSoup 2 import re
而後咱們打開HTML文件並新建一個BeautifulSoup對象,具體代碼以下。
1 soup = BeautifulSoup(open('loremIpsum.html'))
經過使用句點標記法,咱們能夠方便地訪問第一個<div>元素,這個元素的做用是組織元素並提供樣式。訪問第1個div元素的代碼以下。
1 print("First div\n", soup.div)
輸出內容是一個HTML片斷,其中能夠看到第1個<div>標籤及其所含內容。
First div <div class="tile"> <h4>Development</h4> 0.10.1 - July 2014<br/> </div>
Tips:對於這個div元素來講,其類屬性的值爲tile,這個屬性的做用是爲該元素指定CSS樣式。層疊樣式表(Cascading Style Sheets,CSS)是一種描述網頁元素樣式的語言。經過CSS類能夠方便地控制Web頁面的外觀,所以CSS規範的應用極爲普遍。有了CSS,咱們能夠方便地定義元素的佈局、字體和顏色,這對於內容和外觀的隔離幫助很大。而內容和外觀的隔離,又讓設計工做變得更加簡單、清晰。
咱們能夠像訪問字典那樣來訪問標籤的屬性,下面以輸出<div>標籤的類屬性的值爲例進行說明。
1 print(「First div class」, soup.div[‘class’])
咱們利用點號,能夠訪問任意深度的元素。下面以輸出首個<dfn>標籤中的文本爲例進行說明。
1 print(「First dfn text」, soup.dl.dt.dfn.text)
輸出的是一行拉丁文字(英文的意思是:Solisten, I pray)。
First dfn text Quareattende, quaeso
有時,咱們只對HTML頁面中的超連接感興趣,例如咱們可能只想知道哪些頁面具備外向連接。在HTML文檔中,連接是用<a>標籤訂義的,經過這個標籤的href屬性,就能找到外向連接的URL。BeautifulSoup類中有一個簡單易用的find_all()方法,後面將會常常用到;經過這個方法,咱們能夠找到文檔中全部的超連接。
1 for link in soup.find_all(‘a’): 2 print(「Link text」, link.string, 「URL」, link.get(‘href’))
這裏,咱們從文檔中找到了如下3個URL相同但文字相異的連接。
Link text loripsum.net URL http://loripsum.net/ Link text Poterat autem inpune; URL http://loripsum.net/ Link text Is es profecto tu. URL http://loripsum.net/
Find_all()方法就介紹到這裏,下面演示如何訪問全部<div>標籤中的內容。
1 # Omitting find_all 2 for i, div in enumerate(soup('div')): 3 print(i, div.contents)
屬性contents中存放的是一個HTML元素列表。
['\n', <h4>Development</h4>, '\n 0.10.1 - July 2014', <br/>, '\n'] ['\n', <h4>Official Release</h4>, '\n 0.10.0 June 2014', <br/>, '\n'] ['\n', <h4>Previous Release</h4>, '\n 0.09.1 June 2013', <br/>, '\n']
爲了便於查找,每一個標籤的ID都是惟一的。下面的代碼將選取ID爲official的那個<div>元素並打印輸出第3個元素。
1 #Div with id=official 2 official_div = soup.find_all("div", id="official") 3 print("Official Version", official_div[0].contents[2].strip())
許多頁面都是根據訪問者的輸入或者外部數據即時生成的,網上購物網站上的頁面尤爲如此。當咱們跟動態網站打交道時,必須牢記全部標籤的屬性值隨時均可能發生變化。對於大型網站來講,自動生成的ID會產生一個大型的字母數字字符串。所以,這時最好不要使用徹底匹配方式進行查找,而要使用正則表達式。下面介紹如何經過模式匹配進行查找。上面的代碼片斷輸出的內容是在某個網站上查找一款軟件產品時所返回的版本號和月份。
Official Version 0.10.0 June 2014
咱們知道,class是Python編程語言中的一個關鍵字。爲查詢標籤的類屬性,必須使用class_做爲匹配符。下面展現如何求已經定義了類屬性的<div>標籤的數量。
1 print("# elements with class", len(soup.find_all(class_=True)))
如你指望的那樣,咱們找到了3個標籤。
# elements with class 3
下面計算帶有「tile」類的<div>標籤的數目。
1 tile_class = soup.find_all("div", class_="tile") 2 print("# Tile classes", len(tile_class))
實際上,文檔中存在兩個含有tile類的<div>標籤以及1個含有notile類的<div>標籤,所以有以下寫法。
# Tile classes 2
下面定義一個匹配全部<div>標籤的正則表達式。
1 Print(「# Divs with class containing tile」, len(soup.find_all(「div」, 2 Class_=re.compile(「tile」))))
此次找到了3個符合要求的標籤。
# Divs with class containing tile 3
使用CSS時,能夠利用模式來匹配文檔中的元素,這些模式一般稱爲CSS選擇器。咱們能夠利用BeautifulSoup類提供的CSS選擇器來選擇頁面元素。經過select()函數,咱們能夠匹配帶有notile類的<div>元素以下。
1 Print(「Using CSS selector\n」, soup.select(‘div.notile’))
下面是輸出內容。
Using CSS selector [<div class=」notile」> <h4>Previous Release</h4> 0.09.1 June 2013<br/> </div>]
HTML有序列表看起來與項目編號列表很是接近,由一個<ol>標籤和若干<li>標籤組成,其中每一個列表項對應一個<li>標籤。就像Python的列表同樣,select()函數返回的內容也能夠進行切分。圖5-2展現了有序列表。
下面的代碼將選擇有序列表中的前兩項內容。
1 Print(「Selecting ordered list list items\n」, soup.select(「ol > li」)[:2])
這兩個列表項的內容以下。
Selecting ordered list list items [<li>Cur id non ita fit?</li>, <li>In qua si nihil est praeter Rationem, sit in una virtute finis bonorum;</li>]
利用CSS選擇器的袖珍型語言,能夠選擇第二個列表項。注意,這裏是從⒈開始算起的。具體代碼以下。
1 Print(「Second list item in ordered list」, soup.select(「ol>li:nth-of-type(2)」))
下面是列表中的第二項內容,翻譯成英語,大意是「In which, if there is nothing contrary to reason, let him be the power of the end of the good things in one」。
Second list item in ordered list [<li>In qua si nihil est praeter Rationem, sit in una virtute finis bonorum;</li>]
當用瀏覽器閱覽頁面時,能夠經過特定的正則表達式來檢索匹配的文本節點。如下代碼展現瞭如何藉助text屬性來找出全部包含字符串「2014」的文本節點。
1 Print(「Searching for text string」, soup.find_all(text=re.compile(「2014」)))
獲得的文本節點以下。
Searching for text string [u’\n 0.10.1 – July 2014’, u’\n 0.10.0 June 2014’]
上面對BeautifulSoup類的功能作了簡要介紹,此外,Beautiful Soup還能用於修改HTML和XML文檔。它不只可以用來排錯,還能美化打印效果以及處理不一樣的字符集。下面的示例代碼引自ch-05.ipynb文件。
1 from bs4 import BeautifulSoup 2 import re 3 4 soup = BeautifulSoup(open('loremIpsum.html'),"lxml") 5 6 print("First div\n", soup.div) 7 print("First div class", soup.div['class']) 8 9 print("First dfn text", soup.dl.dt.dfn.text) 10 11 for link in soup.find_all('a'): 12 print("Link text", link.string, "URL", link.get('href')) 13 14 # Omitting find_all 15 for i, div in enumerate(soup('div')): 16 print(i, div.contents) 17 18 19 #Div with id=official 20 official_div = soup.find_all("div", id="official") 21 print("Official Version", official_div[0].contents[2].strip()) 22 23 print("# elements with class", len(soup.find_all(class_=True))) 24 25 tile_class = soup.find_all("div", class_="tile") 26 print("# Tile classes", len(tile_class)) 27 28 print("# Divs with class containing tile", len(soup.find_all("div", class_=re.compile("tile")))) 29 30 print("Using CSS selector\n", soup.select('div.notile')) 31 print("Selecting ordered list list items\n", soup.select("ol > li")[:2]) 32 print("Second list item in ordered list", soup.select("ol > li:nth-of-type(2)")) 33 34 print("Searching for text string", soup.find_all(text=re.compile("2014")))
咱們鼓勵讀者閱讀5.11節列出的參考資料,以便進一步深刻學習Beautiful Soup的高級功能,例如搜索返回節點的子節點,獲取返回節點的第n個父節點,獲取返回節點的第n個兄弟節點以及其餘高級功能。
5.10小結
本章介紹了檢索、加工與存儲不一樣格式數據的方法。這些格式包括CSV、NumPy .npy、Python pickle、JSON、RSS和HTML等格式。其中,咱們用到了NumPy pandas、JSON、feedparser以及Beautiful Soup等程序庫。
第6章將爲讀者講解利用Python顯實數據可視化的重要主題。分析數據時,可視化是一種比較常見的任務。它可以展示數據中各個變量之間的關係。經過數據可視化技術,還能夠形象地展現出數據的統計特性。
第5章完。
隨書源碼官方下載:
https://www.ptpress.com.cn/shopping/buy?bookId=bae24ecb-a1a1-41c7-be7c-d913b163c111
須要登陸後免費下載。