科普篇:如何正确使用正则表达式

Posted ICT销售与大客户联盟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了科普篇:如何正确使用正则表达式相关的知识,希望对你有一定的参考价值。

科普篇:如何正确使用正则表达式

科普篇:如何正确使用正则表达式

正则表达式对数据处理而言非常重要。数据科学家的一部分使命是操作大量数据。有时候,这些数据中会包含大量文本语料。比如,假如我们需要搞清楚「特朗普文件 [注意,可能是敏感词]」中谁给谁发送过邮件,那么我们就要筛查 1150 万份文档!我们可以采用人工方式,亲自阅读每一封电子邮件,但我们也可以利用 Python 的力量。毕竟,代码存在的意义就是自动执行任务。

即便如此,从头开始写一个脚本也需要大量时间和精力。这就是正则表达式的用武之地。正则表达式(regular expression)也被称为 RE、regex 和 regular pattern,这是一种让我们能快速筛查和分析文本的紧凑型语言。

正则表达式始于 1956 年——Stephen Cole Kleene 创造了它并将其用于描述人类神经系统的 McCulloch-Pitts 模型。到了 60 年代,Ken Thompson 将这种标记方法添加到了一个类似 Windows 记事本的文本编辑器中,自那以后,正则表达式不断发展壮大。

正则表达式的一大关键特征是其经济实用的脚本。你甚至可以将其看作是代码中的捷径。没有它,我们就要码更多代码才能实现相同的功能。

现在,我们来看看正则表达式的能力。

01 介绍 Python 的正则表达式模块

首先,准备数据集:打开那个文本文件,将其设置成「只读」,然后读取它。我们也为其分配了一个变量 fh,表示文件句柄(file handle)。

fh = open(r"test_emails.txt", "r").read()

注意我们直接在目录路径之前使用了 r。这项技术会将一个字符串转换成一个原始字符串,这有助于避免由某些机器阅读字符的方式所导致的冲突,比如 Windows 中目录路径中的反斜杠。

你可能注意到了我们目前没有使用整个语料库。我们只是人工地取了该语料库中前面几封邮件,然后将其做成了一个测试文件。这样做的目的是在本教程中输出显示测试结果时,就不用每次都显示数千行结果了。这能免除很多烦恼。你自己练习的时候使用完整语料库或我们的测试文件都不会有问题。

现在,假设我们想知道这些电子邮件的发件人。我们可以试试只用原始的 Python 来实现:

for line in fh.split("\n"):
if "From:" in line:
print(line)

也可以使用正则表达式:

import re
for line in re.findall("From:.*", fh):
print(line)

我们来解读一下这段代码。我们首先导入了 Python 的 re 模块。然后我们写了操作代码。在这个简单的示例中,这段代码只比原始 Python 少一行。但是,随着任务的增加,正则表达式可以让你的脚本继续保持简单经济。

re.findall() 返回字符串中满足其模式的所有实例的列表。这是 Python 内置的 re 模块中最常用的函数之一。分解看看。该函数的形式是 re.findall(pattern, string),有两个参数。其中,pattern 表示我们希望寻找的子字符串,string 表示我们要在其中查找的主字符串。主字符串可以包含很多行。

在我们继续深入之前,我们先了解一些常见的正则表达式模式。

02 常见的正则表达式模式

我们在上面的 re.findall() 中使用的模式中包含一个完全拼写出来的字符串 From:。这在我们知道我们所要寻找的东西是什么时非常有用,可以确定到实际的字母以及大小写。如果我们不知道我们所想要的字符串的确切格式,我们将难以为继。幸运的是,正则表达式有解决这类情况的基本模式。我们看看本教程中会使用的一些模式:

\w 匹配字母数字字符,即 a-z、A-Z 和 0-9,也会匹配下划线 _ 和连接号 –
\d 匹配数字,即 0-9
\s 匹配空白字符,包括制表符、换行符、回车符和空格符
\S 匹配非空白字符
. 匹配除换行符 \n 之外的任意字符

有了这些正则表达式模式,你就能在我们继续解释代码时很快理解。

03 使用正则表达式模式

我们现在可以解释上面 re.findall("From:.*", text) 一行中的 .* 了。首先来看 .

for line in re.findall("From:.", fh):
print(line)

通过在 From: 后面添加一个 .,我们是要寻找 From: 之后另外的一个字符。因为 . 是查找除 \n 之外的任意字符,所以这会得到我们看不到的空格。我们可以多加一些点来验证这个情况

for line in re.findall("From:...........", fh):
print(line)

看起来加点就能让我们得到这一行的其余内容了。但这很单调乏味,而且我们不知道需要加多少个点。这就是星号 * 发挥作用的地方。

* 匹配 0 个或更多个其左侧的模式的实例。也就是说它会查找重复的模式。当我们查找重复模式时,我们说我们的搜索是「贪婪匹配」。如果我们没有查找重复模式,我们可以说我们的搜索是「非贪婪匹配」或「懒惰匹配」。

04 让我们使用 * 构建一个 . 的贪婪搜索

for line in re.findall("From:.*", fh):
print(line)

因为 * 匹配 0 个或多个其左侧模式的实例且 . 在其左侧,所以我们可以获取 From: 字段中的所有字符,直到该行结束。这样就用美丽而简洁的代码输出显示了一整行。

我们甚至可以更进一步只取出其中的名称。

match = re.findall("From:.*", fh)
for line in match:
print(re.findall("\".*\"", line))

这里,我们先使用之前的做法通过 re.findall() 得到了包含 From:.* 模式的行的列表。接下来,我们遍历这个列表。在这一次训练中,我们都再执行一次 re.findall()。这一次,该函数先从匹配第一个引号开始。

注意我们在第一个引号后使用了一个反斜杠。这个反斜杠是一个用于给其它特殊字符转义的特殊字符。比如说,当我们想将引号用作字符串本身而不是特殊字符时,我们可以像 \" 这样使用反斜杠对其转义。如果我们不使用反斜杠转义上述模式,它就会变成 "".*"",Python 解释器就会将其看作是两个空字符串之间的一个句号和一个星号。这会出错并使该脚本中断。因此,我们这里必须使用反斜杠给引号转义。

在第一个引号匹配后,.* 会获取这一行中下一个引号前的所有字符。当然,该模式中的下一个引号也经过了转义。这让我们可以得到引号之中的名称。每个名称都输出显示在方括号中,因为 re.findall 以列表形式返回匹配结果。

match = re.findall("From:.*", fh)
for line in match:
print(re.findall("\w\S*@.*\w", line))

看起来很简单,是不是?只是模式不一样而已。让我们详细看看。

for line in match:
print(re.findall("\w\S*@", line))

for line in match:
print(re.findall("@.*", line))

域名通常包含字母数字字符、句号,有时候还会有连接号。这很简单,一个 . 就行。为了实现贪婪搜索,我们使用 * 来延展。这让我们可以匹配直到该行结束的任意字符。

for line in match:
print(re.findall("@.*\w", line))

看起来有些麻烦。实际上正则表达式确实需要花些时间才能熟练,但一旦你掌握了,在写分析字符串的代码时就会快很多。接下来,我们会介绍一些常见的 re 函数,这些函数在重新组织这个语料库时会很有用。

06 常见的正则表达式函数

re.findall() 毫无疑问非常有用,re 模块还提供了一些同样方便的函数,其中包括:

re.search()
re.split()
re.sub()

我们先逐一介绍一下这些函数,然后再将它们用来整理笨重难读的语料库。

re.search()

re.findall() 匹配的是一个模式在一个字符串中的所有实例然后以列表的形式返回它们,而 re.search() 匹配的是一个模式在一个字符串中的第一个实例,然后以 re 匹配对象的形式返回它。

match = re.search("From:.*", fh)
print(type(match))
print(type(match.group()))
print(match)
print(match.group())

与 re.findall() 类似,re.search() 也有两个参数。第一个参数是所要匹配的模式,第二个是要在其中查找的字符串。这里为了简洁我们已经分配了 match 变量的结果。

我们还能看到 print(match) 会显示字符串以及除字符串本身之外的属性,而 print(match.group()) 只会显示字符串。

re.split()

address = re.findall("From:.*", fh)
for item in address:
for line in re.findall("\w\S*@.*\w", item):
username, domain_name = re.split("@", line)
print("{}, {}".format(username, domain_name))

re.sub()

re.sub() 是另一个很好用的 re 函数。顾名思义,它的功能是替换一个字符串的一部分。举个例子:

其中第一行和第二行的任务我们之前已经见过。第三行我们在 address 上应用 re.sub(); address 是电子邮件标头中的完整的 From: 字段。

re.sub() 有三个参数。第一个是所要替换的子字符串,第二个是用来替换前者的字符串,第三个是主字符串本身。

(来源:网络)

科普篇:如何正确使用正则表达式

“相关文章阅读”





以上是关于科普篇:如何正确使用正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

如何使正则表达式正确验证信息?

如何使用 PHP 和正则表达式检查输入值是不是正确? [复制]

正则表达式实践补充完整篇

正则表达式-理论基础篇

正则表达式-理论基础篇

如何在 pymongo 中正确设计正则表达式?