进行数据解析的理由不计其数,相关的工具和技巧也同样如此。但是,当您需要用这些数据做一些新的事情时,即使有“合适的”工具可能也是不够的。这一担心对于异类数据源的集成同样存在。用来做这项工作的合适工具迟早应该是一种编程语言。
Oracle 提供了一些非常强大的实用程序来加载、处理和卸载数据。SQL*Loader、Data Pump、外部表、Oracle Text、正则表达式都能提供这些功能。然而人们常常会需要在数据库外做一些事情(或者,说得琐碎些,可能您还没有获得必要的数据库权限)。
利用 Python 可以进行高水平的、有效的数据解析。而利用互联网上免费提供的大量标准库和众多模块可以处理数据逻辑,不必手动剖析字节。
字符串理论
文本解析的最低级别是字符串。Python 并不把字符区分为单独的数据类型,但却区分普通字符串和 Unicode 字符串类型。字符串可以括在单引号、双引号或三引号中,并且是 Python 的一种不可变对象 — 一旦创建就不能对其进行修改。每一个操作都会创建一个新的字符串对象。对于具有静态类型语言经验的编程人员而言,乍听上去这可能真得很奇怪,但此类实现有一些特定的原因,多数与性能有关。
因为 Python 完全支持 Unicode,所以处理多语言信息不存在问题。手动创建 Unicode 字符串时,您可以选择直接在字符串前使用 u 前缀(如 u"Unicode text")或者使用内置的 unicode() 函数。可以使用 unicode() 或 encode() 方法在任何支持的字符集中对字符串进行编码。有关支持的编码列表,请查阅 Python 库参考 的标准编码部分或使用导入编码;输出 encodings._aliases.keys()。
您可以放心地使用 UTF-8 编写 Python 程序,记住仅变量名必须是有效的 ASCII 字符串。注释可以是希腊文、汉字或任意内容。不过,这样的文件或者要求使用附加字节顺序标记 (BOM) 的编辑器来保存,或者,需要您编写第一行代码:
# -*- coding: utf-8 -*-
字符串提供有一组方法可用于进行大多数有用的文本操作,如 find()、split()、rjust() 或 upper()。它们在内置的 str 类型上实现,该类型可以表示普通字符串和原始字符串。(原始字符串与普通字符串对反斜线的解释不同。)
>>> zen = "Explicit is better than implicit." >>> print zen.title() 'Explicit Is Better Than Implicit.' >>> zen.split(' ') ['Explicit', 'is', 'better', 'than', 'implicit.'] >>> zen.rstrip('.').lower().replace('is', 'is always') 'explicit is always better than implicit'
Python 的可迭代类型的最棒的一个特性是索引方法。普通索引以 0 开始而负索引向后计数,所以 [-1] 表示最后一个字符,[:5] 表示前 5 个字符,而 [5:-5] 表示前 5 个和后 5 个字符组成的字符串。
>>> sample = "Oracle Database" >>> sample[0] 'O' >>> sample[0:6], sample[7:15] ('Oracle', 'Database') >>> sample[-8:] 'Database' >>> sample[sample.index('Data')+4:] 'base'
正则表达式
Python 当然支持正则表达式。事实上,Python 的正则表达式 re 模块支持 Unicode、匹配、搜索、拆分、替换和分组。如果您熟悉 Oracle 对正则表达式的实现方式,您就不会对 Python 的函数感到陌生。
在详细比较 Python 和 Oracle 对正则表达式的实现时,值得注意的差异包括:
-
当关系设计要求一种不同于编程语言 1 的方法时,re.search() 可用于代替 Oracle 的 REGEXP_LIKE、REGEXP_INSTR 和 REGEXP_SUBSTR。
-
对 Python 语法改写后,re.sub() 的使用方式可以与 REGEXP_REPLACE 完全相同。不过,要注意 Oracle 的位置参数从 1 开始,而 Python 编制任何索引时都从 0 开始。
-
Oracle 的 match_parameter 表示正则表达式的一组标志,方式与 Python 在搜索模式或模式对象编译属性中使用 (?iLmsux) 语法的方式相同。要获得有效标志的列表,请比较 Python 库参考 的 4.2.3 节和 Oracle 数据库 SQL 语言参考 中 match_parameter 的有效值列表。
Python 的 re.search() 函数非常灵活,这归功于正则表达式这一基本概念。re 模块的最底层有一个对象,它表示匹配模式的方式允许以多种不同的方法对源字符串进行剖析。re.compile() 函数返回一个采用某一模式和若干可选标志的编译模式对象,如 re.I,它表示不区分大小写的匹配。
>>> import re >>> p = re.compile("^a.*", re.I) >>> print p <_sre.SRE_Pattern object at 0x011CA660>
您无须显式编译正则表达式。re 模块中的函数以透明方式完成此工作。如果代码中多处用到编译模式,使用该模式非常有益,但是如果该模式仅使用一次,则不需要这样的编码开销。
Python 中有六个正则表达式编译标志:
-
I (IGNORECASE) 用于不区分大小写的匹配
-
L (LOCALE) 使得特殊的序列(如词和空格)与语言设置相关
-
M (MULTILINE) 意味着在多行中搜索该模式,这样 ˆ 可以匹配字符串的开始位置和每一个换行符后面的位置,$ 可以匹配每一个换行符前面的位置和字符串的结束位置
-
S (DOTALL) 强制使用点专用字符 (.) 匹配任意字符,包括换行符
-
U (UNICODE) 使得特殊的序列可以识别 Unicode
-
X (VERBOSE) 可以增强您编写的正则表达式的可读性。
要一次使用多个标志,只需将它们加在一起即可 — 如 re.compile("Oracle", re.I+re.S+re.M)。另一种方式是使用 (?iLmsux) 语法将使用所需数量的标志选项作为搜索模式的前缀。这样,前一表达式可写作 re.compile("(?ism)Oracle")。
有关使用正则表达式的最好建议是尽可能地避免使用它们。在将它们嵌入代码前,请确定没有字符串方法可以完成相同的工作,因为字符串方法更快且不会带来导入以及正则表达式处理这些额外的开销。在字符串对象上使用 dir() 就可以看到可用的内容。
下例展示了在 Python 这样一种动态语言中看待正则表达式的方式。解析 tnsnames.ora 文件以便为每个网络别名创建简单连接字符串(将 file() 函数指向您的 tnsnames.ora 文件的位置):
>>> import re
>>> tnsnames = file(r'tnsnames.ora').read()
>>> easy_connects = {}
>>> tns_re = "^(w+?)s?=.*?HOSTs?=s?(.+?)).*?PORTs?=s?(d+?)).
*?SERVICE_NAMEs?=s?(.+?))"
>>> for match in re.finditer(tns_re, tnsnames, re.M+re.S):
... t = match.groups()
... easy_connects[t[0]] = "%s:%s/%s" % t[1:]
>>> print easy_connects
此程序在 Oracle Database XE 默认的 tnsnames.ora 文件上的输出是:
{'XE': 'localhost:1521/XE'}
请注意,此正则表达式非常愚钝,会被 IPC 条目所阻塞,因此需要将这些条目放在文件的结尾处。解析匹配圆括号是一个 NP 完成问题。
因为提供有多种公开方法,Python 匹配对象的功能非常强大,这些方法包括 span()(它可以返回匹配范围)、group()(它可以按给定的索引返回匹配组)以及 groupdict()(它可以在模式含有命名的组时以字典形式返回匹配组)。
逗号分隔值
CSV 格式因其简洁性和跨平台设计常用于组织间的信息交换。使用正则表达式通常可以轻松地解析逗号分隔值,但使用 Python 的 csv 模块可以使此任务变得更为容易。
使用该模块要求开发人员熟悉该模块所采用的逻辑。有关 CSV 文件的最重要的信息是它的“方言”,它包含分隔符、引号字符、行终止符等相关信息。Python 2.5 中目前可用的方言是 excel 和 excel-tab。内置的嗅探器总是试图猜测正确的格式。写入器与阅读器对象支持 CSV 数据的输入和输出。
就本例而言,我用的是 HR 模式的 JOBS_HISTORY 表中的数据。它演示了如何直接从一个 SQL 查询创建 CSV 文件 job_history.csv。
>>> import csv
>>> import cx_Oracle
>>> db = cx_Oracle.connect('hr/hrpwd@localhost:1521/XE')
>>> cursor = db.cursor()
>>> f = open("job_history.csv", "w")
>>> writer = csv.writer(f, lineterminator="
", quoting=csv.QUOTE_NONNUMERIC)
>>> r = cursor.execute(" "SELECT * FROM job_history ORDER BY employee_id, start_date")
>>> for row in cursor:
... writer.writerow(row)
...
>>> f.close()
该文件包含:
101,"1989-09-21 00:00:00","1993-10-27 00:00:00","AC_ACCOUNT",110 101,"1993-10-28 00:00:00","1997-03-15 00:00:00","AC_MGR",110 102,"1993-01-13 00:00:00","1998-07-24 00:00:00","IT_PROG",60 114,"1998-03-24 00:00:00","1999-12-31 00:00:00","ST_CLERK",50 122,"1999-01-01 00:00:00","1999-12-31 00:00:00","ST_CLERK",50 176,"1998-03-24 00:00:00","1998-12-31 00:00:00","SA_REP",80 176,"1999-01-01 00:00:00","1999-12-31 00:00:00","SA_MAN",80 200,"1987-09-17 00:00:00","1993-06-17 00:00:00","AD_ASST",90 200,"1994-07-01 00:00:00","1998-12-31 00:00:00","AC_ACCOUNT",90 201,"1996-02-17 00:00:00","1999-12-19 00:00:00","MK_REP",20
或者,您也可以使用 Oracle SQL Developer 以 CSV 格式导出数据。
可以通过以下方式读取该 CSV 文件:
>>> reader = csv.reader(open("job_history.csv", "r"))
>>> for employee_id, start_date, end_date, job_id, department_id in reader:
... print job_id,
...
JOB_ID IT_PROG AC_ACCOUNT AC_MGR MK_REP ST_CLERK ST_CLERK
AD_ASST SA_REP SA_MAN AC_ACCOUNT
注意我不必在上面显式指定方言,它是自动推断出的。我只是输出了 job_id 列,但对这样经过解析的文件我确实可以做的是将其插入数据库中。为确保日期得到正确处理,在批量插入前对 NLS_DATE_FORMAT 进行手动设置。
SQL> CREATE TABLE job_his ( 2 employee_id NUMBER(6) NOT NULL, 3 start_date DATE NOT NULL, 4 end_date DATE NOT NULL, 5 job_id VARCHAR2(10) NOT NULL, 6 department_id NUMBER(4) 7 ); >>> reader = csv.reader(open("job_history.csv", "r")) >>> lines = [] >>> for line in reader: ... lines.append(line) ... >>> cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'") >>> cursor.executemany("INSERT INTO job_his VALUES(:1,:2,:3,:4,:5)", lines) >>> db.commit()
如果您使用 SQL Developer 创建该 CSV 文件,则可能需要修改日期格式,如下所示:
>>> cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YY/MM/DD'")
csv 模块美中不足的地方是缺乏原生 Unicode 支持。有关解决方案和使用 CSV 文件的更多示例,请参见 Python 库参考 的 9.1.5 示例部分。
URL
urlparse 模块使您可以将统一资源定位器字符串分解为各个组成部分,分别表示 URL 模式、网络位置、路径、参数、查询字符串、碎片标识符、用户名、口令、主机名和/或端口。Python 2.5 支持 24 个最常用的模式,包括 svn+ssh、sftp 和 mms。下例显示了 urlparse 模块的一些特性:
>>> from urlparse import urlparse >>> url = "http://www.oracle.com/technology/index.html?rssid=rss_otn_news#section5" >>> pr = urlparse(url) >>> print type(pr) <class 'urlparse.ParseResult'> >>> print pr.hostname www.oracle.com >>> print pr.query rssid=rss_otn_news >>> print url==pr.geturl() True
RSS 信源
RSS 基于一个非常简单的概念:您在事件发生时获得它的最新消息,而不是碰巧发现。整合许多不同来源的 RSS 信源是开发领域的一个流行趋势,对新闻信源聚合器和 Web 2.0 混搭尤其如此。
RSS 是 XML 的一种方言,因此使用 Python 提供的一种 XML 解析器可轻松地对其进行处理。Python 标准库本身还没有提供用于解析信源的模块。不过,feedparser.org 免费提供一个稳定的、经过广泛检验的通用信源解析器。由于它没有外部依赖性,因此这是快速熟悉模块安装概念的一个很好的机会。
下载 feedparser 模块的最新版本(撰写此文时为 4.1)后,对其进行解压缩并将工作目录修改为 feedparser-4.1。在控制台/命令提示符下,运行 python setup.py install。此命令将模块放入 Python 文件夹,使其立即可供使用。就是这样。
了解一下 Oracle 的动态如何?
>>> import feedparser
>>> import time
>>> rss_oracle = feedparser.parse("http://www.oracle.com/technology/syndication/rss_otn_news.xml")
>>> for e in rss_oracle.entries[:5]:
.. t = time.strftime("%Y/%m/%d", e.updated_parsed)
.. print t, e.title
2007/07/23 Integrating Oracle Spatial with Google Earth
2007/07/11 Oracle Database 11g Technical Product Information Now Available
2007/07/11 Explore the Oracle Secure Enterprise Search Training Center
2007/07/09 Implementing Row-Level Security in Java Applications
2007/06/29 Build Your Own Oracle RAC Cluster on Oracle Enterprise Linux and iSCSI
feedparser 模块具有足够的智能,可以正确解析日期、处理 HTML 标记、规范内容以便可以针对所有支持的 RSS 和 ATOM 变体使用一致的 API、解析相对链接、检测有效字符编码等。
接下来解析什么?
有了正则表达式工具箱,您可以搜索几乎所有的纯文本内容。至于解析文本数据,Python 有很多其他特性,包括:
- email.parse,用于解析电子邮件消息
- ConfigParser,用于解析从 Windows 系统中获得的 INI 配置文件
- robotparser 模块,用于解析您 Web 站点的 robots.txt
- optparse 模块,用于进行强大的命令行参数解析
- HTMLParse 模块中的 HTMLParse 类,用于有效地解析 HTML 和 XHTML(类似于 SAX)
- 若干 XML 解析器(xml.dom、xml.sax、xml.parsers.expat、xml.etree.ElementTree)
对于二进制数据,您可以利用 binascii 模块,它包含一组函数可用于在二进制编码数据和 ASCII 编码数据之间转换,并附带了分别用于 base64 和 uuencode 转换的 base64 和 uu 模块。
总结
这篇方法文档介绍了在 Python 中进行数据解析所采用的一些基本和高级的技巧。您现在应当已经认识到了 Python 附带的标准库的威力。在开始制作您自己的解析器之前,首先检查一下所需的功能是否已可供导入。
字符串操作比正则表达式操作速度快,同时足以满足很多的编程需要。但是到底选用 Python 还是 Oracle 正则表达式函数取决于您的应用程序逻辑和业务需要。