详细教程见《Automate the boring stuff with Python》。
本文是正则表达式的一个简答应用,配合pyperclip最终实现:当有文本复制到剪贴板后,运行程序,便可打印出其中包含的电话号码和邮箱。
正则表达式的基本语法
1.步骤
想要识别出一段文本中的号码,当然可以写个函数。号码一般是有规律的,比如415-555-4242(美国),第一部分是地区号,后面接3个数字,再接4个数字。如果定义一个函数,先检查这串字符长度是不是12,然后用一系列 if 和 for 循环检查1、2、3位是不是数字,4位是不是连字符,5,6,7位是不是数字......也行,但是函数冗长,显得很笨拙。
既然号码是有规律的,那么只要能和计算机约定这种规律,让它也按照我们认识到的规律去查找不就行了?因此,正则表达式,可以理解为“约定某种规则”的表达式。它的英文名也确实如此,“Regular Expression”。
假定用“\d”代表一个从0-9的数字(digit),那么上面的号码就可以表示为“\d\d\d-\d\d\d-\d\d\d\d“
在Python中需要先引入 re 模块,然后使用其中的 compile 方法约定这种规则。
import re
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
(Regex是正则表达式的缩写,r' '代表raw string原始字符串,可以无视字符串中的转义符)
对正则表达式使用search方法,会自动寻找输入文本中匹配的对象,创造出一个match对象。这里把它赋给“mo”。
mo = phoneNumRegex.search('My number is 415-555-4242.')
注意mo不是一个变量,而是个match对象,直接打印是不能看到结果的,通过group方法才可以看到结果。
>>>print('Phone number found: ' + mo.group())
Phone number found: 415-555-4242
综上,创建一个regex匹配有四步:
①引入re模块。
②用re模块里的compile函数,创建一个regex对象。
③把文本传递给regex对象的search方法,返回一个match对象。
④通过match对象的group方法访问结果。
2.括号分组
号码是由几部分组成的,在正则表达式中也能通过“()”约定好分组,然后在group()方法中输入对应的编号,访问对应的结果。
>>>phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
>>>mo = phoneNumRegex.search('My number is 415-555-4242.')
>>>mo.group(1)
'415'
>>>mo.group(2)
'555-4242'
>>>mo.group(0)
'415-555-4242'
>>>mo.group()
'415-555-4242'
想看到完整的分组结果,可以用groups方法。
>>>mo.groups()
('415','555-4242')
>>>areaCode,mainNumber = mo.groups()
>>>print(areaCode)
415
>>>print(mainNumber)
555-4242
groups方法返回一个元组(tuple),所以可以把其中的两个值分别赋给两个变量areaCode和mainNumber。
3.其它约定
号码的地区号是可有可无的,有的部分可能还更长一点,邮箱是由数字和字母组成的,又该如何在raw string中约定好这种规则呢?
以下是其它约定。
- ? 放在一组括号的后面,表示这一部分可有可无。
- * 表示这一部分可以有任意个。
- + 表示这一部分有一个或多个。
- {n} 表示这一部分出现了n次。
- {n,m} 表示这一部分出现了n到m次。
- 上面几种方式默认查找符合条件的最长的一串字符,如果要最短的,再加上一个 ?
- ^xx 表示字符串必须以xx开头
- xx$ 表示字符串必须以xx结尾
- . 表示任意一个字符,除了空行字符。(.at 可以匹配cat,hat,rat,匹配flat的话结果是lat)
- \d 代表数字,\s 代表空格,\w 代表字母。(如果大写\D\S\W,就代表除了数字、空格、字母的任意字符)
- [abc] 代表方括号中出现的任意字符。(如果写成[a-zA-z]就表示a到z、A到Z的任意字母,所以[0-9]也可以表示数字)
- [^abc] 代表任何不是abc的字符。
- 如果要表示括号,句号本身,加个反斜杠。如:\(xx\)表示(xx)
- 竖号又称pipe,“|”,表示“或”,a|b 表示找到两者之一即可。
4.组织复杂的正则表达式
约定的规则一般是很长的,可以用以下方式实现分行加注释。后面的re.VERBOSE开启verbose模式,会自动忽视raw string中的空格和注释。
phoneRegex = re.compile(r'''(
(\d{3}|\(\d{3}\))? #地区号
(\s|-\.)? #连字符
(\d{3}) #三个数字
(\s|-\.) #连字符
(\d{4}) #四个数字
(\s*(ext|x|ext.)\s*(\d{2,5}))? #拓展号码
)''',re.VERBOSE)
开始吧
宏观上思考,提取号码和邮箱分为以下三个步骤:
- 从剪贴板上获取文本
- 找到文本中所有的号码和邮箱
- 把它们粘贴到剪贴板上
那么具体代码的实现,应该包含这些:
- 使用pyperclip模块完成字符串的拷贝和粘贴。
- 创建两个正则表达式,一个用于匹配号码,一个用于匹配邮箱。
- 找出两个正则表达式的所有匹配结果。
- 把所有结果工整排版,放进一个字符串里,方便粘贴。
- 如果没有找到匹配的话,展示某些信息。
代码如下:(注意:①安装pyperclip模块②“-”.join方法用于以“-”连字符把几串字符连接起来。)
#! python3
# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard.
import pyperclip,re
#创建两个正则表达式
phoneRegex = re.compile(r'''(
(\d{3}|\(\d{3}\))? #地区号
(\s|-\.)? #连字符
(\d{3}) #三个数字
(\s|-\.) #连字符
(\d{4}) #四个数字
(\s*(ext|x|ext.)\s*(\d{2,5}))? #拓展号码
)''',re.VERBOSE)
emailRegex = re.compile(r'''(
[a-zA-Z0-9.%+-]+ #用户名
@ #@符号
[a-zA-z0-9.-]+ #域名
(\.[a-zA-z]{2,4}) #.com等等
)''',re.VERBOSE)
#找出文本中的匹配
text = str(pyperclip.paste())
matches = []
for groups in phoneRegex.findall(text):
phoneNum = '-'.join([groups[1],groups[3],groups[5]])
if groups[8] != '':
phoneNum += 'x' + groups[8]
matches.append(phoneNum)
for groups in emailRegex.findall(text):
matches.append(groups[0])
#把结果拷贝到剪贴板上
if len(matches) > 0:
pyperclip.copy('\n'.join(matches))
print('Copied to clipboard:')
print('\n'.join(matches))
else:
print('No phone numbers or email addresses found.')