假设你有一个无聊的任务,要在一篇长的网页或文章中,找出所有电话号码和
邮件地址。如果手动翻页,可能需要查找很长时间。如果有一个程序,可以在剪贴板的文本中查找电话号码和 E-mail 地址,那你就只要按一下Ctrl-A 选择所有文本,按下Ctrl-C
将它复制到剪贴板,然后运行你的程序。它会用找到的电话号码和 E-mail地址,替换掉剪贴板中的文本。
当你开始接手一个新项目时,很容易想要直接开始写代码。但更多的时候,最好是后退一步,考虑更大的图景。我建议先草拟高层次的计划,弄清楚程序需要做什么。暂时不要思考真正的代码,稍后再来考虑。现在,先关注大框架。
例如,你的电话号码和E-mail 地址提取程序需要完成以下任务:
• 从剪贴板取得文本。
• 找出文本中所有的电话号码和 E-mail 地址。
• 将它们粘贴到剪贴板。
现在你可以开始思考,如何用代码来完成工作。代码需要做下面的事情:
• 使用pyperclip 模块复制和粘贴字符串。
• 创建两个正则表达式,一个匹配电话号码,另一个匹配 E-mail 地址。
• 对两个正则表达式,找到所有的匹配,而不只是第一次匹配。
• 将匹配的字符串整理好格式,放在一个字符串中,用于粘贴。
• 如果文本中没有找到匹配,显示某种消息。
这个列表就像项目的路线图。在编写代码时,可以独立地关注其中的每一步每一步都很好管理。它的表达方式让你知道在Python 中如何去做。
第 1 步:为电话号码创建一个正则表达式
首先,你需要创建一个正则表达式来查找电话号码。创建一个新文件,输入以下代码,保存为 phoneAndEmail.py:
#! python3
# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard. import
pyperclip, re
phoneRegex = re.compile(r'''( (\d{3}|\(\d{3}\))? # area code
(\s|-|\.)? # separator
(\d{3}) # first 3 digits
(\s|-|\.) # separator
(\d{4}) # last 4 digits
(\s*(ext|x|ext.)\s*(\d{2,5}))? # extension
)''', re.VERBOSE)
# TODO: Create email regex.
# TODO: Find matches in clipboard text. # TODO: Copy results to the clipboard.
TODO 注释仅仅是程序的框架。当编写真正的代码时,它们会被替换掉。
电话号码从一个“可选的”区号开始,所以区号分组跟着一个问号。因为区号可能只是 3 个数字(即\d{3}),或括号中的 3
个数字(即\(\d{3}\)),所以应该用管道符号连接这两部分。可以对这部分多行字符串加上正则表达式注释# Area code,帮助你记忆(\d{3}|\(\d{3}\))?要匹配的是什么。
电话号码分割字符可以是空格(\s)、短横(-)或句点(.),所以这些部分也应该用管道连接。这个正则表达式接下来的几部分很简单:3 个数字,接下来是另一个分割符,接下来是 4
个数字。最后的部分是可选的分机号,包括任意数目的空格,接着 ext、x 或 ext.,再接着 2 到 5 位数字。
第 2 步:为 E-mail 地址创建一个正则表达式
还需要一个正则表达式来匹配E-mail 地址。让你的程序看起来像这样:
#! python3
# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard. import
pyperclip, re
phoneRegex = re.compile(r'''(
--snip--
# Create email regex. emailRegex = re.compile(r'''(
➊ [a-zA-Z0-9._%+-]+ # username
➋ @ # @ symbol
➌ [a-zA-Z0-9.-]+ # domain name (\.[a-zA-Z]{2,4})
# dot-something
)''', re.VERBOSE)
# TODO: Find matches in clipboard text. # TODO: Copy results to the clipboard.
E-mail 地址的用户名部分➊是一个或多个字符,字符可以包括:小写和大写字
母、数字、句点、下划线、百分号、加号或短横。可以将所有这些放入一个字符分类:[a-zA-Z0-9._%+-]。
域名和用户名用@符号分割➋,域名➌允许的字符分类要少一些,只允许字母、数字、句点和短横:[a-zA-Z0-9.-]。最后是“dot-com”部分(技术上
称为“顶级域名”),它实际上可以是“dot-anything”。它有 2 到 4 个字符。
E-mail 地址的格式有许多奇怪的规则。这个正则表达式不会匹配所有可能的、有效的 E-mail 地址,但它会匹配你遇到的大多数典型的电子邮件地址。
第 3 步:在剪贴板文本中找到所有匹配
既然已经指定了电话号码和电子邮件地址的正则表达式,就可以让 Python 的
re模块做辛苦的工作,查找剪贴板文本中所有的匹配。pyperclip.paste()函数将取得一个字符串,内容是剪贴板上的文本,findall()正则表达式方法将返回一个元组的列表。
让你的程序看起来像这样:
#! python3
# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard. import
pyperclip, re
phoneRegex = re.compile(r'''(
--snip--
# Find matches in clipboard text. 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])
# TODO: Copy results to the clipboard.
每个匹配对应一个元组,每个元组包含正则表达式中每个分组的字符串。回忆一下,分组 0 匹配整个正则表达式,所以在元组下标 0 处的分组,就是你感兴趣的内容。
在➊处可以看到,你将所有的匹配保存在名为 matches 的列表变量中。它从一个空列表开始,经过几个 for 循环。对于E-mail 地址,你将每次匹配的分组 0
添加到列表中➌。对于匹配的电话号码,你不想只是添加分组 0。虽然程序可以“检测”几种不同形式的电话号码,你希望添加的电话号码是唯一的、标准的格式。 phoneNum
变量包含一个字符串,它由匹配文本的分组 1、3、5 和 8 构成➋。(这些分组是区号、前 3 个数字、后 4 个数字和分机号。)
第 4 步:所有匹配连接成一个字符串,复制到剪贴板
现在,E-mail 地址和电话号码已经作为字符串列表放在 matches 中,你希望将它们复制到剪贴板。pyperclip.copy()函数只接收一个字符串值,而不是字符串的列表,所以你在
matches 上调用 join()方法。
为了更容易看到程序在工作,让我们将所有找到的匹配都输出在终端上。如果没有找到电话号码或 E-mail 地址,程序应该告诉用户。
让你的程序看起来像这样:
#! python3
# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard.
--snip--
for groups in emailRegex.findall(text): matches.append(groups[0])
# Copy results to the clipboard. 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.')
第 5 步:运行程序
作为一个例子,打开你的 Web 浏览器,访问 No Starch Press 的联系页面 http://www.nostarch.com/contactus.htm。按下 Ctrl-A
选择该页的所有文本,按下 Ctrl-C将它复制到剪贴板。运行这个程序,输出看起来像这样:
Copied to clipboard:
800-420-7240
415-863-9900
415-863-9950
info@nostarch.com media@nostarch.com academic@nostarch.com help@nostarch.com
第 6 步:类似程序的构想
识别文本的模式(并且可能用 sub()方法替换它们)有许多不同潜在的应用。
• 寻找网站的URL,它们以 http://或 https://开始。
• 整理不同日期格式的日期(诸如 3/14/2015、03-14-2015 和 2015/3/14),用唯一的标准格式替代。
• 删除敏感的信息,诸如社会保险号或信用卡号。
• 寻找常见打字错误,诸如单词间的多个空格、不小心重复的单词,或者句子末尾处多个感叹号。它们很烦人!!
7.16 小结
虽然计算机可以很快地查找文本,但你必须精确地告诉它要找什么。正则表达
式让你精确地指明要找的文本模式。实际上,某些文字处理和电子表格应用提供了查找替换功能,让你使用正则表达式进行查找。
Python 自带的 re 模块让你编译 Regex 对象。该对象有几种方法:search()查找单词匹配,findall()查找所有匹配实例,sub()对文本进行查找和替换。
除本章介绍的语法以外,还有一些正则表达式语法。你可以在官方 Python 文档中找到更多内容:http://docs.python.org/3/library/re.html。指南网站
http://www.regular- expressions.info/也是很有用的资源。
既然已经掌握了如何操纵和匹配字符串,接下来就该学习如何在计算机硬盘上读写文件了。
7.17 习题
1.创建 Regex 对象的函数是什么?
2.在创建Regex 对象时,为什么常用原始字符串?
3.search()方法返回什么?
4.通过 Match 对象,如何得到匹配该模式的实际字符串?
5.用 r'(\d\d\d)-(\d\d\d-\d\d\d\d)'创建的正则表达式中,分组 0 表示什么?分组 1
呢?分组 2 呢?
6.括号和句点在正则表达式语法中有特殊的含义。如何指定正则表达式匹配
真正的括号和句点字符?
7.findall()方法返回一个字符串的列表,或字符串元组的列表。是什么决定它提供哪种返回?
8.在正则表达式中,|字符表示什么意思?
9.在正则表达式中,?字符有哪两种含义?
10.在正则表达式中,+和*字符之间的区别是什么?
11.在正则表达式中,{3}和{3,5}之间的区别是什么?
12.在正则表达式中,\d、\w 和\s 缩写字符类是什么意思?
13.在正则表达式中,\D、\W 和\S 缩写字符类是什么意思?
14.如何让正则表达式不区分大小写?
15.字符.通常匹配什么?如果 re.DOTALL 作为第二个参数传递给 re.compile(),它会匹配什么?
16..*和*?之间的区别是什么?
17.匹配所有数字和小写字母的字符分类语法是什么?
18.如果 numRegex = re.compile(r'\d+'),那么 numRegex.sub('X', '12 drummers, 11 pipers, five rings, 3
hens')返回什么?
19.将 re.VERBOSE 作为第二个参数传递给 re.compile(),让你能做什么?
20.如何写一个正则表达式,匹配每 3 位就有一个逗号的数字?它必须匹配以下数字:
• '42'
• '1,234'
• '6,368,745'
但不会匹配:
• '12,34,567' (逗号之间只有两位数字)
• '1234' (缺少逗号)
21.如何写一个正则表达式,匹配姓 Nakamoto 的完整姓名?你可以假定名字总是出现在姓前面,是一个大写字母开头的单词。该正则表达式必须匹配:
• 'Satoshi Nakamoto'
• 'Alice Nakamoto'
• 'RoboCop Nakamoto'
但不匹配:
• 'satoshi Nakamoto'(名字没有大写首字母)
• 'Mr. Nakamoto'(前面的单词包含非字母字符)
• 'Nakamoto' (没有名字)
• 'Satoshi nakamoto'(姓没有首字母大写)
22.如何编写一个正则表达式匹配一个句子,它的第一个词是 Alice、Bob 或
Carol,第二个词是 eats、pets 或 throws,第三个词是 apples、cats 或 baseballs。该句子以句点结束。这个正则表达式应该不区分大小写。它必须匹配:
• 'Alice eats apples.'
• 'Bob pets cats.'
• 'Carol throws baseballs.'
• 'Alice throws Apples.'
• 'BOB EATS CATS.'
但不匹配:
• 'RoboCop eats apples.'
• 'ALICE THROWS FOOTBALLS.'
• 'Carol eats 7 cats.'
.18 实践项目
作为实践,编程完成下列任务。
7.18.1 强口令检测
写一个函数,它使用正则表达式,确保传入的口令字符串是强口令。强口令的定义是:长度不少于 8 个字符,同时包含大写和小写字符,至少有一位数字。你可能需要用多个正则表达式来测试该字符串,以保证它的强度。
7.18.2 strip()的正则表达式版本
写一个函数,它接受一个字符串,做的事情和 strip()字符串方法一样。如果只传入了要去除的字符串,没有其他参数,那么就从该字符串首尾去除空白字符。否则,函数第二个参数指定的字符将从该字符串中去除。