第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
须要登陆后免费下载。