专栏首页AI研习社嘀~正则表达式快速上手指南(上篇)

嘀~正则表达式快速上手指南(上篇)

本文为雷锋字幕组编译的技术博客,原标题Regular Expressions for Data Scientists,来源dataquest。 翻译 | 汪其香 Noddleleslee 陈亚彬 赵朋飞 杨婉迪 校对 | 余杭 整理 | 凡江

作为数据科学家,快速处理海量数据是他们的必备技能。有时候,这包括大量的文本语料库。例如,假设要找出在 Panama Papers(https://en.wikipedia.org/wiki/Panama_Papers) 泄密事件中邮件的发送方和接收方,我们需要详细筛查1150万封文档!我们可以手工完成上述任务,人工阅读每一封邮件,读取每一份最后发给我们的邮件,或者我们可以借助Python的力量。毕竟,代码存在的一个至关重要的理由就是自动处理任务。

尽管如此,从头开始编写脚本、写脚本、抓取数据需要大量的时间和精力。这正是正则表达式的用武之地。RE,regex 和regular patterns 表达的意思皆是正则表达式,它形成一门简洁的语言帮助我们快速地整理和分析文本。

正则出现在1956年,Stephen Cole Kleene 创建它用于描述人类神经系统的MP模型(McCulloch and Pitts model)(http://aishack.in/tutorials/artificial-neurons-mccullochpitts-model/)的概念。1960年代,Ken Thompson 将这个概念添加到类似Windows记事本的文本编辑器中,自此正则开始壮大。

正则一个关键特性是节省脚本。我们可以视其为代码的捷径。没有它,我们不得不为同样目的敲大量的垃圾代码。

本教程需要Python基础知识。如果你理解if-else 表达式,while 语句和for 循环,列表和字典,本教程的大部分都可以搞定啦。此外你需要代码编辑器,如Visual Studio Code,PyCharm 或Atom都可以。这样当我们遍历每一行代码时就不会茫然,此外基础的pandas库也是必要的。如果你需要复习,可以跳转到 pandas 的教程(https://www.dataquest.io/blog/pandas-python-tutorial/)。

学完本教程,你会对正则的使用熟悉很多,可以使用re模块的基础模式和函数完成字符串分析。我们也学会如何高效地使用正则和pandas库化大量紊乱的数据集为有序。

现在,让我们看看正则可以做些什么。

数据集介绍

我们使用Kaggle的欺诈邮件文本语料库。它包括1998到2007发出的上千封钓鱼邮件。点击此处(https://www.kaggle.com/rtatman/fraudulent-email-corpus)可以下载数据集。在对整个语料库操作之前,让我们先学习在一封邮件应用正则表达。

Python 正则表达式模块的介绍

首先打开文本文件读取数据,设置为只读模式,并读取数据集,最后将上述操作结果赋给变量 fh(“file handle” 即文件句柄)。

请注意我们在设置目录路径之前添加 r。它将转换字符串为原始字符串,避免机器读取字符时候引起冲突,例如 Windows 的目录路径中的反斜杠。

你也许注意到我们现在并没有使用整个语料库。相反地,我们先人工挑选语料库的相对靠前的一些邮件作为测试文件。本教程不打算每次都展示上千行的结果,每次都打印其中的一部分作为测试。这可能会让人感到恼怒。你可以使用整个语料库,也可以使用我们的测试文件。无论哪种方式,都能很好得获得学习经验。

现在,假设我们现在想知道邮件的来源。我们可以在自己的Python尝试如下代码:

或者,我们可以使用正则表达式:

我们来遍历这段代码。首先导入 re 模块。然后敲出图示余下代代码。这个例子中,这比原来的Python 代码仅少 1 行 。然而随着脚本行数的快速增长,正则表达式可以节省脚本的代码量。

re.findall() 以列表形式返回字符串中符合模式的所有实例。它是Python内置 re 模块中最经常使用的函数。让我们来剖析 re.findall。re.findall(pattern, string)接受两个参数。pattern表示我们想要搜索的子字符串,string 表示我们想要搜索的主字符串。主字符串可以由多行组成。

.* 是字符串模式的简写。我们很快就会解释它的细节。现在它们与From: 域中的名称和电子邮件地址相匹配。

在让我们更深一步探索之前,先浏览一下常用的正则表达式。

常用的正则表达式

我们之前用到的 re.findall() 包含"From:"的字符串。这个函数当我们明确知道搜索目标时候十分有用,甚至包括明确字母拼写和是否大小写。如果我们不明确知道搜索目标时,该函数就会失效。幸运的是正则表达有解决这个问题的基本模式。让我们看一些这篇文章将用到的:

  • \w 匹配字母数字字符,即a-z,A-Z,0-9。它也匹配下划线和波折号。
  • \d 即0-9。
  • \s matches 匹配空白格,包括制表符、换行字符、回车符和空格字符。
  • \S 匹配非空白格字符。
  • . 匹配除换行字符\n外的任意字符串。

有这些正则表达式的说明在手,你就可以在我们解释上述代码时能够快速地理解。

使用正则表达式

现在我们来解释re.findall("From:.*", text) 中.* 的作用。首先看. :

From:后面添加. ,表示寻找它旁边的字符,因为.查找 \n外的任何字符,它也会捕捉肉眼不可见的空格。我们可以添加更多的点来验证。

看起来添加很多点可以获得行中我们想要的剩余部分。但这是冗余的而且我们不知道要敲多少个点。这就是很有用的*的由来。

* 匹配其左侧表达式的0个或多个模式的实例。这意味它寻找重复模式。当我们寻找重复模式时,称为贪婪搜索。否则,我们称之为非贪婪搜索或懒惰搜索。

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

因为 * 匹配其左侧 0 个或多个模式类的实例,而 . 在其左侧,因此我们可以获得From: 到行末的所有字符。这种漂亮高效的方式可以输出完整的行。

我们甚至可以更进一步,只分离出名字:

我们使用re.findall() 返回包含"From:.*" 模式的列表,就像我们以前做的那样。为了简洁起见 我们给match 变量赋以上述操作的结果。接下来,我们迭代列表。每一次循环,我们都再次执行re.findall 。这一次,这个函数从第一个引号开始匹配。

请注意我们在第一个引号旁使用反斜杠。反斜杠是用于转义其他特殊字符的特殊字符。例如,当我们想使用引号作为字符串而不是特殊字符时,我们用反斜杠来表示转义:\"。如果不使用反斜杠表示转义,就是"".*"",Python解释器视作两个空字符串之间读取一个句点和一个星号。这就会出现错误,脚本不能运行。因此,关键是使用反斜杠表示转义。

在第一个引号匹配之后,.* 获取行中直到下一个转义的引号的所有字符。获取引号内的名字。每个名字都在方括号内打印出,因为re.findall 以列表形式返回匹配内容。如果我们需要获取电子邮件地址呢?

看起来很简单不是嘛?只是匹配模式有些许不同,让我们逐一攻破。

以下是如何匹配电子邮件地址的前面部分:

电子邮件总是包含@符号,让我们从它开始。电子邮件@符号之前的部分可能包含字母数字字符,\w 就派上用场。然而,因为一些邮件包含句点或破折号,这是不够的。我们用\S 来查找非空白字符。但\w\S 仅仅找到两个字符。添加 * 重复寻找过程。因此模式前半部分是:\w\S*@。

现在来看看@符号后半部分的模式:

域名通常包含字母数字字符、句点和破折号。这很简单,一个 . 就能搞定。为了使用贪婪模式,我们用*来扩展搜索。这使我们可以匹配直到行结束的任何字符。

如果我们仔细观察这行,我们会发现每个电子邮件都封装在尖括号内,<和>。 我们的模式.*包括闭合的尖括号。让我们纠正一下:

电子邮件地址以字母数字字符结束,所以我们用\w模式覆盖。因此@ 符号后面是.*\w,这意味着我们想要的模式是一组以字母数字字符结尾的字符。这不包括>。

完整电子邮件地址模式是:\w\S*@.*\w。

这是相当多的工作。熟练使用正则表达式需要一段时间,但是一旦您掌握它的模式,您就能够更快地为字符串分析编写代码。接下来,我们将运行一些re 模块常见函数,当我们开始重新整理语料库时它们将非常有用。

常见的正则表达式函数

re.findall() 无疑是有用的,re 模块提供了更多同样便捷的函数。

包括:

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

在使用它们把杂乱无序的语料库变为有序之前,我们对它们逐一分析。

re.search()

re.findall() 以列表形式返回匹配字符串中满足模式的所有实例,re.search() 匹配字符串中模式的第一个实例,并将其作为一个re 模块的匹配对象。

和 re.findall() 类似, re.search() 也接受两个参数。第一个参数是匹配的模式,第二个参数是要搜索的字符串范围。这里为了简洁起见,我们已经将结果赋值给match 变量。

因为 re.search() 返回一个re 模块的匹配对象,我们不能直接打印出对应的名字和电子邮件地址。 相反,我们必须先采用 group()这个函数. 我们已经在上面的代码中打印了它们类型,可以看出group() 将匹配对象转化成一个字符串。

我们也可以看到打印match 时显示的是对应的属性而不是字符串本身, 而打印 match.group() 只显示字符串。

re.split()

假设我们需要一种快速的方法来获取电子邮件地址的域名。我们可以用三次正则操作,像这样:

第一行用法前面已经提到了。我们返回一个字符串列表,每个字符串包含From: 字段的内容,并将其赋给变量。接下来的通过遍历这个列表来查找邮件的地址。同时通过迭代电子邮件地址和使用 re 模块的split() 函数来把每一个地址剪成两半,用 @作为分隔符。最后再打印出来。

re.sub()

另一个方便的 re 函数是 re.sub()。正如函数名所示,它用来替换字符串的各个部分。举个例子:

前两行已经在前面出现过了。

在第三行我们将 address 作为 re.sub() 函数的第三个参数,即邮件标题中完整的From: 字段。

re.sub() 需要三个参数。第一个是被代替的子字符串,第二是想要放在目标位置的字符串,而第三是主字符串。

pandas 中的正则表达式

现在我们有了正则表达式的一些基础知识,我们可以尝试一些更复杂的。然而,我们需要正则表达式跟pandas Python数据分析库结合。Pandas 库中有一个很有用的把数据组织成整齐表格的对象,即 DataFrame 对象,也可以从不同的角度理解它。结合正则表达式的代码,它就像用一个特别锋利的刀雕刻软黄油。

不用担心从来没用过 Pandas。我们会通过代码一步一步进行,这样你就不会感到困惑。正如我们在引言中提到的,如果你想详细学习,请访问 Pandas tutotial(https://www.dataquest.io/blog/pandas-python-tutorial/)。

我们可以通过 Anaconda(https://docs.continuum.io/anaconda/) 或者 pip 来下载 pandas 库。 详情请查看安装指南(http://pandas.pydata.org/pandas-docs/stable/install.html)。

用正则表达式和Pandas分拣邮件

Corpus 是一个包含数千封电子邮件的文本文件。我们将使用正则表达式和Pandas 来将每封电子邮件适当分类 使Corpus 语料库更便于阅读和分析。我们会将每封邮件分为以下几个类别之一:

  • sender_name
  • sender_address
  • recipient_address
  • recipient_name
  • date_sent
  • subject
  • email_body

每个类别将成为我们Pandas数据帧或表格中的一列。这非常有用,因为我们可以自行处理每一列。例如,我们可以直接编写来找出电子邮件来自哪个域名,而不需要首先编码来将电子邮件地址与其他部分隔离开来。基本上,对数据集先分类可以让我们编写更简洁的代码。反过来,简洁的代码减少了机器所需的操作数量,这加快了我们的处理速度,特别是在处理大量数据集时。

准备Script

我们从上面一个简单的脚本开始。从头开始以便弄清楚它们内部运行的原理。

在代码的一开始首先导入 re 和pandas 模块,我们导入的Python email 包对于邮件正文很重要,如果仅仅使用正则表达式来处理电子邮件的正文会相当复杂,可能需要足够的清理不必要信息方面的工作才能保证它能正常运行。

email 包。然后我们创建一个空的列表emails 用来存放包含每个电子邮件详细信息的字典。

我们经常将代码的结果打印到屏幕上来判断代码是对还是错。然而,由于数据集中有成千上万的电子邮件,打印出上千行到屏幕上会占据本教程页面。我们当然不想让你一遍又一遍地滚动成千上万行的结果。因此,正如我们在本教程开始时所做的,我们打开并阅读了Corpus的较短版本。为了本次教程我们手工编写一点。你可以使用实际的数据集。

每次运行 print() 函数,你只需几秒钟就可以把几千行打印到屏幕上。

现在我们开始使用正则化表达式。

我们用 re 模块的 split 函数将 fh 中整个文本块拆分为一个单独的电子邮件列表,分配给 contents。这很重要,因为我们希望通过循环遍历列表来一个个地处理电子邮件。但是我们怎么知道用 "From r"来分割呢?我们之所以知道这一点,是因为在编写脚本之前查看了文件。我们没有必要仔细阅读数千电子邮件。只需要通过前几行来大致看看数据的结构是什么样子的。正因为如此,每个电子邮件前面都是字符串 "From r"。我们已经截图了文本文件的样子:

邮件用 “From r”开头

绿色部分是第一个电子邮件。蓝色部分是第二个电子邮件。我们可以看到,这两个电子邮件都是以 "From r"开头,用红色的框来显示。

我们在这个教程中之所以使用 Fraudulent Email Corpus是为了表明当数据是无序的和不熟悉的时候,我们不能只依靠代码来处理,它需要一双眼睛。就像刚刚展示的那样,我们需要查看 Corpus 来研究它的结构。另外 这样的数据可能还需要再处理 ,这个 Corpus 语料库也是同理。举个例子,即使我们用本教程的完整脚本算出本数据集包含3977 封邮件,实际上更多。有些邮件的开头没有 "From r"字段所以没有被拆分成单独的邮件。但是我们保留了这个结果以免它无穷无尽。

注意我们也用了 contents.pop(0)去掉列表中的第一个元素。那是在第一封电子邮件的前面有"From r" 字符串。当这个字段被分割的时候,在索引0的位置生成了一个空字符串。我们即将编写的脚本是为电子邮件而设计的。如果出现空字符串它可能会报错。去掉空字符串可以让我们避免这些错误打断脚本的运行。

本文分享自微信公众号 - AI研习社(okweiwu),作者:雷锋字幕组

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-04-14

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MIT Taco 项目:自动生成张量计算的优化代码,深度学习加速效果提高 100 倍

    我们生活在大数据的时代,但在实际应用中,大多数数据是 “稀疏的”。例如,如果用一个庞大的表格表示亚马逊所有客户与其所有产品的对应映射关系,购买某个产品以 “1”...

    AI研习社
  • 调试机器学习模型的六种方法

    在机器学习模型中,开发人员有时会遇到错误,但经常会在没有明确原因的情况下导致程序崩溃。虽然这些问题可以手动调试,但机器学习模型通常由于输出预测不佳而失败。更糟糕...

    AI研习社
  • 如何搭建增量推荐系统?

    尽管我会尽量减少数学术语的使用,但本文希望读者熟悉一些概念,如矩阵分解、嵌入空间以及基本的机器学习术语。这篇文章并不是推荐系统的介绍,而是对它们的增量变体的介绍...

    AI研习社
  • 【ES6基础】模板字符串(Template String)

    模板字符串是ES6中非常重要的一个新特性,这个特性使得我们处理相关业务变得更加容易。比如在处理嵌入表达式、多行字符串、字符串中插入变量、字符串格式化等方面的应用...

    前端达人
  • 【ES6基础】模板字符串(Template String)

    模板字符串是ES6中非常重要的一个新特性,这个特性使得我们处理相关业务变得更加容易。比如在处理嵌入表达式、多行字符串、字符串中插入变量、字符串格式化等方面的应用...

    前端达人
  • Python爬虫之正则表达式入门正则表达式语法正则表达式实例ReMatch对象贪婪匹配和最小匹配

    Re库是Python的标准库,主要用于字符串匹配 调用方式: import re

    desperate633
  • 实战|记一次授权的渗透测试

    前段时间收到小伙伴的求助,说是有一个站搞不了,让我看看能不能帮忙弄一下;刚好最近应急完了在看日志,看的有点烦,于是便接下了这个任务,增加点乐趣。

    信安之路
  • SmartMesh开发者社区朋克宣言

    https://github.com/SmartMeshFoundation/developer-community

    rectinajh
  • redis-4.0.1 源码一键安装脚本(centos 7)

    老七Linux
  • 一起来学Go --- (go的枚举以及数据类型)

    枚举指一系列的相关的常量,比如下面关于一个星期的中每天的定义,通过上篇博文,我们可以用在const后跟一对圆括号的方式定义一组常量,这种定义法在go语言中通常用...

    Wyc

扫码关注云+社区

领取腾讯云代金券