干货 | 数据科学入门必读:如何使用正则表达式?

前言

正则表达式对数据处理而言非常重要。数据科学家的一部分使命是操作大量数据。

有时候,这些数据中会包含大量文本语料。比如,假如我们需要搞清楚「xxx文件 」中谁给谁发送过邮件,那么我们就要筛查 1150 万份文档!我们可以采用人工方式,亲自阅读每一封电子邮件,但我们也可以利用 Python 的力量。毕竟,代码存在的意义就是自动执行任务。

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

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

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

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

我们将使用来自 Kaggle 的 Fraudulent Email Corpus(欺诈电子邮件语料库)。其中包含 1998 年到 2007 年之间发送的数千封钓鱼邮件。这些邮件读起来很有意思。我们首先将使用单封邮件学习基本的正则表达式命令,然后我们会对整个语料库进行处理。

语料库地址:https://www.kaggle.com/rtatman/fraudulent-email-corpus

1

介绍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 表示我们要在其中查找的主字符串。主字符串可以包含很多行。

.* 是字符串模式的简写。我们马上就会详细解释。现在只需知道它们的作用是匹配 From: 字段中的名称和电子邮箱地址。

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

2

常见的正则表达式模式

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

\w 匹配字母数字字符,即 a-z、A-Z 和 0-9,也会匹配下划线 _ 和连接号 –

\d 匹配数字,即 0-9

\s 匹配空白字符,包括制表符、换行符、回车符和空格符

\S 匹配非空白字符

. 匹配除换行符 \n 之外的任意字符

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

3

使用正则表达式模式

我们现在可以解释上面 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 个或更多个其左侧的模式的实例。也就是说它会查找重复的模式。当我们查找重复模式时,我们说我们的搜索是「贪婪匹配」。如果我们没有查找重复模式,我们可以说我们的搜索是「非贪婪匹配」或「懒惰匹配」。

4

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

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 以列表形式返回匹配结果。

5

如果我们想得到电子邮箱地址呢?

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))

电子邮箱地址中总会包含一个 @ 符号,所以我们从它开始入手。电子邮箱地址中 @ 符号前面的部分可能包含字母数字字符,这意味着需要 \w。但是,由于某些电子邮箱地址包含句号或连接号,所以这还不够。我们增加了 \S 来查找非空白字符。但 \w\S 只能得到两个字符,所以增加 * 来重复查找。所以 @ 符号之前部分的模式是 \w\S*@。接下来看 @ 符号之后的部分。

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

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

简单看看这些行,我们可以发现每个电子邮箱地址都被放在一对尖括号 <> 之中。我们的模式 .* 会将右尖括号 > 包含进来。我们再调整一下:

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

电子邮箱地址是以字母数字字符结尾的,所以我们用 \w 作为这一模式的结尾。因此,@ 符号之后的部分是 .*\w,也就是说我们想要的模式是一组以字母数字字符结尾的任意类型的字符。这样就排除了 >。因此,完整的电子邮箱地址模式就为 \w\S*@.*\w

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

6

常见的正则表达式函数

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 变量的结果。

因为 re.search() 返回的是一个 re 匹配对象,所以我们不能直接通过 print 展示其中的名称和电子邮箱地址。我们必须首先为其应用 group() 函数。我们已经在上面的代码中将它们输出显示了出来。如我们所见,group() 函数的作用是将匹配对象转换成字符串。

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

re.split()

假设我们需要一种获取电子邮箱地址域名的快速方式。我们可以用 3 个正则表达式操作来完成。如下:

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))

第一行我们很熟悉。我们返回一个字符串列表并为其分配一个变量,其中每个字符串都包含了 From: 字段的内容。接下来我们遍历整个列表,寻找电子邮箱地址。与此同时,我们遍历这些电子邮箱地址并使用 re 模块的 split() 函数以 @ 符号为分割符将每个电子邮件一分为二。最后,我们将其显示出来。

re.sub()

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

sender = re.search("From:.*", fh) address = sender.group() email = re.sub("From", "Email", address) print(address) print(email)

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

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

文章来源:大数据周刊

文章编辑:小柳

原文发布于微信公众号 - 灯塔大数据(DTbigdata)

原文发表时间:2018-05-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

浅谈前端JavaScript编程风格

前言 多家公司和组织已经公开了它们的风格规范,具体可参阅jscs.info,下面的内容主要参考了Airbnb的JavaScript风格规范。当然还有google...

21270
来自专栏Crossin的编程教室

浅谈 Python 2 中的编码问题

Python 2.x 里的编码实在是一件令人烦躁的事情。不断有初学者被此问题搞得晕头转向。我自己也在很长一段时间内深受其害,直到现在也仍会在开发中偶尔被坑。在本...

375140
来自专栏思考的代码世界

Python编程从入门到实践之类|第10天

面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为...

31440
来自专栏Brian

Python进阶教程(一)

概述 hi,朋友们大家好,今天将英文原著作者 @yasoob《Intermediate Python》进行翻译和在工作中使用的Python技巧进行了总结。Git...

38770
来自专栏SeanCheney的专栏

《利用Python进行数据分析·第2版》第2章 Python语法基础,IPython和Jupyter Notebooks2.1 Python解释器2.2 IPython基础2.3 Python语法基础

当我在2011年和2012年写作本书的第一版时,可用的学习Python数据分析的资源很少。这部分上是一个鸡和蛋的问题:我们现在使用的库,比如pandas、sci...

472110
来自专栏程序员互动联盟

【一起学python】print 语句

联盟有个小伙伴,为了督促自己学习进步,决定把自己以前学的python重新梳理下,并且以文章的方式展示出来,联盟专门做一起学python系列专栏,鼓励这位小伙伴学...

36270
来自专栏JetpropelledSnake

Python面试题之回调函数

编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写库;...

17320
来自专栏数据结构与算法

1464 装箱问题 2

题目描述 Description 一个工厂制造的产品形状都是长方体,它们的高度都是h,长和宽都相等,一共有六个型号,他们的长宽分别为1*1, 2*2, 3*3,...

37580
来自专栏项勇

笔记45 | 代码性能优化建议[转]

14260
来自专栏difcareer的技术笔记

Android智能指针

网上已经有很多分析智能指针的文章了,讲得不错的是:Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析。本文尽量从不分析代码的角度,将And...

8540

扫码关注云+社区

领取腾讯云代金券