查看原文:【数据seminar】数据治理 | 数据清洗必备 — 正则表达式
我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系。该板块将涉及以下几个模块:
1. 计算机基础知识
2. 编程基础
(1) 数据治理 | 带你学Python之 环境搭建与基础数据类型介绍篇
(4) 数据治理 | 还在用Excel做数据分析呢?SQL它不香吗
(5) 数据治理 | 普通社科人如何学习SQL?一篇文章给您说明白
3. 数据采集
4. 数据存储
(1) 安装篇: 数据治理 | 遇到海量数据stata卡死怎么办?这一数据处理利器要掌握
(2) 管理篇: 数据治理 | 多人协同处理数据担心不安全?学会这一招,轻松管理你的数据团队
(3) 数据导入: 数据治理 | “把大象装进冰箱的第二步”:海量微观数据如何“塞进”数据库?
5. 数据清洗
(1) 本期内容:数据治理 | 数据清洗必备 — 正则表达式
6. 数据实验室搭建
目录
Part 1 前言
在前面的系列文章中,我们已经介绍了 Python 的编程语法以及其他的技术与技巧。本文将在 Python 语法的基础上,介绍编程界大名鼎鼎的文本处理工具——正则表达式。一般在做数据清洗和数据处理时,基本的Python语法是不够用的,我们必须借助一些Python标准库和第三方库来作为数据清洗的辅助工具,包括但不限于re库、Numpy库 和 Pandas库。本文主要介绍 Python 正则表达式标准库——re
。
Part 2 re 模块介绍
re 模块是 Python 正则表达式标准库。正则表达式又称规则表达式,英文名为 Regular Expression,在编程语言中通常以 regex,regexp 或 re 来表示正则表达式。那么正则表达式到底是什么,有什么作用呢?简单来说,正则表达式是对字符操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑;利用这个“规则字符串”,就可以在我们需要处理的文本中匹配(抓取)或替换那些符合我们设定规则的特定字符。例如,可以利用正则表达式将一个字符串中的“你”替换为“我”;或者判断字符串中是否含有“你”字;又或者匹配字符串中所有以“你”为开头,以“的”为结尾的字符串删除等等。
下面我们将会列举 re 模块中使用正则表达式匹配字符串常用的函数(正则方法),正则表达式是正则方法的必要参数,下面将着重介绍基本的正则方法。为了方便理解,将使用最基本的正则表达式(字符精确匹配),由易到难去介绍正则表达式。
Part 3 re正则方法
1、re.match
match 方法尝试从字符串的起始位置匹配根据输入的正则表达式匹配一个符合要求的字符串,如果没有匹配成功的话,match 方法就返回None。也就是说,match 方法只会从字符串索引为 0 的位置匹配指定规则的字符串,即使存在符合规则的字符串但却不是在待处理字符串的开头处,也不会匹配成功。
语法格式
re.match(pattern, string, flags=0)
pattern:匹配的正则表达式,必选参数。
string:要匹配的字符串, 必选参数。
flags:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等,默认值为 0,表示不使用其他的标志。
例如:
import re # 先导入 re 模块
# 匹配字符串"Python Java"中开头处的"Python"
res = re.match("Python", "Python Java")
print(res)
#输出:<re.Match object; span=(0, 6), match='Python'>
# 返回的是一个 match 对象,可以使用 group()方法,获取匹配到的数据
print(res.group())
# 输出:'Python',对应 match 对象中的< match='Python' >
# 匹配字符串"Python Java"中开头处的"Java"
res = re.match("Java", "Python Java")
print(res) # 输出:None 即没有匹配到目标字符串
上面的代码 res = re.match("Python", "Python Java")
中,re.match
表示使用 re 调用 re 模块中的 match 方法;match 方法接收到两个参数,第一个参数 "Python"
表示匹配的正则表达式,第二个参数 "Python Java"
则是正则表达式需要去匹配的字符串,结合 match 方法只从目标字符串开头处进行匹配的特点,这句代码的意思就是:在字符串 "Python Java" 的开头处匹配符合规则 "Python" 的字符串,随后将得到的结果赋值给变量 res
。为了方便,代码中将输出结果以注释的形式附在代码后面。上面的例子中,很容易看出,字符串 "Python Java" 的开头处存在符合正则表达式 "Python" 的字符串,也就是说可以匹配成功,但是匹配结果是一个 match 对象,并不是最终匹配成功的字符串,所以需要使用 match.group()
将匹配结果输出。另外当没有成功匹配时,将不会再返回一个 match 对象,而是 None,此时不可以使用 .group()
进行输出。
2、re.search
扫描整个字符串并返回第一个成功的匹配。即使存在不止一个满足正则表达式的字符串,也只会返回第一个匹配成功的结果。
语法格式
re.search(pattern, string, flags=0)
pattern:匹配的正则表达式,必选参数。
string:要匹配的字符串,必选参数。
flags:标志位。
例如:
# 前面已经导入了 re 库
# 匹配字符串 "Is her name heria?" 中的 "her"
res = re.search("her", "Is her name heria?")
print(res)
# 输出:<re.Match object; span=(3, 6), match='her'>
# 可以知道 search 方法也会返回一个 match 对象
# 观察返回的 match 对象,<span=(3, 6)>代表匹配成功的目标字符串是从
# 待匹配字符串索引为 3 的位置开始,到索引为 6 的位置结束(不包含 6)
# 使用group()获取匹配到的数据,输出:her
print(res.group())
# 匹配不存在的字符串
res = re.search("julia", "Is her name heria?")
print(res) # 输出:None
代码res = re.search("her", "Is her name heria?")
与前面的match
方法类似,区别在于re.search
方法并不只从字符串的开头处匹配,而是可以匹配任意位置,但只返回字符串从左到右第一个匹配成功的字符串。即使字符串"Is her name heria?"
中存在两个"her"
,也只会返回一个结果,因为 search 方法只在乎有没有,并不在乎有哪些或在哪里。需要注意的是,search 方法同样返回一个 match 对象,输出方法与 match 方法一样。
3、re.findall
findall() 函数与前面讲到的 match 和 search 两个函数都不一样, match 和 search 都只匹配一次,而 findall 会匹配所有符合正则表达式规则的字符串;match 和 search 都会返回一个 match 对象,没有匹配成功会返回 None,而 findall 会返回一个列表,每一个成功匹配的字符串都是该列表的一个元素,若没有符合规则的匹配项,就会返回一个空列表。
语法格式
re.findall(pattern, string, flags=0)
pattern:匹配的正则表达式,必选参数。
string:要匹配的字符串,必选参数。
flags:标志位。
例如:
# 匹配字符串"Is her name heria?"中的所有"her"
res = re.findall("her", "Is her name heria?")
print(res)
# 输出:['her', 'her']
# 匹配字符串"Is her name heria?"中的所有"julia"
res = re.findall("julia", "Is her name heria?")
print(res) # 输出:[]
re.findall
方法比较容易理解,这个方法会获取所有符合正则表达式的匹配项,并将它们保存到一个列表中;如果没有成功的匹配项,则会直接返回一个空的列表,返回结果可以直接输出。
4、re.sub
只是匹配出符合规则的字符串或许不是我们的目的,如果想要将符合规则的字符串替换为指定值的时候就可以使用 sub 函数。sub 函数常用于数据清洗中去除脏字符或不需要的内容,它既可以剔除不需要的内容(将不需要的字符替换为空),又可以将指定的字符替换成为我们想要的字符。
语法格式
re.sub(pattern, repl, string, count=0, flags=0)
pattern:匹配的正则表达式,必选参数。
repl:想要替换成的内容,必选参数。
string:要匹配的字符串,必选参数。
count:替换的次数,默认替换所有匹配到的结果;可选参数,默认为 0,为 0 时表示替换所有的匹配项。
flags:标志位。
text = "一九四九年新中国成立了。"
# 将句号替换为感叹号
res = re.sub("。", "!", text)
print(res)
# 输出结果:"一九四九年新中国成立了!"
# 将 "了" 去除
res = re.sub("了", "", text)
print(res)
# 输出结果:"一九四九年新中国成立。"
re.sub
方法有着比其他正则方法更多的输入参数,代码re.sub(pattern, repl, string)
表示将string
中符合正则表达式pattern
的结果替换为参数repl
。另外,count
参数开可以指定替换的最大次数。映射到上面的例子re.sub("。", "!", text)
,就表示将 text 中所有的 "。" 替换为 "!" 。最后返回替换后的参数string
。
5、re.compile
compile 用于编译正则表达式,生成一个正则表达式对象,供 match、 search、findall等函数使用。如果遇到一个正则表达式需要重复使用很多次,处于效率考虑,我们会先把正则先预编译好,接下来重复使用时就不再需要编译这个步骤了,直接匹配,提高我们的效率。
语法格式
re.compile(pattern[, flags])
pattern:匹配的正则表达式,必选参数。
flags:标志位。
# 编译正则表达式(匹配连续4个数字)
rule = re.compile('\d{4}')
res_match = rule.match('01020304').group()
print(res_match) # 输出: 0102
res_search = rule.search('01020304').group()
print(res_search) # 输出: 0102
res_findall = rule.findall('01020304')
print(res_findall) # 输出: ['0102', '0304']
# 将匹配到的4个连续数字替换为'abcd'
res_sub = rule.sub('abcd', '01020304')
print(res_sub) # 输出: abcdabcd
这种方法是把正则方法中的正则表达式单独编译,然后直接使用编译好的正则表达式直接调用正则方法,这样做就不需要再传入正则表达式参数,正则方法会以调用它的正则表达式进行匹配,替换等操作。
Part 4 正则模式和修饰符
1、正则模式
看了前一节的正则方法,你可能会觉得正则表达式不过如此,好像只能替换一些固定的词语,实际上前面所举的例子都是最简单易懂的,使用这些案例的目的是让你快速的了解什么是正则表达式以及不同的正则方法可以做什么。前面讲到简单的使用场景,比如在一段文本中匹配或替换一些指定的文本;后面在了解re.compile函数时使用了一个匹配非一对一的匹配连续4位数字的正则表达式"\d{4}"
,这有可能让你感到新鲜,其实这是字符串转义符的妙用。\d
在正则表达式表示阿拉伯数字,{4}
则用来限定\d
的位数。正则表达式中不仅使用转义符构建正则规则,还能通过使用一些特殊符号来表示正则表达式规则,这些转义组合和符号就是正则表达式模式。常见的模式如下表所示:
模式 | 描述 |
\w | 匹配字母,数字及下划线 |
\W | 匹配非字母,数字及下划线,与 |
\s | 匹配任意空白字符,等价于 [\t\n\r\f] |
\S | 匹配任意非空字符,与 |
\d | 匹配任意数字,等价于 [0-9] |
\D | 匹配任意非数字,与 |
\A | 匹配字符串开始 |
\Z | 匹配字符串结束,如果存在换行符,只匹配到换行符前的结束字符串 |
\z | 匹配字符串结束 |
\G | 匹配最后匹配完成的位置 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾 |
. | 匹配任意字符,除了换行符 |
[...] | 用来表示一组字符,单独列出:[amk] 匹配 'a','m' 或 'k' |
[^...] | 不在 [] 中的字符:[^abc]匹配除了 'a', 'b', 'c' 之外的字符。 |
* | 匹配 0 个或多个的表达式。 |
+ | 匹配 1 个或多个的表达式。 |
? | 匹配 0 个或 1 个由前面的正则表达式定义的片段,非贪婪方式 |
{n} | 精确匹配 n 个前面表达式。 |
{n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
a|b | 匹配 a 或 b |
() | 匹配括号内的表达式,也表示一个组 |
例如:
STR = "(5) 和 (7) 相加得到 (12)"
# 匹配所有一位数字
print(re.findall('\d', STR))
# 输出:['5', '7', '1', '2']
# 将所有的空格字符替换为空字符,即剔除所有空格字符
print(re.sub('\s', '', STR))
# 输出:"(5)和(7)相加得到(12)"
# 匹配字符 7 或字符 12
print(re.findall('7|12', STR))
# 输出:['7', '12']
# 非贪婪模式匹配所有括号内的内容
print(re.findall('\(.*?\)', STR))
# 输出:['(5)', '(7)', '(12)']
2、正则修饰符
前面讲到 re.match、re.search、re.findall 函数时都会讲到一个标志位参数 flags,实际上这是一个修饰正则匹配的参数,参数范围和功能如下表所示:
修饰符 | 描述 |
re.A | 让 |
re.I | 使匹配对字母大小写不敏感 |
re.M | 多行模式,当某字符串中有换行符 |
re.S | 使正则模式 '.' 匹配包括换行符在内的所有字符 |
re.U | 根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B. |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。 |
3、贪婪模式和非贪婪模式
-
非贪婪模式:正则表达式趋向于匹配最小长度,即一旦匹配到结果就结束。
-
贪婪模式:正则表达式趋向于匹配最大长度,即匹配到最长的一项才结束.
re 中默认的匹配方式是贪婪模式。
import re
'''贪婪模式和非贪婪模式'''
re_str = '11-22-11-22-11'
# 正则表达式默认为贪婪模式
# '.*' 表示匹配 0 个或多个 任意字符
A = re.search('11.*11', re_str).group()
print(A)
# (贪婪模式)输出结果: 11-22-11-22-11
# 加上 '?' 设置为非贪婪模式
B = re.search('11.*?11', re_str).group()
print(B)
# (非贪婪模式)结果: 11-22-11
'''转义符'''
# Python中字符串前面加上 'r' 或 'R' 表示原生字符串,即所有转义符都不起作用
str1 = "C:\\a\\b\\c"
print(str1)
# print输出会对反斜杠进行了转义,输出结果:C:\a\b\c
# 那么如果需要匹配字符串 C:\\a 的话,那么匹配规则就要写 C:\\\\a
# 因为对转义符 "\" 进行转义,才能单纯地表示符号 "\"
ret_1 = re.match("C:\\\\a", str1).group()
print(ret_1)
# 输出结果: C:\a
# 因为匹配到了结果 'C:\\a',输出时转义符生效,输出为 'C:\a'
# 在匹配规则前面加 r,表示取消转义,转义符不再起作用。
# 那么就只是要写 C:\\a 就可以匹配字符串 C:\\a
ret_2 = re.match(r"C:\\a", str1).group()
print(ret_2)
# 结果: C:\a,匹配到了结果 'C:\\a',输出时转义符生效,输出为 'C:\a'
Part 5 扩展
正则表达式的应用很广泛,正确地使用正则表达式可以从大量的文本中找出重要的信息。本文是以初学者角度介绍 Python 正则表达式和正则方法。所介绍的都是较为简单的例子。实际上,正则表达式可以匹配更复杂的字符串,也可以匹配日常生活中实用的文本。例如:
-
匹配所有汉字的正则表达式:
"[\\u4e00-\\u9fa5]"
-
匹配中国邮政编码的正则表达式:
"[1-9]\d{5}(?!\d)"
-
匹配手机E-mail 邮箱的正则表达式:
r"1\d{10}|[a-z0-9.\-+_]+@[a-z0-9.\-+_]+\.[a-z]+"
-
……