2.3 内容延伸:读取非结构化网页、文本、图像、视频、语音
在前面的章节中,我们介绍的内容是企业常见的数据来源和获取方式。本节将拓展数据来源方式和格式的获取,主要集中在非结构化的网页、文本、图像、视频和语音。
2.3.1 从网页中获取运营数据
要从网页中获取数据,可使用Python内置标准库或第三方库,例如urllib、urllib2、httplib、httplib2、requests等。本节使用requests方法获取网页数据。
import requests # 导入库 url = 'http://www.dataivy.cn/blog/dbscan/' # 定义要获取的网页地址 res = requests.get(url) # 获得返回请求 html = res.text # 返回文本信息 print(html) # 打印输出网页源代码
在代码中,先导入用到的网络请求处理库requests,然后定义一个用来获取的url,通过requests的get方法获取url的返回请求,并通过返回请求的text方法获取内容(源代码),最终打印输出网页信息。部分结果如下:
<! DOCTYPE html> <html lang="zh-CN" class="no-js"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <link rel="profile" href="http://gmpg.org/xfn/11"> ...... </body> </html>
从网页中读取的信息其实是网页的源代码,源代码经过浏览器解析后才是我们看到的不同的页面内容和效果。因此,在获取网页中包含了内容的源代码后,下面要做的是针对源代码的解析。有关该内容会在3.10.1节中具体说明。
2.3.2 读取非结构化文本数据
非结构化的文本数据指的是文本数据中没有结构化的格式,需要定制化解析才能获取数据,并且每条记录的字段也可能存在差异,这意味着传统的结构化读取方式将很难工作。非结构化的日志就是一个典型示例,服务器的日志可由运维工程师自行定义,因此不同公司的日志格式会有所不同。另外在网站日志中还可能包含通过页面“埋码”的方式采集到的用户的行为数据,这些都会使日志面临非结构化的解析问题。
在“附件-chapter2”文件夹中有一个名为traffic_log_for_dataivy的日志文件,里面存放了www.dataivy.cn网站一段时间的日志数据。本节示例代码的目的是将日志读取出来。
file = 'traffic_log_for_dataivy' fn = open(file, 'r') # 打开要读取的日志文件对象 content = fn.readlines() # 以列表形式读取日志数据 print(content[:2]) fn.close() # 关闭文件对象
上述代码中先定义了一个要读取的非结构化文本文件,通过Python标准库open方法以只读模式打开文件,然后通过readlines方法将文件内容以行为单位读取为数据列表,打印输出前2条数据,最后关闭文件对象。执行后返回如下结果:
['120.26.227.125- - [28/Feb/2017:20:06:51 +0800] "GET / HTTP/1.1" 20010902 "-" "curl"\n', '139.129.132.110- - [28/Feb/2017:20:06:51 +0800] "GET / HTTP/1.1" 20010903 "-" "curl"\n']
其实日志文件只是普通文本文件的一种类型而已,其他的非结构化数据文件都可以以类似的方法读取,即使文件没有任何扩展名也可以。
总结:对于非结构化的文本处理,通常更多侧重于特定场景,通用性较差,原因就在于非结构化的形式本身变化多样。自然语言理解、文本处理和挖掘、用户日志和机器日志解析等都是围绕该领域的主要工作主题。
2.3.3 读取图像数据
在Python 3中,读取图像通常使用Pillow和OpenCV两个库,相对而言,笔者使用后者的情况更多。本节以“附件-chapter2”文件夹中cat.jpg为例进行说明。
1.使用Pillow读取图像
有关Pillow与PIL的渊源,我们已经在1.2.3节中介绍过,这里直接介绍Pillow的用法。
Pillow中包含很多库,常用的是Image,通过使用其中的open方法来读取图像。用法如下:
from PIL import Image # 导入库 file = 'cat.jpg' # 定义图片地址 img = Image.open(file, mode="r") # 读取文件内容 img.show() # 展示图像内容
其中关键的方法是open,其参数包括以下两个。
❑ file:文件对象名称,可以是文件名,也可以是图像文件字符串。
❑ mode:打开模式,默认只能是r模式,否则会报错。当file是图像字符串时,会调用系统的rb模式读取。
提示
从Pillow 1.0及以上版本开始,就已经不支持直接使用import Image了,之后的方法统一为from PIL import Image。而在此之前的Pillow以及之前的PIL库都支持直接使用import Image方法。因此读者在使用不同的版本时需要注意。
通过open读取之后会返回一个图像文件对象,后续所有的图像处理都基于该对象进行。上述代码执行后,通过img.show()会调用系统默认的图像浏览器打开图像进行查看,如图2-28所示。
图2-28 调用img.show()展示图像
该对象包含了很多方法,这些方法可以用来打印输出文件的属性,例如尺寸、格式、色彩模式等。
print('img format: ', img.format) # 打印图像格式 print('img size: ', img.size) # 打印图像尺寸 print('img mode: ', img.mode) # 打印图像色彩模式
上述代码执行后返回的结果如下:
img format: JPEG img size: (435, 361) img mode: RGB
其中图像的类型是图像本身的格式,例如jpg、gif、png等;图像尺寸是指图像分辨率,示例中的尺寸是435×361(单位是像素);图像的模式指的是颜色模式,示例图像是RGB模式。
相关知识点:图像颜色模式
在不同的领域中,图像的色彩模式有多种标准。比较常见的颜色模式包括以下几种。
❑ RGB:自然界中所有的颜色几乎都可以用红、绿、蓝这3种颜色按不同波长及强度组合得到,这种颜色模式在数字显示领域非常流行。
❑ CMYK:这是一种工业四色印刷标准,4个字母分别指代青(Cyan)、洋红(Magenta)、黄(Yellow)、黑(Black)。
❑ HSB:这种模式使用色泽(Hue)、饱和度(Sa-turation)和亮度(Brightness)来表达颜色的要素,这种模式更多基于人类心理的认识和感觉。
❑ 其他模式:灰度模式、索引模式、位图模式等,在一定场景下较为常见。
不同的色彩模式之间可以相互转换,例如从RGB模式转换为灰度模式,如图2-29所示。
图2-29 灰度图像模式
img_gray = img.convert('L') # 转换为灰度模式 img_gray.show() # 展示图像
除此以外,基于该文件对象也可以进行其他操作,例如图像格式转换、旋转、裁剪、合并、滤波处理、色彩处理、缩略图处理等。限于篇幅,在此不做过多介绍。
2.使用OpenCV读取图像
在第1章介绍了OpenCV的安装方法,但截至本书撰写时,通过pip安装的包仅为3.4.3版本。而此时4.0版本已经发布,但通过其他方法无法安装该版本。
OpenCV读取和展示图像主要使用cv2库。
import cv2 # 导入库 file = 'cat.jpg' # 定义图片地址 img = cv2.imread(file) # 读取图像 cv2.imshow('image', img) # 展示图像 cv2.waitKey(0) # 与显示参数配合使用
上述两种方法执行后,都会产生如图2-28所示的结果。不同的是,图2-28中通过PIL调用的是系统默认的图像显示工具,而在OpenCV中是通过自身创建的图像功能显示图像。
另外,两种方法中都有一个waitKey()的方法,该方法的作用是键盘绑定函数,其中的参数表示等待毫秒数。执行该方法后,程序将等待指定的毫秒数,看键盘是否有输入,然后返回值对应的ASCII值。如果其参数为0,则表示无限期地等待,直到键盘有输入。
笔者通常使用第2种方法读取图像,此方法更加简单。其中imread方法细节如下。
语法:
cv2.imread(filename[, flags])
描述:
读取图像内容,如果图像无法读取则返回空信息。支持的图像格式几乎包括了日常所有场景下的格式。具体包括以下这些。
❑ Windows bitmaps文件:*.bmp、*.dib。
❑ JPEG文件:*.jpeg、*.jpg、*.jpe。
❑ JPEG 2000文件:*.jp2。
❑ PNG文件:*.png。
❑ WebP文件:*.webp。
❑ 移动图像格式:*.pbm、*.pgm、*.ppm *.pxm、*.pnm。
❑ Sun rasters文件:*.sr、*.ras。
❑ TIFF文件:*.tiff、*.tif。
❑ OpenEXR文件:*.exr。
❑ Radiance HDR文件:*.hdr、*.pic。
参数:
❑ filename必填,字符串,图像地址。
❑ flags可选,int型或对应字符串,颜色的读取模式。如果flag>0或者cv2.IMREAD_COLOR,读取具有R/G/B三通道的彩色图像;如果flag=0或cv2.IMREAD_GRAYSCALE,读取灰度图像;如果flag<0或cv2.IMREAD_UNCHANGED,读取包含Alpha通道的原始图像。
返回:
图像内容,如果图像无法读取则返回NULL。
3.使用Matplotlib展示图像
除了使用OpenCV自带的图像展示方法外,OpenCV还常与matplotlib配合展示图像,这种场景更加常用。组合使用时可借用Matplotlib的强大图像展示能力进行图像的对比和参照以及不同图像模式的输出。
但是需要注意的是,使用OpenCV读取彩色图像时,OpenCV是以B、G、R的顺序存储和显示图像数据的,而Matplotlib是以R、G、B的顺序显示图像的;如果直接使用OpenCV读取图像后调用Matplotlib显示,那么会与原始图像不一致。
下面通过实例代码来演示如何通过Matplotlib显示OpenCV读取的图像,以及OpenCV和Matplotlib显示图像的差异。
# 导入库 import matplotlib.pyplot as plt import cv2 # 导入库 # 读取图像文件并创建多图像副本 file = 'cat.jpg' # 定义图片地址 img = cv2.imread(file) # 读取图像 b, g, r = cv2.split(img) # 分离颜色三通道 img2 = cv2.merge([r, g, b]) # 重新组合图像数据 # 使用Matplotlib展示图像 plt.subplot(121) # 定位第1个网格 plt.title('plt BGR image') # 设置子网格标题 plt.imshow(img) # 设置子网格展示的图像 plt.subplot(122) # 定位第2个网格 plt.title('plt RGB image') # 设置子网格标题 plt.imshow(img2) # 设置子网格展示的图像 # 使用OpenCV展示图像 cv2.imshow('OpenCV BGR image', img) # 展示BGR图像 cv2.imshow('OpenCV RGB image', img2) # 展示RGB图像 cv2.waitKey(0) # 与显示参数配合使用 cv2.destroyAllWindows() # 关闭所有窗口
上述代码中,我们分别导入用到的matplotlib.pyplot和cv2库,然后读取图像文件并创建多图像副本。其中涉及两个新的参数用法:split和merge。
❑ split:使用cv2.split可以将OpenCV读取的图像数据,分离为B、G、R单颜色通道图像。
❑ merge:使用cv2.merge将多个通道颜色数据以R、G、B的次序再次组合起来(跟img中的模式不同),形成img2。
使用Matplotlib展示图像中,我们用到了以下参数:
❑ subplot:用来定位子网格的位置,默认参数包括subplot(nrows, ncols, plot_number), nrws为行数,ncols为列数,plot_number为网格位置,该位置以行为索引标准,行索引完毕之后再换另一行。例如,一个[4, 2, 3]的位置代表的是创建一个4行2列的网格,并定位在第3个子网格(第2行的第1个位置)。如果3个值都小于10,那么可以组合并简写为一个3位数,例如本示例中写为plt.subplot(121)或plt.subplot(1, 2, 1)都是可以的,二者是等效的。
❑ title:用来设置网格的标题。
❑ imshow:用来展示一个图像对象。
使用Matplotlib展示的图像结果如图2-30所示。
图2-30 matplotlib两种读取方式下展示结果差异
使用OpenCV展示图像中的参数基本都是我们之前用过的。其中的cv2.destroyAllWin-dows()用来关闭所有窗口,该函数在下面读取视频数据时还会用到。本段函数的展示图像结果如图2-31所示。
图2-31 cv2两种读取方式下展示结果差异
观察通过Matplotlib和OpenCV读取的图像结果,会发现二者在默认模式下的显示结果是不同的。因此,读者如果要用Matplotlib展示OpenCV读取的图像结果,必须注意图像显示模式的差异和调整。
2.3.4 读取视频数据
Python读取视频最常用的库也是Opencv。本节以“附件-chapter2”文件夹中Megamind.avi的视频为例进行说明,以下是一段读取视频内容的代码示例。
import cv2 # 导入库 cap = cv2.VideoCapture("tree.avi") # 获得视频对象 status = cap.isOpened() # 判断文件是否正确打开 # 输出基本属性 if status: # 如果正确打开,则获得视频的属性信息 frame_width = cap.get(3) # 获得帧宽度 frame_height = cap.get(4) # 获得帧高度 frame_count = cap.get(7) # 获得总帧数 frame_fps = cap.get(5) # 获得帧速率 print('frame width: ', frame_width) # 打印输出 print('frame height: ', frame_height) # 打印输出 print('frame count: ', frame_count) # 打印输出 print('frame fps: ', frame_fps) # 打印输出 # 读取视频内容并展示视频 success, frame = cap.read() # 读取视频第1帧 while success: # 如果读取状态为True cv2.imshow('vidoe frame', frame) # 展示帧图像 success, frame = cap.read() # 获取下1帧 k = cv2.waitKey(int(1000 / frame_fps))# 每次帧播放延迟一定时间,同时等待输入指令 if k == 27: # 如果等待期间检测到按Esc键 break # 退出循环 # 操作结束释放所有对象 cv2.destroyAllWindows() # 关闭所有窗口 cap.release() # 释放视频文件对象
上述代码分为4个部分,以空行分隔。
第一部分为前3行,先导入库,然后读取视频文件并获得视频对象,最后获得视频读取状态。其中的关键方法是VideoCapture,用来读取图像。
语法:
cv2.VideoCapture(VideoCapture ID|filename|apiPreference)
描述:
读取视频设备或文件,并创建一个视频对象实例。
参数:
❑ VideoCapture ID:必填。int型,系统分配的设备对象的ID,默认的设备对象的ID为0。
❑ Filename:必填。包括如下部分。
❍ 视频文件的名称,字符串,例如abc.avi。目前版本下只支持avi格式。
❍ 序列图像,字符串,例如img_%2d.jpg(图像序列包括img_00.jpg, img_01.jpg, img_02.jpg, ...)。
❍ 视频URL地址,字符串,例如protocol://host:port/script_name?script_params|auth。
❍ apiPreference:int型,后台使用的API。
返回:
一个视频对象实例。
第2部分为if循环体内的9行代码,该代码主要用来在判断文件被正确读取的情况下,输出视频文件的整体信息。除了代码中get方法使用的参数值外,OpenCV还支持更多图像属性,如表2-7所示。
表2-7 get方法支持的图像属性
该段代码输出如下视频信息:
frame width: 320.0 frame height: 240.0 frame count: 444.0 frame fps: 14.999925000374999
第3部分为具体读取和展示视频的每一帧内容。首先读取视频的第1帧,如果状态为True,则展示图像并读取下一帧,期间通过cv2.waitKey参数做图像延迟控制,同时延迟期间等待系统输入指定,如果有输入Esc则退出循环读取帧内容。视频内容的截图如图2-32所示。
图2-32 视频播放截图
相关知识点:动态图像如何产生
我们视觉上看到的视频(或动态图)在计算机中其实是不存在的,计算机中存储的是一幅一幅的图像,在视频里面被称为帧,一帧对应的就是一幅图像。当图像连续播放的速度超过一定阈值时,由于人类的视觉具有视觉暂留(延迟效应),多个暂留图像的叠加便形成了我们看到的动态图像。一般情况下,如果1秒钟播放超过16帧时,我们会认为这是一幅动态图像。
在视频中有几个关键名词。
帧率(FPS):每秒播放的帧数被定义为帧率,帧率越高,在视觉上认为图像越连贯,就越没有卡顿的现象。常见的帧率包括23.967(电影)、25(PAL电视),示例图像大约为15。帧率与图像清晰度无关,它只是决定了视频的连贯性。
帧分辨率:帧分辨率基本决定了视频的清晰度(除此之外还有视频处理效果、设备播放差异等,这里指的是同等条件下的视频源)。在同样大小的图像中,分辨率越高图像通常就会越清晰。所以形容视频时提到的1080P(1920×1080)、720P(1280×720)其实指的就是分辨率标准。当然,对于同样分辨率下,在不同国家、不同电视规制、不同扫描标注下,也会更加细分。
注意
在OpenCV中的图像读取和处理,其实是不包括语音部分的,但从视频文件的组成来讲通常包括序列帧和语音两部分。目前的方式通常是将两部分分开处理。
第4部分为当所有操作结束后,删除所有由OpenCv创建的窗体,释放视频文件对象。
有关OpenCV的更多信息,具体可查阅opencv.org。
2.3.5 读取语音数据
对于语音文件的读取,可以使用Python的audioop、aifc、wav等库实现。但针对语音处理这一细分领域,当前市场上已经具备非常成熟的解决方案,例如科大讯飞、百度语音等。大多数情况下,我们会通过调用其API实现语音分析处理的功能,或者作为分析处理前的预处理功能。
在具体实现过程中,既可以直接下载SDK做离线应用,也可以使用在线的服务。科大讯飞的语音服务如图2-33所示。
图2-33 科大讯飞语音服务
本节将以百度语音API服务应用为例,说明如何通过请求百度语音的API,将语音数据转换为文字信息。
在正式应用百度语音API之前,请读者先参照2.2.5节中介绍的步骤,建立百度账户并注册成为百度开发者。基于该条件下,我们继续开通语音识别服务。具体方法如下:
1)进入http://yuyin.baidu.com/app,在弹出的界面中单击要针对哪个应用开通语音识别服务。我们默认使用在之前建立的API_For_Python应用。因此,单击该应用的“开通服务”,如图2-34所示。
图2-34 开通服务
2)在弹出的窗口中,单击选择“语音识别”并确定,如图2-35所示。
图2-35 选择开通语音识别服务
3)开通成功后系统会提示,然后单击图2-36中右侧的,会弹出如图2-36所示信息。
图2-36 查看Key信息
弹出的API Key和Secret Key为在后续语音识别中要使用的信息。
以下为完整代码:
代码以空行作为分隔,包括4个部分:
第1部分为导入需要的库信息,具体用途见代码注解。
第2部分为获得要使用百度语音识别API的token信息。其中的API_Key和Secret_Key见图2-36。token_url通过占位符定义出完整字符串,并在请求时发送具体变量数据,从返回的信息中直接读取token便于在下面应用中使用。有关获取token的更多信息,具体查阅http://yuyin. baidu.com/docs/asr/56。
提示
在请求获取token时,可使用get或post(推荐使用)两种方法,token的有效期默认为1个月,如果过期需要重新申请。
第3部分主要用于获取和处理语音文件数据。通过最常见的open方法以二进制的方式读取语音数据,然后从获得的语音数据中获取原始数据长度,并将原始数据转换为base64编码格式。这里需要注意的是,需要将其decode为utf-8格式的编码,否则产生的结果对象为bytes类型,JSON会报解析错误“TypeError: Object of type 'bytes' is not JSON serializable”。
注意
百度语音识别API对于要识别的音频源是有要求的:原始PCM的录音参数必须符合8k/16k采样率、16位深、单声道,支持的压缩格式有:pcm(不压缩)、wav、opus、amr、x-flac。
第4部分为本节内容的主体,发送请求获取语音识别结果。本段落中先定义了发送头信息;然后定义了一个字典,用于存储要发送的key-value字符串并将其转换为JSON格式;接着通过post方法以隐式发送的方式进行上传并获得返回结果;最后输出返回结果和其中的语音转文字的信息。该部分内容的细节比较多,具体参见百度语音API开发说明http://yuyin.baidu.com/docs/asr/57。
关于cuid的获取,由于笔者是在本地计算机上测试的,因此使用的是MAC地址。获取MAC地址的方法是:打开系统终端命令行窗口(Win+R,输入cmd并按Enter键),在命令行中输入命令ipconfig/all,在列出的所有连接中找到其中媒体状态不是“媒体已断开”并且属于当前连接的物理地址信息,如图2-37所示为笔者计算机MAC信息。
图2-37 获取MAC地址信息
有关语音服务的更多信息,具体查阅http://www.xfyun.cn。
上述代码执行后返回如下结果:
{"co rpus_no":"6520028890940897658", "err_msg":"success.", "err_no":0, "result":["百度语音提供技术支持,"], "sn":"989585587221518062523"} [’百度语音提供技术支持,']
系统成功返回识别结果,录音的内容是“百度语音提供技术支持”。第2段的编码是unicode编码格式的中文。
总结:上述语音识别仅提供了关于语音转文字的方法。其实语音本身包括非常多的信息,除了相对浅层的生理和物理特征,例如语速、音调、音长、音色、音强等外,还包括更深层次的社会属性,这部分内容需要自然语音理解的深层次应用。目前的语音数据读取后主要应用方向包括:
❑ 语音转文字。这也是广义上语音识别的一种,直接将语音信息转为文字信息,例如微信中就有这个小功能。
❑ 语音识别。语音识别指的是对说话者通过选取语音识别单元、提取语音特征参数、模型训练、模型匹配等阶段,实现其角色识别和个体识别的过程,例如通过某段语音识别出是哪个人说的话。
❑ 语音语义理解。在语音识别的基础上,需要对语义特征进行分析,目的是通过计算得到语音对应的潜在知识或意图,然后提供对应的响应内容或方法。语音识别和语音理解的差异之处在于,语音识别重在确定语音表达的字面含义,属于表层意义;而语音理解重在挖掘语音的背后含义,属于深层意义。
❑ 语音合成。语音合成就是让计算机能够“开口说话”,这是一种拟人的技术方法。语音合成,又称文本转语音(Text to Speech)技术,它通过机械的、电子的方法将文字信息转变为人类可以听得懂的语音。
❑ 应用集成。经过分析、识别后的信息可以与硬件集成,直接通过语音发送指令。例如通过跟Siri(苹果手机上的语音助理)的“沟通”,除了可以进行日常对话,还可以告诉你天气情况、帮你设置系统日程、介绍餐厅等。这是智能机器人在模式识别方面的典型应用。
基于上述的复杂应用场景,通常语音后续分析、处理和建模等过程都无法由数据工程师单独完成,还需要大量的语料库素材,以及社会学、信号工程、语言语法、语音学、自然语音处理、机器学习、知识搜索、知识处理等交叉学科和相关领域才有可能解开其中的密码。