《自然语言处理实战入门》 第二章:NLP 前置技术----正则表达式


简介

一个正则表达式(或RE)指定了一集与之匹配的字符串;模块内的函数可以让你检查某个字符串是否跟给定的正则表达式匹配(或者一个正则表达式是否匹配到一个字符串,这两种说法含义相同)。

正则表达式是处理字符串的强大工具,拥有独特的语法和独立的处理引擎。

我们在大文本中匹配字符串时,有些情况用str自带的函数(比如find, in)可能可以完成,有些情况会稍稍复杂一些(比如说找出所有“格式类似邮箱”的字符串,所有和python相关的句子),这个时候我们需要一个可根据模式提取字串的工具,这个时候正则表达式就派上用场了。


python 字符串操作

在了解正则表达式之前,我们可以先了解python 字符串的相关操作方式,毕竟多数情况下字符串的api 就可以解决大多数问题。

在 Python 中处理文本数据是使用 str 对象,也称为 字符串。 字符串是由 Unicode 码位构成的不可变 序列。 字符串字面值有多种不同的写法:

  • 单引号: ’ 允许包含有 “双” 引号’

  • 双引号: " 允许包含有 ‘单’ 引号"

  • 三重引号: ’ ’ ’ 三重单引号 ’ ’ ', " " " 三重双引号 " " "

使用三重引号的字符串可以跨越多行 —— 其中所有的空白字符都将包含在该字符串字面值中。

python的字符串内建函数

方法 描述
string.capitalize() 把字符串的第一个字符大写
string.center(width) 返回一个原字符串居中,并使用空格填充至长度 width 的新字符串
string.count(str, beg=0, end=len(string)) 返回 str 在 string 里面出现的次数,如果 beg 或者 end 指定则返回指定范围内 str 出现的次数
string.decode(encoding='UTF-8', errors='strict') 以 encoding 指定的编码格式解码 string,如果出错默认报一个 ValueError 的 异 常 , 除非 errors 指 定 的 是 'ignore' 或 者'replace'
string.encode(encoding='UTF-8', errors='strict') 以 encoding 指定的编码格式编码 string,如果出错默认报一个ValueError 的异常,除非 errors 指定的是'ignore'或者'replace'
string.endswith(obj, beg=0, end=len(string)) 检查字符串是否以 obj 结束,如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束,如果是,返回 True,否则返回 False.
string.expandtabs(tabsize=8) 把字符串 string 中的 tab 符号转为空格,tab 符号默认的空格数是 8。
string.find(str, beg=0, end=len(string)) 检测 str 是否包含在 string 中,如果 beg 和 end 指定范围,则检查是否包含在指定范围内,如果是返回开始的索引值,否则返回-1
string.format() 格式化字符串
string.index(str, beg=0, end=len(string)) 跟find()方法一样,只不过如果str不在 string中会报一个异常.
string.isalnum() 如果 string 至少有一个字符并且所有字符都是字母或数字则返 回 True,否则返回 False
string.isalpha() 如果 string 至少有一个字符并且所有字符都是字母则返回 True, 否则返回 False
string.isdecimal() 如果 string 只包含十进制数字则返回 True 否则返回 False.
string.isdigit() 如果 string 只包含数字则返回 True 否则返回 False.
string.islower() 如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False
string.isnumeric() 如果 string 中只包含数字字符,则返回 True,否则返回 False
string.isspace() 如果 string 中只包含空格,则返回 True,否则返回 False.
string.istitle() 如果 string 是标题化的(见 title())则返回 True,否则返回 False
string.isupper() 如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False
string.join(seq) 以 string 作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串
string.ljust(width) 返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串
string.lower() 转换 string 中所有大写字符为小写.
string.lstrip() 截掉 string 左边的空格
string.maketrans(intab, outtab]) maketrans() 方法用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。
max(str) 返回字符串 str 中最大的字母。
min(str) 返回字符串 str 中最小的字母。
string.partition(str) 有点像 find()和 split()的结合体,从 str 出现的第一个位置起,把 字 符 串 string 分 成 一 个 3 元 素 的 元 组 (string_pre_str,str,string_post_str),如果 string 中不包含str 则 string_pre_str == string.
string.replace(str1, str2,  num=string.count(str1)) 把 string 中的 str1 替换成 str2,如果 num 指定,则替换不超过 num 次.
string.rfind(str, beg=0,end=len(string) ) 类似于 find()函数,不过是从右边开始查找.
string.rindex( str, beg=0,end=len(string)) 类似于 index(),不过是从右边开始.
string.rjust(width) 返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串
string.rpartition(str) 类似于 partition()函数,不过是从右边开始查找
string.rstrip() 删除 string 字符串末尾的空格.
string.split(str="", num=string.count(str)) 以 str 为分隔符切片 string,如果 num 有指定值,则仅分隔 num+ 个子字符串
string.splitlines([keepends]) 按照行('\r', '\r\n', \n')分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。
string.startswith(obj, beg=0,end=len(string)) 检查字符串是否是以 obj 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查.
string.strip([obj]) 在 string 上执行 lstrip()和 rstrip()
string.swapcase() 翻转 string 中的大小写
string.title() 返回"标题化"的 string,就是说所有单词都是以大写开始,其余字母均为小写(见 istitle())
string.translate(str, del="") 根据 str 给出的表(包含 256 个字符)转换 string 的字符, 要过滤掉的字符放到 del 参数中
string.upper() 转换 string 中的小写字母为大写
string.zfill(width) 返回长度为 width 的字符串,原字符串 string 右对齐,前面填充0

python 字符操作实例:


# 一些str 操作 实例

>>> 'Py' in 'Python'
True


>>> '   spacious   '.lstrip()
'spacious   '
>>> 'www.example.com'.lstrip('cmowz.')
'example.com'


参考文献:


正则表达式的相关原理

python属于perl风格,属于传统型NFA引擎,与此相对的是POSIX NFA和DFA等引擎。

NFA是基于表达式主导的引擎,同时,传统型NFA引擎会在找到第一个符合匹配的情况下立即停止:即得到匹配之后就停止引擎。

而POSIX NFA 中不会立刻停止,会在所有可能匹配的结果中寻求最长结果。这也是有些bug在传统型NFA中不会出现,但是放到后者中,会暴露出来。

引申一点,NFA学名为”非确定型有穷自动机“,DFA学名为”确定型有穷自动机“
在这里插入图片描述


python 正则表达式

我们进行使用正则表达式的时候,最好的参考资料就是python的官方文档。

python 官方文档 re — 正则表达式操作

python re模块
Python通过re模块提供对正则表达式的支持。

使用re的一般步骤是

1.将正则表达式的字符串形式编译为Pattern实例
2.使用Pattern实例处理文本并获得匹配结果(一个Match实例)
3.使用Match实例获得信息,进行其他的操作。

一个简单的测试脚本,比较不同正则表达式写法的优缺点

#在测试表达式的效率时,可借助以下代码比较所需时间。在两个可能的结果中择期优者。
import re
import time
re_lx1 = re.compile(r'your_re_1')
re_lx2 = re.compile(r'your_re_2')

starttime = time.time()
repeat_time = 100
for i in range(repeat_time):
    s='test text'*10000
    result = re_lx1.search(s)
time1 = time.time()-starttime
print(time1)

starttime = time.time()
for i in range(repeat_time):
    s='test text'*10000
    result = re_lx2.search(s)
time2 = time.time()-starttime
print(time2)


应用举例

以寺庙相关信息的抽取为例,data extraction实战,让我们直观体验一下构建知识图谱中的基础性工作,知识抽取,从非结构化数据中抽取结构化内容,这和我们大数据领域中首当其冲的数据清洗步骤是不谋而合的。

给出一段寺庙信息 ,文本实例

*************************************************************************
title:大报国慈仁寺

大报国慈仁寺,俗称报国寺,位于北京市西城区,在广安门内大街路北。
经考证报国寺始建于辽代;明代塌毁,成化二年(1466年)重修,改名慈仁寺,俗称报国寺;清乾隆十九年(1754年)重修,更名为大报国慈仁寺。曾有七进院落,七层殿堂,后有毗卢阁,为当时北京南城最大庙宇。1900年因义和团在此寺设坛,被八国联军用炮轰毁。现全寺已修整一新,辟作“报国寺文化市场”,成为中国收藏活动著名的聚集地。
明清之际学者顾炎武(字亭林)在北京时曾住该寺西院。道光二十三年(1843年)改西院为顾亭林祠。如今在各种古旧书籍、钱币邮票、古玩首饰等的商摊中,祠堂已不可见,只余《顾亭林先生祠记》和《重建顾亭林先生祠记》两块碑文记载当年旧事。
目前每周四为报国寺文化市场交易日。

*************************************************************************
title:法门寺

法门寺,又称法云寺、阿育王寺,位于中国陕西省宝鸡市扶风县城北10公里处的法门镇。始建于东汉末年桓灵年间,距今约有1700多年历史,有“关中塔庙始祖”之称。法门寺因舍利而置塔,因塔而建寺,原名阿育王寺。释迦牟尼佛灭度后,遗体火化结成舍利。1980年以来,法门寺在前任方丈澄观、净一法师的住持下,相继建成大雄宝殿、玉佛殿、禅堂、祖堂、斋堂、寮房、佛学院等仿唐建筑。现任主持为中国佛教协会副会长学诚法师。
=== 建寺 ===
关于建寺时间,从唐代时就已无法准确确定了。有一种说法认为法门寺及真身宝塔始建于古印度孔雀王朝阿育王(前273年~前232年)时期。阿育王统一印度后为了弘扬佛法,将佛的舍利分送世界各地,兴建八万四千塔。中国有十九处,法门寺为第五处,先建塔后建寺。北周以前法门寺名为阿育王寺,寺塔名为阿育王塔。另一种说法受到了出土的汉代瓦当、砖刻的支持,认为法门寺建于东汉桓灵之世。
公元558年,北魏皇室后裔拓跋育曾扩建,并于元魏二年(494年)首次开塔瞻礼舍利。


#-*-coding:utf-8-*-

import re

def read_file(filename):
    with open(filename, encoding='utf-8') as fd: 
        for line in fd: 
            yield line

if __name__== "__main__":

    filename = "templeArticles.txt"
    title = re.compile('^title:')
    weiyu = re.compile('位于([\\S]+)(,|。){0,1}')
    shijianyu = re.compile('始建于(((?!,|;|。).)+)(,|;|。)')

    for line in read_file(filename):
        # 处理文件每一行文件

        if re.match(title,line):
            print(line[6:-1])
            continue
        if re.findall(weiyu,line):
            print('位于: ' + re.findall(weiyu,line)[0][0])
            continue
        if re.findall(shijianyu,line):
            print('始建于:' + re.findall(shijianyu,line)[0][0])
            continue


结果
在这里插入图片描述


常用正则表达式

一、校验数字的表达式

数字:^[0-9]*$
n位的数字:^\d{n}$
至少n位的数字:^\d{n,}$
m-n位的数字:^\d{m,n}$
零和非零开头的数字:^(0|[1-9][0-9]*)$
非零开头的最多带两位小数的数字:^([1-9][0-9]*)+(\.[0-9]{1,2})?$
带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})$
正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$
有两位小数的正实数:^[0-9]+(\.[0-9]{2})?$
有1~3位小数的正实数:^[0-9]+(\.[0-9]{1,3})?$
非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\+?[1-9][0-9]*$
非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
非负整数:^\d+$ 或 ^[1-9]\d*|0$
非正整数:^-[1-9]\d*|0$ 或 ^((-\d+)|(0+))$
非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$
非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$
正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$
负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$
浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$

校验字符的表达式

汉字:^[\u4e00-\u9fa5]{0,}$
英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
长度为3-20的所有字符:^.{3,20}$
由26个英文字母组成的字符串:^[A-Za-z]+$
由26个大写英文字母组成的字符串:^[A-Z]+$
由26个小写英文字母组成的字符串:^[a-z]+$
由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22]+
禁止输入含有~的字符:[^~\x22]+

三、特殊需求表达式


Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?
InternetURL:[a-zA-z]+://[^\s]* 或 ^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
电话号码("XXX-XXXXXXX""XXXX-XXXXXXXX""XXX-XXXXXXX""XXX-XXXXXXXX""XXXXXXX""XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1}))$)
身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)
帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$
强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
日期格式:^\d{4}-\d{1,2}-\d{1,2}
一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$

钱的输入格式:
有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "" 的 "10000" 和 "10,000":^[1-9][0-9]*$
这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9]+(.[0-9]{2})?$
这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$
中文字符的正则表达式:[\u4e00-\u9fa5]
双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
空白行的正则表达式:\n\s*\r (可以用来删除空白行)
HTML标记的正则表达式:<(\S*?)[^>]*>.*?|<.*? /> ( 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))


正则表达式在线测试工具

通过使用 一些在线工具我们可以对自己的正则表达式进行调试,这是非常方便的。样例中匹配:正数、负数、和小数

import re
pattern = re.compile(ur'^(\-|\+)?\d+(\.\d+)?$')
str = u''
print(pattern.search(str))

在这里插入图片描述

正则表达式在线验证工具: http://regexr.com/

正则表达式试题

菜鸟工具:正则表达式在线测试

在线正则表达式测试 - OSChina


参考文献

菜鸟教程:python正则表达式

Python下的正则表达式原理和优化笔记

正则表达式引擎执行原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shiter

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值