如何用 Python 和正则表达式抽取文本结构化信息?

2024年07月26日 python高级教程 正则表达式 Python51

痛点

很多人的日常工作,都是要和大量的文本打交道的。

例如学者需要阅读大量的文献材料,从中找到灵感、数据与论据。

学生需要阅读很多教科书和论文,然后写自己的报告或者做幻灯。

财经分析师,需要从大量的新闻报道中,找到行业的发展趋势和目标企业动态的蛛丝马迹。

不是所有的文本处理,都那么新鲜而有趣。

有一项重要但繁琐的工作,就是从大量的文本当中抽取结构化的信息。

许多数据分析的场景,都要求输入结构化的信息。

例如在咱们之前介绍过的《贷还是不贷:如何用 Python 和机器学习帮你决策?》和《如何用 Python 和深度神经网络锁定即将流失的客户?》中,你都看到了,机器模型更喜欢被结构化的表格信息来喂养。

然而,结构化的信息,不一定就在那里,静候你来使用。很多时候,它蕴藏在以往生成的非结构化文本中。

你可能早已习惯,人工阅读文本信息,把关键点摘取出来,然后把它们拷贝粘贴放到一个表格中。从原理上讲,这样做无可厚非。但是实际操作里面,效率太低,而且太麻烦。

大部分人,是不愿意从事这种简单重复的枯燥工作的。

一遍遍机械重复鼠标划定文本范围,“Ctrl+C”、切换到表格文档、找准输入位置,再 “Ctrl+V”……

这种事儿做得太多,对你的肩肘关节,甚至是身心健康,都有可能造成不利影响。

想不想尝试用一种更简单的自动化方式,替你快速完成这些烦人的操作步骤呢?

读过本文后,希望你能找到答案。

样例

这里,我们举一个极端简化的中文文本抽取信息例子。

之所以这样做,是为了避免你在解读数据上花费太多时间。

我更希望,你能够聚焦于方法,从而掌握新知。

假设一个高中班主任,高考后让班长统计一下学生们的毕业去向。班长很认真地进行了调查,然后做了如下汇报:

张华考上了北京大学
李萍进了中等技术学校
韩梅梅进了百货公司
……

为了让你对样例足够熟悉,甚至有共鸣,这里我从 1998 年版的新华字典中,“借鉴” 了部分内容。

够贴心吧?

现实生活中,一个班大概不会只有 3 个人,因此你可以想象这是一个长长的句子列表。

但其实班主任有个隐含的意思没有表达出来,即:

我想要一张表格!

所以,看到这一长串的句子,你可以想象他的表情。

班长估计也很难堪:

想要表格你早说啊!

这时候,假设你是班长,怎么办?

信息都在文本里面。但如果需要转换成表格,就得一个个信息点去寻找和处理。

其实,对于四五十人的班级来说,手动操作也不是什么太难的事情。

但是设想一下,如果你需要处理的数据量,是这个例子的十倍、百倍甚至千万倍呢?

继续坚持手动处理?

这不仅麻烦,而且不现实。

我们需要找到一种简单的方法,帮助我们自动抽取相应的信息。

此处我们使用的方法,是正则表达式。

正则

“正则表达式” 这个名字,初听起来好像很玄妙。实际上,它是从英文 “regular expression” 翻译过来的。

如果译成白话,那就是 “有规律的表述形式”。

这,听起来,是不是就更加接地气了?

但是,给你补一下 “假行家 101” 课程:

说别人听得懂的话,你能唬得住谁?

约定俗成,咱们继续沿用 “正则表达式”,来称呼它好了。

从创生之日起,它就给文本处理带来了高效率。

但是,用它的主要人群,却不是时常跟文字打交道的作家、编辑、学者、文员,而是……

程序员!

程序员写的代码,是文本;程序员处理的数据,很多也是文本格式。其中便有很多显著的规律可循。

正是靠着正则表达式这种独门秘籍,许多别人做起来,需要昏天黑地一整周的任务,程序员可以半小时搞定,然后喝着咖啡等下班。

即便到了泛人工智能的今天,正则表达式依然有许多令你意想不到的应用。

例如人机对话系统。

你可能看了新闻报道,总以为人机对话都是靠着知识图谱或者深度学习搞出来的。

不能说这里面,没有上述炫酷技术的参与。但它们充其量,只占其中一部分,或许还只是一小部分。

生产实践里面,大量的对话规则后面,并不是让你倍感神奇深奥的神经网络,而是一堆正则表达式。

你可能会担心,这样高端的应用技术,自己能掌握吗?

答案是:

当然!

正则表达式,并不难学。

尤其是当你把它和 Python 结合到一起,那简直就是效率神器了。

我们这就来看看,正则表达式怎么帮我们识别出样例文本里面 “人名” 和 “去向” 信息。

试练

请你开启一个浏览器,键入这个网址(https://regex101.com/)。

你会看见如下界面。

它可是一个正则表达式实验的利器。我教 INFO 5731 课程时,学生们就是在掌握了这个工具以后,迅速玩儿转了正则表达式。

这么好的工具,一定要价不菲吧?

不,它是免费的。你放心大胆使用就好了。

我们首先把左侧的编程语言,从默认的 PHP ,调整为 Python。

之后,把需要进行处理的文本,贴到中间空白的大文本框里面。

下面我们来尝试进行 “匹配”。

什么叫做匹配呢?

就是你写一个表达式,电脑便拿着鸡毛当令箭,在每一行文本上,都认认真真地找有没有符合该表达式的文本段落。

如有,则会高亮显示出来。

这里我们观察一下,发现每个句子里面,人员去向前面,都有一个 “了” 字。

好,我们就在中部上方小文本框里,把 “了” 字输入进去。

可以看到,三句话里面的 “了”,全都亮了。

这就是你接触到的第一种匹配方式 —— 按照字符原本的意思来查找一致的内容。

因为样例文本的规律性,我们可以把 “了” 当成一个定位符,它后面,到句子结束位置,是 “去向” 信息。

咱们需要找的一半结构化信息,不就是这个 “去向” 吗?

我们尝试匹配 “去向”。

怎么匹配呢?这次每一行的字儿都不一样啊?

没关系,正则表达式强大之处,此时就显示出来了。

你可以用一个点号,也就是.,表示任意字符。

字母、数字、标点…… 甚至是中文,也能涵盖在内。

然后我们继续想想看,去向信息这里,会有几个字呢?

不好说。

例子里面这简单的三句话,就有 “4 个字” 或者 “6 个字” 两种情况。

所以,我们无法指定去向信息里面字符的长度。

但这也没关系,我们只需要用一个星号(*),就可以代表出现次数,从 0 到无穷大都可以匹配。

当然,实际情况中,是不会真出现无穷大的。

我们在刚才输入的基础上,加上.*,结果就成了这个样子:

不错嘛!

不过似乎去向信息和 “了” 字儿都是一样颜色的高亮。那不就混到了一起吗?

我们可不想这样。

怎么办?

请你在.* 的两侧,尝试加入一对小括号(注意,不要用中文全角符号)试试看。

你会发现,这次 “了” 依然用蓝色表示,而后面的去向信息,已经变成了绿色。

这一对小括号,很重要,它叫做 “分组”,是提取信息的基本单位。

我们的任务已经解决了一半了,是吧?

下面我们来尝试把人名一并抽取出来。

我们来找人名的锚定位置。

细细观察,你不难发现,每个人名的后面,都有个动词跟着。

升学的同学,用的是 “考” 字,而就业的同学,用的是 “进” 字。

我们先尝试一下 “考” 字。

这里我们尝试直接把 “考” 字放在 “了” 字以前。但是你会发现,什么匹配结果也没有。

为什么?

回看数据,你会发现,人家用的原词是 “考上了”。

当然这里我们可以输入 “上” 字。不过你要考虑一下更为通用的情况。

好比说,“考取了” 怎么办?“考入了” 呢?

更好的方式,是继续使用我们刚才学会的 “大招”,在 “考” 和 “了” 之间,插入一个.*。

这时候,你的正则表达式的样子是 考.*了(.*)

看,第一行的信息成功匹配了吧?

但是,那后面还有两行没有匹配,怎么办?

我们依样画葫芦,就会发现,使用进.*了(.*) 就能正确匹配后两行。

问题来了:

匹配第一行的,匹配不了后两行,反之亦然。

这不好。我们希望写的表达式,能够更通用。

怎么办?

我们看看正则表达式当中 “或” 关系的表示。

这里,我们可以把两个字符用竖线隔开,旁边用中括号括起来,代表两者任一出现,都算匹配成功。

也就是,把正则表达式,写成这样:[考|进].*了(.*)

太棒了,三行的内容都已经匹配成功。

这里,动词词组,和代表时态的 “了” 作为中间锚定信息,我们可以放心大胆,把之前的人名信息,提取出来了。

也就是这样写:(.*)[考|进].*了(.*)

注意此时,人名分组是绿色,去向分组是红色的。

我们成功提取了两组信息!庆祝一下!

可是,如果你给班主任看这里的结果,估计他不会满意。

表格,我要表格!

别着急,该 Python 出场了。

下面我们尝试在 Python 把数据正式提取出来。

环境

本文的配套源代码,我放在了 Github 上。

你可以在我的公众号 “玉树芝兰”(nkwangshuyi)后台回复 “regex”,查看完整的代码链接。

如果你对我的教程满意,欢迎在页面右上方的 Star 上点击一下,帮我加一颗星。谢谢!

注意这个页面的中央,有个按钮,写着 “在 Colab 打开”(Open in Colab)。请你点击它。

然后,Google Colab 就会自动开启。

我建议你点一下上图中红色圈出的 “COPY TO DRIVE” 按钮。这样就可以先把它在你自己的 Google Drive 中存好,以便使用和回顾。

Colab 为你提供了全套的运行环境。你只需要依次执行代码,就可以复现本教程的运行结果了。

如果你对 Google Colab 不熟悉,没关系。我这里有一篇教程,专门讲解 Google Colab 的特点与使用方式。

为了你能够更为深入地学习与了解代码,我建议你在 Google Colab 中开启一个全新的 Notebook ,并且根据下文,依次输入代码并运行。在此过程中,充分理解代码的含义。

这种看似笨拙的方式,其实是学习的有效路径。

代码

首先,读入 Python 正则表达式包。

import re

然后,我们把数据准备好。注意为了演示代码的通用性,我这里在最后加了一行文字,区别于之前的文字规律,看看我们的代码能否正确处理它。

data = """张华考上了北京大学
李萍进了中等技术学校
韩梅梅进了百货公司
他们都有光明的前途"""

然后,该写正则表达式了。你真的需要自己手动来写吗?

当然不必。

强大的 regex101 网站,已经帮助我们准备好了。

请你点击上图中红色圈出的按钮,网站会为你准备好一个初始代码的模板,可以匹配你需要的模式。

你不需要完全照搬代码。其中有这样一句,是很重要的,拷贝过来,贴到 Colab Notebook 就好。

regex = r"(.*)[考|进].*了(.*)"

以上就是你的正则表达式,在 Python 里面应有的样子。

我们准备一个空列表,用来接收数据。

mylist = []

接着,写一个循环。

for line in data.split('\n'):
mysearch = re.search(regex, line)
if mysearch:
name = mysearch.group(1)
dest = mysearch.group(2)
mylist.append((name, dest))

我给你解释一下这个循环里面,各条语句的含义:

data.split('\n') 把文本数据按行来拆分开。这样我们就可以针对每一行,来获取数据。mysearch = re.search(regex, line) 这一句尝试匹配模式到该行内容。if mysearch 这个判断语句,是让程序分辨一下,该行是否有我们要找的模式。例如最后一行文字,里面并没有咱们前面分析的文字模式。遇到这样的行,直接跳过。name = mysearch.group(1) 是说匹配的第一组内容,也就是 regex101 网站里绿色代表的人名分组存到 name 变量里。下一句依次类推。注意 group 对应你正则表达式里面小括号出现的顺序,从 1 开始计数。mylist.append((name, dest)) 把该行抽取到的信息,存入到咱们之前定义的空列表里面。

注意,如果不加 mysearch = re.search(regex, line) 这一句,程序会对每一行都尝试匹配并且抽取分组内容,那么结果就会报这样的错误:

所以你看,用正则表达式抽取信息时,不能蛮干。

此时,我们查看一下 mylist 这个列表里面的内容:

mylist

结果为:

[('张华', '北京大学'), ('李萍', '中等技术学校'), ('韩梅梅', '百货公司')]

不错,一个不多,一个不少,恰好是我们需要的。

我们要把它导出成为表格。方法有很多,但是最简便顺手的,是用 Pandas 数据分析软件包。

import pandas as pd

只需要利用 pd.DataFrame 函数,我们就能把上面列表和元组(tuple)组成的一个二维结构,变成数据框。

df = pd.DataFrame(mylist)
df.columns = ['姓名', '去向']

注意,这里我们还非常细心地修改了表头。

看看你的劳动成果吧:

df

有了数据框,转换成为 Excel ,就是一行代码的事情了:

df.to_excel("dest.xlsx", index=False)

进入 Files 标签页,刷新并且查看一下当前目录下的内容:

这个 dest.xlsx 就是输出的结果了。下载之后我们可以用 Excel 打开查看。

任务完成!

你可以把结果提交给班主任,看他满意的笑容了。

小结

这篇教程里面,咱们谈了如何利用文本字符规律,借助 Python 和正则表达式,来提取结构化信息。

希望你已经掌握了以下本领:

  • 了解正则表达式的功用;

  • 用 regex101 网站尝试正则表达式匹配,并且生成初步的代码;

  • 用 Python 批量提取信息,并且根据需求导出结构化数据为指定格式。

再次强调一下,对于这么简单的样例,使用上述方法,绝对是大炮轰蚊子。

然而,如果你需要处理的数据是海量的,这个方法给你节省下来的时间,会非常可观。

希望你能够举一反三,在自己的工作中灵活运用它。

本文链接:http://so.lmcjl.com/news/9201/

展开阅读全文