Python数据分析与数据化运营
上QQ阅读APP看书,第一时间看更新

2.2 使用Python获取运营数据

使用Python获取数据,目前主要的方法集中在文本文件、Excel文件、关系型和非关系型数据库、API、网页等方面。本节就来介绍具体获取方法。注意,本章的示例代码中,默认Python的工作目录与数据文件所在位置相同,即都是在“附件-chapter2”。如果Python的工作目录不在相同目录下,请先在PyCharm中的Python Console中使用cd方法切换到相同目录;或者直接修改示例代码中的文件为实际路径即可。

2.2.1 从文本文件读取运营数据

1.使用read、readline、readlines读取数据

Python可以读取任意格式的文本数据,使用Python读取文本数据的基本步骤是:

1)定义数据文件;

2)获取文件对象;

3)读取文件内容;

4)关闭文件对象。

(1)定义数据文件

定义数据文件即定义要读取的文件,该步骤不是必须的,可以跟“获取文件对象”整合。但为了后续操作的便捷性、全局数据对象的可维护性以及减少代码冗余,建议读者养成习惯,将数据文件预先赋值给一个对象。

定义文本数据文件的方法是:

        file_name = [文件名称]

示例

        file_name = 'd:/python_data/data/text.txt'

文件名称中可以只写文件名,此时默认Python读取当前工作目录下的文件;也可以加入路径,默认使用斜杠,尤其是Windows下要注意用法。

(2)获取文件对象

获取文件对象的意义是基于数据文件产生对象,后续所有关于该数据文件的操作都基于该对象产生。

语法:

        file object = open(name [, mode][, buffering])

参数:

❑ name:要读取的文件名称,即上一个环节定义的file_name,必填。

❑ mode:打开文件的模式,选填,在实际应用中,r、r+、w、w+、a、a+是使用最多的模式。完整mode模式如表2-1所示。

表2-1 Python文件打开模式(mode)

❑ buffering:文件所需的缓冲区大小,选填;0表示无缓冲,1表示线路缓冲。

返回:通过open函数会创建一个文件对象(file object)。

示例:

        file_name = 'text.txt'
        file_object = open(file_name)

在“定义数据文件”部分提到可以将前两个环节结合起来,定义的语法是:

        file_object = open('text.txt')

(3)读取文件内容

Python基于文件对象的读取分为3种方法,如表2-2所示。

表2-2 Python读取文件内容的3种方法

示例,在“附件-chapter2”文件夹中有一个名为“text.txt”的数据文件,其中包含2个文本行,数据内容如图2-2所示。

图2-2 示例数据源文件内容

❑ 通过read方法读取该数据文件得到的数据结果:

            'line1: This is line1\nline2: This is line2'

❑ 通过readline方法读取该数据文件得到的数据结果:

            'line1: This is line1\n'

❑ 通过readlines方法读取该数据文件得到的数据结果:

            ['line1: This is line1\n', 'line2: This is line2

在实际应用中,read方法和readlines方法比较常用,而且二者都能读取全部文件中的数据。二者的区别只是返回的数据类型不同,前者返回字符串,适用于所有行都是完整句子的文本文件,例如大段文字信息;后者返回列表,适用于每行是一个单独的数据记录,例如日志信息。不同的读取方法会直接影响后续基于内容的处理应用;readline由于每次只读取一行数据,因此通常需要配合seek、next等指针操作才能完整遍历读取所有数据记录。

相关知识点:指针

Python文件操作中的指针类似于Word操作中的光标,指针所处的位置就是光标的位置,它决定了Python的读写从哪里开始。默认情况下,当通过open函数打开文件时,文件的指针处于第一个对象的位置。因此,在上述通过readline读取文件内容时,获取的是第一行数据。仍然是上面示例中的数据文件,我们通过如下代码演示基于不同指针位置读取的内容:

        fn = open('text.txt')# 获得文件对象
        print (fn.tell())# 输出指针位置
        line1 = fn.readline()# 获得文件第一行数据
        print (line1) # 输出第一行数据
        print (fn.tell())# 输出指针位置
        line2 = fn.readline()# 获得文件第二行数据
        print (line2)# 输出第二行数据
        print (fn.tell())# 输出指针位置
        fn.close() # 关闭文件对象

执行上述代码后,返回如下信息:

        0
        line1: This is line1
        22
        line2: This is line2
        42

从返回结果看:当打开文件时,文件指针的位置处于文件开头,输出为0;当读取完第一行之后,文件指针位置处于第一行行末,位置是第22个字符后面(也就是’\n’后面,'\n'是换行符);当读取完第二行(最后一行)之后,文件指针位置处于第二行行末,位置是第42个字符后面(注意:由于是最后一行,没有换行符’\n')。

(4)关闭文件对象

每次使用完数据对象之后,需要关闭数据对象。方法是file_object.close()。

提示

理论上,Python可以读取任意格式的文件,但在这里通常以读取格式化的文本数据文件为主,其中包括txt、csv、tsv等格式的文件,以及有固定分隔符分隔并以通用数据编码和字符集编码(例如utf8、ASCII、GB2312等)存放的无扩展名格式的数据文件。在2.3节中我们会介绍使用Python读取非结构化数据的方法。

2.使用Numpy的loadtxt、load、fromfile读取数据

Numpy读取数据的方法包括loadtxt、load和fromfile等3种,概要描述如表2-3所示。

表2-3 Numpy读取文件的3种方法

(1)使用loadtxt方法读取数据文件

Numpy可以读取txt格式的数据文件,数据通常都是一维或二维的。

语法:

        loadtxt(fname,  dtype=<type  'float'>,  comments='#',  delimiter=None,  converters=
      None, skiprows=0, usecols=None, unpack=False, ndmin=0)

参数:

❑ fname:文件或字符串,必填,这里指定要读取的文件名称或字符串,支持压缩的数据文件,包括gz和bz格式。

❑ dtype:数据类型,选填,默认为float(64位双精度浮点数)。Numpy常用类型如表2-4所示。

表2-4 Numpy数据类型

❑ comments:字符串或字符串组成的列表,是表示注释字符集开始的标志,选填,默认为#。

❑ delimiter:字符串,选填,用来分割多个列的分隔符,例如逗号、TAB符,默认值为空格。

❑ converters:字典,选填,用来将特定列的数据转换为字典中对应的函数的浮点型数据,例如将空值转换为0,默认为空。

❑ skiprows:跳过特定行数据,选填,用来跳过特定前N条记录,例如跳过前1行(可能是标题或注释),默认为0。

❑ usecols:元组,选填,用来指定要读取数据的列,第一列为0,以此类推,例如(1,3,5),默认为空。

❑ unpack:布尔型,选填,用来指定是否转置数组,如果为真则转置,默认为False。

❑ ndmin:整数型,选填,用来指定返回的数组至少包含特定维度的数组,值域为0/1/2,默认为0。

返回:从文件中读取的数组。

示例:在“附件-chapter2”文件夹中有一个名为numpy_data.txt的数据文件,数据为3行5列的矩阵,数据内容如图2-3所示,该示例通过loadtxt方法读取其中的数据。

图2-3 示例numpy数据源文件内容

        import numpy as np  # 导入Numpy库
        file_name = 'numpy_data.txt'  # 定义数据文件
        data = np.loadtxt(file_name, dtype='float32', delimiter=' ')  # 获取数据
        print (data)  # 打印数据

上述代码输出的结果如下:

        [[  0.   1.   2.   3.   4.]
        [  5.   6.   7.   8.   9.]
        [ 10.  11.  12.  13.  14.]]

(2)使用load方法读取数据文件

使用Numpy的load方法可以读取Numpy专用的二进制数据文件,从npy、npz或pickled文件加载数组或pickled对象,该文件通常基于Numpy的save或savez等方法产生。

语法:

        load(file, mmap_mode=None, allow_pickle=True, fix_imports=True, encoding='ASCII')

参数:

❑ file:类文件对象或字符串格式,要读取的文件或字符串,必填,类文件对象需要支持seek()和read()方法。

❑ mmap_mode:内存映射模式,值域为None、'r+'、'r'、'w+'、'c',选填。

❑ allow_pickle:布尔型,选填,决定是否允许加载存储在npy文件中的pickled对象数组,默认值为True。

❑ fix_imports:布尔型,选填,如果为True, pickle将尝试将旧的Python 2名称映射到Python 3中并使用新名称,仅在Python 2生成的pickled文件加载Python 3时才有用,默认值为True。

❑ encoding:字符串,决定读取Python 2字符串时使用何种编码,选填。

返回:从数据文件中读取的数组、元组、字典等。

示例:我们将在这个示例中先定义一份数据,然后保存为.npy格式的数据文件(该文件也在“附件-chapter2”中,名为load_data.npy),接着使用Numpy的load方法读取并打印输出。代码如下:

        import numpy as np # 导入nump库
        write_data = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])# 定义要存储的数据
        np.save('load_data', write_data) # 保存为npy数据文件
        read_data = np.load('load_data.npy') #读取npy文件
        print (read_data)# 输出读取的数据

上述代码输出的结果如下:

        [[ 1  2  3  4]
        [ 5  6  7  8]
        [ 9 10 11 12]]

(3)使用fromfile方法读取数据文件

使用Numpy的fromfile方法可以读取简单的文本文件数据以及二进制数据。通常情况下,该方法读取的数据来源于Numpy的tofile方法,即通过Numpy的tofile方法将特定数据保存为文件(默认为二进制数据文件,无论文件扩展名如何定义),然后通过fromfile方法读取该二进制文件。

语法:

        fromfile(file, dtype=float, count=-1, sep='')

参数:

❑ file:文件或字符串。

❑ dtype:数据类型,具体参照表2-3。注意数据类型要与文件存储的类型一致。

❑ count:整数型,读取数据的数量,-1意味着读取所有数据。

❑ sep:字符串,如果file是一个文本文件,那么该值就是数据间的分隔符。如果为空("")则意味着file是一个二进制文件,多个空格将按照一个空格处理。

返回:从文件中读取的数据。

示例:我们仍然以“附件-chapter2”文件夹中numpy_data.txt数据文件为例,首先通过tofile方法创建1个二进制文件,然后对该文件进行读取。

        import numpy as np # 导入Numpy库
        file_name = 'numpy_data.txt' # 定义数据文件
        data = np.loadtxt(file_name, dtype='float32', delimiter=' ') # 获取数据
        tofile_name = 'binary' # 定义导出二进制文件名
        data.tofile(tofile_name) # 导出二进制文件
        fromfile_data = np.fromfile(tofile_name, dtype='float32') # 读取二进制文件
        print (fromfile_data) # 打印数据

上述代码输出的结果如下:

        [  0.   1.   2.   3.   4.   5.   6.   7.   8.   9.  10.  11.  12.  13.  14.]

注意

请务必确保读入文件跟存储文件时的数据类型一致,否则将导致数据报错。有兴趣的读者在使用fromfile导入数据时,可不指定float32格式,看下输出结果。另外,由于使用tofile方法保存的数据会丢失数据形状信息,因此导入时无法重现原始数据矩阵。

3.使用Pandas的read_csv、read_fwf、read_table读取数据

相对于Python默认函数以及Numpy读取文件的方法,Pandas读取数据的方法更加丰富。Pandas读取文本文件数据的常用方法如表2-5所示。

表2-5 Pandas读取数据的常用方法

(1)使用read_cvs方法读取数据

通过read_csv方法可以读取csv格式的数据文件。

语法:

        read_csv(filepath_or_buffer, sep=', ', delimiter=None, header='infer', names=None,
            index_col=None, usecols=None, **kwds)

参数(read_csv的参数众多,以下仅介绍最常用的几个参数);

❑ filepath_or_buffer:字符串,要读取的文件对象,必填。

❑ sep:字符串,分隔符号,选填,默认值为英文逗号’, '。

❑ names:类数组,列名,选填,默认值为空。

❑ skiprows:类字典或整数型,要跳过的行或行数,选填,默认为空。

❑ nrows:整数型,要读取的前记录总数,选填,默认为空,常用来在大型数据集下做初步探索之用。

❑ thousands:字符串,千位符符号,选填,默认为空。

❑ decimal:字符串,小数点符号,选填,默认为点(.),在特定情况下应用,例如欧洲的千位符和小数点跟中国相反,欧洲的“4.321,1”对应着中国的“4,321.1”。

返回:DataFrame或TextParser。

示例:我们以“附件-chapter2”文件夹中csv_data.csv数据文件为例,直接读取文件并打印输出。数据内容如图2-4所示。

图2-4 数据文件内容

        import pandas as pd  # 导入Pandas库
        csv_data  =  pd.read_csv('csv_data.csv',  names=['col1',  'col2',  'col3',  'col4',
            'col5'])  # 读取csv数据
        print (csv_data)  # 打印输出数据

上述代码输出的结果如下:

            col1  col2  col3  col4  col5
        0     0     1     2     3     4
        1     5     6     7     8     9
        2    10    11    12    13    14

(2)使用read_fwf方法读取数据

通过read_fwf方法可以读取表格或固定宽度格式的文本行到数据框。

语法:

        read_fwf(filepath_or_buffer, colspecs='infer', widths=None, **kwds)

参数:read_fwf跟read_csv一样都具有非常多的参数(只是在语法中前者通过**kwds省略了,查询完整语法请使用help(pd.read_fwf)),并且大多数参数的用法相同。除了read_csv中的常用参数外,以下仅介绍read_fwf特有且常用的参数。

❑ widths:由整数组成的列表,选填,如果间隔是连续的,可以使用的字段宽度列表而不是“colspecs”。

返回:DataFrame或TextParser。

示例:我们以“附件-chapter2”文件夹中fwf_data数据文件为例,直接读取文件并打印输出。数据内容如图2-5所示。

图2-5 数据文件内容

        import pandas as pd  # 导入Pandas库
        fwf_data = pd.read_fwf('fwf_data', widths=[5, 5, 5, 5], names=['col1', 'col2',
            'col3', 'col4'])  # 读取csv数据
        print (fwf_data)  # 打印输出数据

上述代码输出的结果如下:

            col1   col2   col3   col4
        0  a2331  a9013  a3211  a9981
        1  b4432  b3199  b9766  b2212
        2  c3294  c1099  c7631  c4302

(3)使用read_table方法读取数据

通过read_table方法可以读取通用分隔符分隔的数据文件到数据框。

语法:

        read_table(filepath_or_buffer,  sep='\t',  delimiter=None,  header='infer',  names=
            None, index_col=None, usecols=None, **kwds)

参数:对于read_table而言,参数与read_csv完全相同。其实read_csv本来就是read_table中分隔符是逗号的一个特例,表现在语法中是read_csv的sep=', '(默认)。因此,具体参数请查阅read_csv的参数部分。

返回:DataFrame或TextParser。

我们以“附件-chapter2”文件夹中table_data.txt数据文件为例,直接读取文件并打印输出。数据内容如图2-6所示。

图2-6 数据文件内容

        import pandas as pd  # 导入Pandas库
        table_data  =  pd.read_table('table_data.txt',  sep='; ',  names=['col1',  'col2',
            'col3', 'col4', 'col5'])  # 读取csv数据
        print (table_data)  # 打印输出数据

上述代码输出的结果如下:

            col1  col2  col3  col4  col5
        0     0     1     2     3     4
        1     5     6     7     8     9
        2    10    11    12    13    14

数据分割(或分列)常用思路分为两种:一种是基于固定宽度,一种是基于分割符号。上述三种方法中,常用的方法是第二和第三种,即read_fwf和read_table方法。

除了上述用于读取文本文件的方法外,Pandas还提供了非常丰富的用于其他场景的数据读取方法,限于篇幅,在此不做更多介绍,仅提供读取列表供有兴趣的读者参考,以及做知识延伸。具体如表2-6所示。

表2-6 Pandas其他数据读取方法

4.如何选择最佳读取数据的方法

关于“最佳”方法其实没有固定定义,因此所谓的最佳方法往往跟以下具体因素有关:

数据源情况:数据源中不同的字段类型首先会制约读取方法的选择,文本、数值、二进制数据都有各自的适应方法约束。

数据处理目标:读取数据往往是第一步,后续会涉及数据探索、预处理、统计分析等复杂过程,这些复杂过程需要用到哪些方法一定程度上都会受数据源的读取影响,影响最多的点包括格式转换、类型转换、异常值处理、分类汇总等。

模型数据要求:不同的模型对于数据格式的要求是不同的,应用到实际中不同的工具对于数据的表示方法也有所差异。

“手感”最好的方法:很多时候,最佳方法往往是对哪个或哪些方法最熟悉,这些所谓的“手感”最好的方法便是最佳方法。

当然,即使使用了不是“最佳”的数据读取方法也不必过于担心,因为Python强大的功能提供了众多可以相互进行转换的方法,从Python存储数据的基本对象类型来看,无非是字符串、列表、字典、元组、数组、矩阵等(当然,不同的库对于这些对象的定义名称可能有所不同,但基本含义相同),不同对象的类型转换都非常容易。但本着少走弯路的原则,在这里笔者还是针对不同的场景提供了较为适合的数据读取方法:

❑ 对于纯文本格式或非格式化、非结构化的数据,通常用于自然语言处理、非结构化文本解析、应用正则表达式等后续应用场景下,Python默认的三种方法更为适合。

❑ 对于结构化的、纯数值型的数据,并且主要用于矩阵计算、数据建模的,使用Numpy的loadtxt方法更为方便,例如本书中使用的sklearn本身就依赖于Numpy。

❑ 对于二进制的数据处理,使用Numpy的load和fromfile方法更为合适。

❑ 对于结构化的、探索性的数据统计和分析场景,使用Pandas方法进行读取效果更佳,因为其提供了类似于R的数据框,可以实现“仿SQL”式的操作方式,对数据进行任意翻转、切片(块等)、关联等都非常方便。

对于结构化的、数值型和文本型组合的数据统计分析场景,使用Pandas更为合适,因为每个数据框中几乎可以装载并处理任意格式的数据。

2.2.2 从Excel获取运营数据

现有的Excel分为两种格式:xls(Excel 97-2003)和xlsx(Excel 2007及以上)。

Python处理Excel文件主要是第三方模块库xlrd、xlwt、pyexcel-xls、xluntils和pyExcel-erator,以及win32com和openpyxl模块,此外Pandas中也带有可以读取Excel文件的模块(read_excel)。

基于扩展知识的目的,我们使用xlrd模块读取Excel数据。

首先安装该库,在系统终端命令行输入命令pip install xlrd。

然后我们以“附件-chapter2”文件夹中demo.xlsx数据文件为例,介绍该库的具体应用。数据概览如图2-7所示。

图2-7 数据文件内容

        import xlrd  # 导入库
        # 打开文件
        xlsx = xlrd.open_workbook('demo.xlsx')
        # 查看所有sheet列表
        print ('All sheets: %s' % xlsx.sheet_names())
        print ('===================================')  # 内容分割线
        # 查看sheet1的数据概况
        sheet1 = xlsx.sheets()[0]  # 获得第一张sheet,索引从0开始
        sheet1_name = sheet1.name  # 获得名称
        sheet1_cols = sheet1.ncols  # 获得列数
        sheet1_nrows = sheet1.nrows  # 获得行数
        print  ('Sheet1  Name:  %s\nSheet1  cols:  %s\nSheet1  rows:  %s')  %  (sheet1_name,
            sheet1_cols, sheet1_nrows)
        print ('===================================')  # 内容分割线
        # 查看sheet1的特定切片数据
        sheet1_nrows4 = sheet1.row_values(4)  # 获得第4行数据
        sheet1_cols2 = sheet1.col_values(2)  # 获得第2列数据
        cell23 = sheet1.row(2)[3].value  # 查看第3行第4列数据
        print  ('Row  4:  %s\nCol  2:  %s\nCell  1:  %s\n'  %  (sheet1_nrows4,  sheet1_cols2,
            cell23))
        print ('===================================')  # 内容分割线
        # 查看sheet1的数据明细
        for i in range(sheet1_nrows):  # 逐行打印sheet1数据
            print (sheet1.row_values(i))

上述代码中,我们先读取一个Excel文件,再查看所有sheet(工作簿)并输出sheet1相关属性信息;然后查看sheet1中特定数据行、列和元素的信息;最后用循环的方式,依次读取每个数据行并打印输出。

以下是代码执行后打印输出的结果:

        All sheets: [u'Sheet1']
        ===================================
        Sheet1 Name: Sheet1
        Sheet1 cols: 4
        Sheet1 rows: 10
        ===================================
        Row 4: [u'431381197408191515', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        Col 2:  [u'Create_Time',  42725.0,  42725.0,  42725.0,  42725.0,  42725.0,  42725.0,
            42725.0, 42725.0, 42725.0]
        Cell 1: 深圳市
        ===================================
        [u'ID_number', u'Status', u'Create_Time', u'Business_City']
        [u'431381198109106573', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381198809122734', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381197903117478', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381197408191515', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381197606166011', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'43138119850623339X', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381198908223477', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'431381198901176911', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']
        [u'43138119870827275X', u'\u6709\u6548', 42725.0, u'\u6df1\u5733\u5e02']

提示

在上述打印输出的内容中,我们发现第二列、第三列、第四列与原始数据不同。第二列和第四列出现“异常”的原因是将中文编码统一转换为了Unicode编码,便于在不同程序间调用;第三列出现“异常”是由于将日期格式转换为了数值格式。

上述操作只是将数据从Excel中读取出来,基于读取的数据转换为数组便可以进行矩阵计算。由于矩阵计算大多是基于数值型数据实现的,因此上述数据将无法适用于大多数科学计算场景,这点需要注意。

在企业实际场景中,由于Excel本身的限制和适用范围,其无法存储和计算过大(例如千万级的数据记录)的数据量,并且Excel本身也不是为了海量数据的应用而产生的。因此,Excel可用于日常基本数据处理、补充数据来源或者汇总数据,同时也可以作为数据结果展示的载体,这种应用对于大量数值表格的展现效果非常好。

2.2.3 从关系型数据库MySQL读取运营数据

在1.2.4节中我们介绍了安装MySQL和Navicat的方法和关键步骤。这里我们介绍如何利用Python从MySQL中读取数据。

在“附件-chapter2”文件夹中有一个名为order.xlsx的数据文件,我们先把该文件导入MySQL数据库,然后再基于数据库进行相关操作。

第一步 新建一个数据库用于存放要导入的目标数据。

步骤1打开Navicat,左侧导航栏中有我们已经建立好的数据库连接,在第1章中我们已经建立好了本地连接,名为“127.0.0.1”。

步骤2双击该数据库连接,会展示出所有当前连接(对应的用户权限)下可用的数据库,如图2-8所示。

图2-8 127.0.0.1下的数据库

步骤3为了保持不同数据的独立性,我们新建一个数据库,单独存放特定主题的数据。在“127.0.0.1”名称上右击,在弹出的菜单中选择,然后做如下设置并点击确定。完成之后在左侧的“127.0.0.1”连接下会新增一个刚才新建的数据库“python_data”。

第二步 将Excel数据导入数据库。

双击刚才新建的名为“python_data”的数据库名称,激活数据库连接。此时右侧功能栏中的部分功能已经可用。点击“导入向导”,开始导出Excel数据,如图2-10所示。

图2-10 创建导入向导

步骤1选择数据文件格式:

步骤2选择数据源文件。

步骤3定义附加选项。由于数据文件中第一行是表头,因此数据从第二行开始;日期分隔符与数据文件一致,需要设置为“-”,其他都为默认值。

图2-9 新建数据库设置

图2-11 设置导入数据源

步骤4设置目标数据表名,此处设置为order。

步骤5设置字段类型、长度、约束等信息。其中主要的设置是将order_data类型设置为date(日期型),长度为0(不做限制); order_tme类型设置为time(时间型),长度为0(不做限制); total_amount类型设置为float(浮点型),长度默认为255,如图2-13所示。

图2-13 设置字段类型等约束

图2-12 设置导入附件选项

提示

实际上,我们的数据很可能用不了那么多的“存储空间”,因此在设置时存在数据空间冗余,不过没关系,这里我们仅做入门引导之用。真正涉及数据库表的模型设计有很多学问,这里的设置不会影响我们做数据统计分析之用。

步骤6设置导入模式。由于我们是新创建表,因此设置为

步骤7启动导入任务,点击即可。

等待系统导入数据,导入成功后,会在窗口中显示图2-14所示信息,最终的信息中包含“successfully”。点击关闭按钮即可。

图2-14 导入成功提示信息

至此我们已经成功导入数据,在数据库python_data下新增了一个名为“order”的数据表。我们要初步验证导入的数据是否与原始数据一致。双击打开该表

打开表之后,会弹出信息提示该表没有主键。该信息不影响我们使用该表,点击确定即可,如图2-15所示。

图2-15 提示表没有主键

提示

在创建表时,我们完全可以指定主键,例如本案例中的order_id是唯一的,那么可指定该字段作为主键,更可以创建一个自增长ID作为主键。本节的主要内容是介绍如何导入外部数据到数据库。关于数据库建表还有很多话题,在此不进行更多介绍了。

打开order表之后,会显示图2-16所示预览内容。

图2-16 数据表概览

在当前窗口,点击“文件→查询表”或直接使用组合键Ctrl+Q,打开SQL窗口,然后输入SELECT * FROM `order` LIMIT 5;点击“运行”或使用组合键Ctrl+R,返回前5条结果,如图2-17所示。读者可以在此输入其他SQL语句或直接浏览数据表来核对数据导入是否准确。

图2-17 运行SQL并返回结果

注意

由于表名order是数据库中的排序关键字,因此在查询表时需要写为`order`,否则会报错。

导入Excel数据主要核对的信息点包括:

❑ 数据记录数是否一致:使用SELECT COUNT(*) FROM `order`;或在导入成功提示窗口也可以看到)。

❑ 数据字段数量是否一致,导入表中的字段不能多也不能少。

❑ 查看数据内容是否一致,尤其是日期、时间等非字符串字段的数据,很多时候时间型的数据转换会出现0000-00-00,这通常是导入设置的日期分隔符问题。

❑ 数据精准度是否一致,尤其是原始数据是浮点型,对应到数据库字段是否还是浮点型(注意小数位数),问题通常出现在字段类型和位数设置。

❑ 注意字段的最大长度,不同的数值型数据长度位数是不同,例如整数型tinyint、int,浮点型的单精度和双精度浮点等,这对于汇总级别的数据会产生极大影响(因为某些字段的汇总数据可能非常大)。

通过Python连接MySQL需要有Python库来建立连接,本节使用MySQL官方驱动连接程序,更多信息可在1.2.3节中的第5小节中找到。

以下是Python读取数据库数据的基本方法:

        import mysql.connector  # 导入库
        config = {'host': '127.0.0.1',  # 默认127.0.0.1
                    'user': 'root',  # 用户名
                    'password': '123456',  # 密码
                    'port': 3306,  # 端口,默认为3306
                    'database': 'python_data',  # 数据库名称
                    'charset': 'gb2312'  # 字符编码
                    }
        cnn = mysql.connector.connect(**config)  # 建立MySQL连接
        cursor = cnn.cursor()  # 获得游标
        sql = "SELECT * FROM `order`"  # SQL语句
        cursor.execute(sql)  # 执行SQL语句
        data = cursor.fetchall()  # 通过fetchall方法获得数据
        for i in data[:2]:  # 打印输出前2条数据
            print (i)
        cursor.close()  # 关闭游标
        cnn.close()  # 关闭连接

上述代码中,实现了通过Python连接MySQL查询所有的数据,并输出前2条数据的功能。执行结果如下:

        (u'3897894579',  datetime.date(2010,  3,  11),  datetime.timedelta(0),  u'PENDING_
            ORDER_CONFIRM', u'1038166', 59.0)
        (u'3897983041',  datetime.date(2010,  3,  11),  datetime.timedelta(0),  u'REMOVED',
            u'1041656', 19.9)

在应用MySQL时,除了查询所有数据外,更多时候我们还会应用更多SQL技巧来帮助我们快速找到目标数据并进行初步处理。以下是常用SQL语法。

1)只查询前N条数据而非全部数据行记录。

示例:只查询前100条数据记录。

        SELECT * FROM `order` LIMIT 100;

LIMIT为限制的数据记录数方法,语法为limit m, n,意思是从第m到第n-1条数据。m可省略,默认值从0开始,上述示例中就是从0开始,取100条数据。例如,要从第11条开始取,取10条可以写为

        SELECT * FROM `order` LIMIT 11, 10;

2)只查询特定列(而非全部列)数据。

示例:只查询total_amount和order_id两列数据。

        SELECT total_amount, order_id from `order`;

选择特定列只需将列名写到表达式中即可,多个列名用英文逗号分隔。另外,还可以使用“表名.字段名”的写法,例如上述表达式可以写成

        SELECT order.total_amount, order.order_id from `order`;

这种写法会出现在表达式中出现多个表的情况下,例如多表关联查询。

3)查询特定列去重后的数据。

示例:查询针对user_id去重后的数据。

        SELECT DISTINCT user_id FROM `order`;

关键词DISTINCT用于返回唯一不同的值,后面接字段名,即要去重的字段。如果后面接多个字段会默认对多个字段同时进行比较,只有多个字段完全相同时才会去重。DISTINCT常用来返回去重后的特定ID,或者与COUNT配合使用,查询特定数据记录的唯一数量。

4)查询带有1个条件的数据。

示例:查询total_amount<100的所有数据。

        SELECT * FROM `order` WHERE total_amount < 100;

WHERE是条件关键字,后面接具体条件。本示例中,由于total_amount为浮点型,因此直接可与数值型比较;如果是字符串型,则比较值需要加引号用来表示一致的字段类型。

5)查询带有多个条件(同时满足)的数据。

示例:查询total_amount<100且status为REMOVED的所有数据。

        SELECT * FROM `order` WHERE total_amount < 100 and `status` = 'REMOVED';

多个条件使用and表示“且”的关系,多个条件必须同时满足。MySQL中字段不区分大小写。由于status也是关键字,因此也需要使用`status`。

6)查询带有多个条件(满足任意一个)的数据。

示例:查询total_amount<100或status为REMOVED的所有数据。

        SELECT * FROM `order` WHERE total_amount < 100 or `status` = 'REMOVED';

多个条件使用or表示“或”的关系,多个条件满足任意一个条件即可。

7)查询特定条件值在某个值域范围内的数据。

示例:查询status的值为REMOVED或NO_PENDING_ACTION或PENDING_ORDER_CONFIRM。

        SELECT * FROM `order` WHERE `status` in ('REMOVED', 'NO_PENDING_ACTION', 'PENDING_
            ORDER_CONFIRM');

对于特定字段采用“穷举法”列出值域的方法,也可以使用or方法连接多个条件,但使用列表的穷举法会使得表达式更简单。

8)使用正则表达式查询具有复杂条件的数据。

示例:查询user_id以106开头且order_id包含04的所有订单数据。

        SELECT * FROM `order` WHERE user_id LIKE '103%' and order_id LIKE '%04%';

正则表达式通常用在复杂条件中,通过使用关键字LIKE来连接不同的正则语法,LIKE后面接具体的匹配模式,常用的匹配模式包括:

❑ 以特定字符开头:’字符串%',例如’ABD%’表示以ABD开头的所有匹配;

❑ 以特定字符结尾:'%字符串’,例如’% ABD’表示以ABD结尾的所有匹配;

❑ 包含特定字符:'%字符串%',例如’%ABD%’表示包含ABD的所有匹配。

关于正则表达式还有更多强大的语法规则,限于篇幅不再展开介绍。

9)将查询到的数据倒叙排列。

示例:将total_amount金额在10~100之间的所有数据按照订单ID倒叙排序。

        SELECT * FROM `order` WHERE 10 < total_amount < 100 ORDER BY order_id DESC;

在查询表达式ORDER BY的结尾,通过使用DESC来表示倒叙(逆序)排序;省略是默认使用ASC正序(顺序)排序。

10)查询指定列中不包含空值的所有数据。

示例:查询order_id不为空的所有数据。

        SELECT * FROM `order` WHERE order_id IS NOT NULL;

在MySQL中,IS NULL表示为空,IS NOT NULL表示非空。需要注意的是,NULL不代表空字符串或者空格,而是真正意义上的没有任何数据,类似于Python中的None。

上述案例只是展示了如何取数,更多情况下,我们会配合特定函数和方法做数据计算、整合和探索性分析,例如:

❑ 使用MySQL聚合函数求算术平均值、计数、求最大/最小值、做分类汇总等;

❑ 使用MySQL数学函数,用来求绝对值、进行对数计算、求平方根等;

❑ 使用MySQL字符串函数进行字符串分割、组合、截取、匹配、处理等;

❑ 使用MySQL的日期函数进行日期获取、转换、处理等;

❑ 使用MySQL将多个表(2个或2个以上)数据进行关联、匹配和整合。

在Python工作者看来,MySQL的很多工作Python本身也能胜任,为什么还要耗费时间和精力学习如何在MySQL中完成这些数据工作?直接用Python读取数据然后基于Python的相关库进行数据运算岂不更好?

Python的工作强项并不是数据计算,而是其灵活、高效、简易和集成多方的工作方式、效率和效果。MySQL(以及其他关系型数据库)作为成熟的数据存储和集成解决方案,在(关系型)数据本身方面更具优势,尤其是对于数据结构定义(实体、属性、关系)、关系操作(选择、连接、聚合等)、关系完整性约束(主外键、唯一性等)等方面具有成熟且稳定的应用价值,这对于数据处理至关重要,因此可以说MySQL在结构化数据存储和初步处理工作上比Python更专业。

还有一个更加关键的原因是,如果所有数据工作都由Python完成,那么Python的事务处理在某些情况下一定会面临资源瓶颈、工作效率等问题,届时可能会导致程序崩溃和报错,这将大大降低程序的可靠性以及结果输出的效率,对于海量数据工作更是如此。

因此,很多时候不是Python不能做,而是在合适的时机选择最合适的工具来完成才是最好的选择。

2.2.4 从非关系型数据库MongoDB读取运营数据

由于MongoDB一般都是企业级数据存储,因此这里我们使用在第1章已经安装过的SSH远程客户端SecureCRT。如果读者在虚拟机或本地安装了MongoDB,也可以使用,操作方法类似。

双击SecureCRT打开程序,点击顶部的“快速连接”按钮,新建连接,如图2-18所示。

图2-18 新建快速连接

然后根据服务器具体配置情况,配置图2-19所示信息,其中主机名和用户名需要单独配置,其他的根据实际情况询问IT或运维管理人员(本案例中都是默认值)。可勾选用于以后启动该程序时直接显示快捷入口。

图2-19 填写连接配置信息

点击连接之后,会弹出密码输入窗口,输入密码,可勾选用于以后无须重复输入密码,具体视公司安全规定执行,如图2-20所示。

图2-20 填写密码

如果服务器配置信息填写正确,连接服务器成功会显示如图2-21所示。

图2-21 成功连接服务器

要想通过Python调用MongoDB,需要先安装PyMongoDB,直接使用pip install pymongo即可实现。

注意

不同的服务器环境对于可访问外网的限制有差异,有的服务器可以访问外网,而有的服务器却不能。我们在第1章中介绍的大多数安装方法,默认都是通过pip请求外部网络资源实现的,对于无法直接从服务器连接外网的,请参照1.2.3节中介绍的方法,先将安装包和依赖包下载到本机,然后再复制到服务器上,再通过setup和pip命令安装。

使用Python连接MongoDB之前,需要服务器上的MongoDB处于启动状态。查看是否成功启动的方法是找一个可以直接连接或访问服务器的浏览器,直接输入“IP:端口”,如果出现“It looks like you are trying to access MongoDB over HTTP on the native driver port.”字样的提示,则说明已经正常启动和运行。例如:笔者的服务器上已经启动了该服务,在浏览器中输入http://10.66.202.134:27017/后提示如图2-22所示信息。

图2-22 MongoDB服务启动后的浏览器返回信息

提示

MongoDB也提供了Windows版本的程序,有兴趣的读者,可登录https://www.mongodb.com/download-center#community下载程序并进行本地部署安装。

要在服务器上进入Python环境,直接在终端窗口输入命令python。在Python命令行窗口中,通过如下方法调用MongoDB数据。

        from pymongo import MongoClient  # 导入库
        client = MongoClient()  # 建立连接
        client = MongoClient('10.66.202.134', 27017)  # 环境变量初始化
        db = client.test_py  # 选择test_py库
        orders = db.ordersets  # 选择orders集合
        terms  =  [{"user":  "tony",  "id":  "31020",  "age":  "30",  "products":  ["215120",
            "245101", "128410"], "date": "2017-04-06"},
                  {"user":  "lucy",  "id":  "32210",  "age":  "29",  "products":  ["541001",
                      "340740", "450111"],
                  "date": "2017-04-06"}]  # 定义一条数据集合用于插入
        orders.insert_many(terms)  # 插入数据
        print (orders.find_one())  # 获取一文档数据
        print ('========================================')
        for i in orders.find():  # 获取所有文档数据并展示
            print (i)

上述代码中,我们连接到MongoDB后选择了test_py库下的ordersets集合,然后插入2条数据到集合中,再从集合中查看单独一条以及所有集合数据。

以下是代码执行后打印输出的结果:

        {u'age': u'30', u'products': [u'215120', u'245101', u'128410'], u'user': u'tony',
            u'date': u'2017-04-06', u'_id': ObjectId('58eb1f0b2f76bf26108b4898'), u'id':
            u'31020'}
        ========================================
        {u'age': u'30', u'products': [u'215120', u'245101', u'128410'], u'user': u'tony',
            u'date': u'2017-04-06', u'_id': ObjectId('58eb1f0b2f76bf26108b4898'), u'id':
            u'31020'}
        {u'age': u'29', u'products': [u'541001', u'340740', u'450111'], u'user': u'lucy',
            u'date': u'2017-04-06', u'_id': ObjectId('58eb1f0b2f76bf26108b4899'), u'id':
            u'32210'}

除了上述基本查询语句外,PyMongo也提供了类似于MySQL一样的条件过滤。

1)查询特定文档数据。示例,只查询第2条数据。

        orders.find()[1] # 查询特定索引的数据记录

通过增加索引值可以指定查询特定索引的文档数据。注意索引值从0开始,0代表第一条是数据。

2)查询特定范围内的文档数据。示例,值查询第1~2条数据。

        orders.find()[0:2] # 查询特定范围内的数据记录

通过增加索引值可以指定查询特定索引范围的文档数据(切片)。注意索引值为空则默认从0开始,0代表第一条是数据。上述表达式可以写成orders.find()[:2]。默认不包含索引结束值,例如上述语法中的0:2的实际索引只有0和1。

3)增加查询条件。示例,只查询user为lucy的文档数据。

        orders.find({"user": "lucy"}) # 所有数据,注意使用迭代方法查看数据
        orders.find_one({"user": "lucy"}) # 单条数据

作为Key-Value形式的代表应用之一,MongoDB几乎所有的用法都以Key-Vaule的形式存在。过滤条件也不例外,只需在find函数中,以Key-Value的形式设置过滤条件即可,其中Key为过滤维度,Value为对应值。

4)排序。示例,针对user排序并输出。

        orders.find({"user": "lucy"}).sort("user") # 所有数据,注意使用迭代方法查看数据

通过使用Sort方法设置排序维度。默认是正序,也可以指定为倒叙排列,同时也可以指定多个字段排序规则,例如collection.find().sort([ ('field1', pymongo. ASCENDING), ('field2', pymongo.DESCENDING)])。从MongoDB 2.6开 始,还提供了按照文本相关性的排序方法,例如collection.find({'score': {'$meta':'textScore'}}).sort([('score', {'$meta': 'textScore'})])。

除了上述基本查询和过滤条件外,PyMongo也提供了用于做数据统计、分析和探索的基本方法,更多信息查阅http://api.mongodb.com/python/current/

在企业实际应用中,非关系型数据库往往基于“大数据”的场景产生,伴随着海量、实时、多类型等特征。这些数据库通过舍弃了关系型数据库的某些特征和约束,然后在特定方面进行增强,因此才能满足特定应用需求。非关系型数据库由于约束性、规范性、一致性和数据准确性低于关系性数据库,因此常用于实时海量数据读写、非结构化和半结构化信息读写、海量集群扩展、特殊场景应用等。所以在金融、保险、财务、银行等领域内,这种应用比较少;而互联网、移动应用等新兴产业和行业领域则应用较多。

2.2.5 从API获取运营数据

为了更好地让所有读者都能了解从API获取数据的具体过程,本节使用百度免费API作为实际数据来源。百度API提供了众多地图类功能,如基本地图、位置搜索、周边搜索、公交驾车导航、定位服务、地理编码及逆地理编码等。本节使用的是百度Web服务API中的Geocoding API。

Geocoding API用于提供从地址到经纬度坐标或者从经纬度坐标到地址的转换服务,用户可以发送请求且接收JSON、XML的返回数据。该应用可用于对运营数据中的地址相关信息进行解析,从而获得经纬度信息,这些信息可用于进一步基于地理位置进行解析、展示和分析等。

要获得该API,读者需要拥有百度相关账户和AK信息。

第一步 获得百度账户,没有账户的读者可在https://passport.baidu.com/v2/?reg处免费注册获取。

第二步 注册成为百度开放平台开发者,读者可进入http://lbsyun.baidu.com/apicon-sole/key?application=key完成相关注册。该过程非常简单,遵循引导整个过程在5分钟内即可完成,如图2-23所示。

图2-23 提交成功

第三步 注册完成之后,会有一个名为“【百度地图开放平台】开发者激活邮件”的验证链接发送到指定(注册时邮箱)邮箱,点击链接即可完成验证,如图2-24所示。

图2-24 激活验证链接

第四步 点击“申请秘钥”进入创建应用界面,在该应用创建中,我们主要使用Geocoding API v2,其他应用服务根据实际需求勾选。IP白名单区域,如果不做限制,请设置为“0.0.0.0/0”。设置完成后,点击提交,如图2-25所示。

图2-25 创建应用

第五步 获得AK秘钥。完成上述步骤之后,会默认跳转到应用列表界面,界面中的“访问应用(AK)”便是该应用的秘钥,如图2-26所示。

图2-26 获得AK秘钥

1.获取并解析JSON数据

我们先通过Python请求该API来获得JSON格式的数据。本示例的目标是通过给百度API发送一条地理位置数据,返回其经纬度信息。

本节会用到Python第三方库requests,读者需要先通过pip install requests进行安装。完整代码如下:

        import requests  # 导入库
        add = ’北京市中关村软件园’  # 定义地址
        ak = 'DdOyOKo0VZBgdDFQnyhINKYDGkzBkuQr'  # 创建访问应用时获得的AK
        url = 'http://api.map.baidu.com/geocoder/v2/? address=%s&output=xml&ak=%s'  # 请求URL
        res = requests.get(url % (add, ak))  # 获得返回请求
        add_info = res.text  # 返回文本信息
        print (add_info)  # 打印输出

在上述代码中,我们先导入一个requests库,该库用来发送网络请求,支持多种发送模式,可以用来做网页内容抓取、自动化网页测试、网页内容采集等。

第二行我们定义了一个地址,该地址通常是运营中的地址类信息,例如通信地址、注册地址、收货地址、联系地址等。

第三行ak是创建访问应用时获得的AK,注意百度每天有6000次的限制,读者可填写自行申请的AK。

第四行定义了一个通过get方法发送的URL请求地址,地址中的address和ak的具体值使用占位符代替,在后面用到时再具体赋值,这种方法经常用到地址、AK有变化的场景中。例如,我们通常会创建多个应用(基于每个账户有限制,以及分开治理应用的考虑),然后从数据库中读取一系列地址用于解析,此时的地址和AK都需动态赋值。

提示

在API请求方法中,最常用的是get和post方法。前者用于向服务器以“明文”(所有参数都在URL中体现)的形式请求数据,常用于普通的页面或服务请求,例如浏览网页、搜索关键字等都是get方法;后者则将“暗语”(所有的数据信息都在HTTP消息中)以键值对的形式发送,在URL中是看不到具体信息的,常用于数据保密性高的场景,例如登录、注册、订单等表单的处理都是post方法。

第五行通过get方法发送请求到上面定义的URL中,并具体赋值。

第六行将返回对象的文本信息赋值到add_info,返回对象中还包括非常多的相关属性信息,例如状态码、cookie、reason、headers、编码等,这些信息可用于判断是否正确返回、问题原因、状态等信息。

最后一行打印输出返回的文本信息。

以下是代码执行后打印输出的结果:

        {"st atus":0, "result":{"location":{"lng":116.29933765654373, "lat":40.053997302537
            42}, "precise":0, "confidence":40, "level":"工业园区"}}

返回结果包括以下字段:

❑ status:返回结果状态值,成功则返回0。

❑ location:经纬度坐标,其中lat是纬度值,lng是经度值。

❑ precise:位置的附加信息,决定是否精确查找。1为精确查找,即准确打点;0为不精确查找,即模糊打点。

❑ confidence:可信度,描述打点准确度。

❑ level:百度定义的地址类型。

该结果可以通过JSON进行格式化处理。

        import json  # 导入库
        add_json = json.loads(add_info)  # 加载JSON字符串对象
        lat_lng = add_json['result']['location']  # 获得经纬度信息
        print (lat_lng)  # 打印输出

代码中,我们导入Python自带的JSON库。该库支持Python其他对象和JSON对象之间的相互转换。

先将获得的地址信息格式转换为JSON字符串(字典类型),然后通过读取字典的Key来获得其Value,由于返回的地址信息中包含字典嵌套,因此这里需要依次读出第一层和第二层信息。读者可通过add_json.items()来更加直观地观察2层嵌套。

上述代码执行后,返回的结果(字典类型)可进行进一步处理:

        {u'lat': 40.05399730253742, u'lng': 116.29933765654373}

2.获取并解析XML数据

Geocoding API也提供XML格式的返回数据,下面以获得XML格式的数据为例介绍代码过程。

        import requests  # 导入库
        add = ’北京市中关村软件园’  # 定义地址
        ak = 'DdOyOKo0VZBgdDFQnyhINKYDGkzBkuQr'  # 创建访问应用时获得的AK
        url = 'http://api.map.baidu.com/geocoder/v2/? address=%s&output=xml&ak=%s'  # 请求URL
        res = requests.get(url % (add, ak))  # 获得返回请求
        add_info = res.text  # 返回文本信息
        print (add_info)  # 打印输出

上述所有代码与JSON格式的代码完全相同,只在第四行定义URL时,将output的返回值类型设置为XML,执行后返回结果如下:

        <? xml version="1.0" encoding="utf-8"? >
        <GeocoderSearchResponse>
            <status>0</status>
            <result>
                <location>
                    <lng>116.299337657</lng>
                    <lat>40.0539973025</lat>
                </location>
                <precise>0</precise>
                <confidence>40</confidence>
                <level>工业园区</level>
            </result>
        </GeocoderSearchResponse>

接着我们通过引入一个XML格式化处理库来从中提取经纬度信息。关于XML文件的解析,Python默认和第三方的常用库包括xml、libxml2、lxml、xpath等,我们使用Python自带的XML进行处理。

        # 设置字符编码为utf-8
        import sys
        reload(sys)
        sys.setdefaultencoding('utf-8')
        import xml.etree.ElementTree as Etree  # 导入XML中的ElementTree方法
        root = Etree.fromstring(add_info)  # 获得XML的根节点
        lng = root[1][0][0].text  # 获得lng数据
        lat = root[1][0][1].text  # 获得lat数据
        print ('lng: %s' % lng)  # 格式化打印输出
        print ('lat: %s' % lat)  # 格式化打印输出

上述代码中,前4行实现了一个功能,将代码的字符编码设置为utf-8,否则系统会因默认为ASCII,对返回的带有中文字的符串无法识别而报错。

代码第五行导入XML自带的ElementTree方法,该方法可以实现对XML内容的查询、新建、修改等操作,是XML中比较简单容易上手的方法(XML除了ElementTree外,还提供了DOM和SAX方法)。

代码第六行获得从API得到的XML对象并获得根节点。

代码第七、八行分别通过嵌套读取从根节点开始向下的第3层经纬度数据。

代码最后两行格式化打印输出。

上述代码执行后返回结果如下:

        lng: 116.299337657
        lat: 40.0539973025

有关百度API的更多信息,具体查阅http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding

在API应用中,中文的编码处理因细节太多而常常让人头疼。因此,如果可以则应尽量减少直接在API的数据中出现中文字符。在实际企业应用中,会出现多种API形式,但无论哪种形式,其基本实现思路都是一致的:导入库→定义请求变量→发送请求→获得返回数据→格式化并获得目标数据,因此需要掌握JSON和XML的数据与其他数据的转换方法。