`
runanli
  • 浏览: 44400 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

高级正则表达式技术(Python版)

 
阅读更多

本文由 伯乐在线 - atupal 翻译自 Ajay Kumar N。欢迎加入技术翻译小组。转载请参见文章末尾处的要求。

正则表达式是从信息中搜索特定的模式的一把瑞士军刀。它们是一个巨大的工具库,其中的一些功能经常被忽视或未被充分利用。今天我将向你们展示一些正则表达式的高级用法。

举个例子,这是一个我们可能用来检测电话美国电话号码的正则表达式:

r'^(1[-\s.])?(\()?\d{3}(?(2)\))[-\s.]?\d{3}[-\s.]?\d{4}$'

我们可以加上一些注释和空格使得它更具有可读性。

r'^'
r'(1[-\s.])?' # optional '1-', '1.' or '1'
r'(\()?'      # optional opening parenthesis
r'\d{3}'      # the area code
r'(?(2)\))'   # if there was opening parenthesis, close it
r'[-\s.]?'    # followed by '-' or '.' or space
r'\d{3}'      # first 3 digits
r'[-\s.]?'    # followed by '-' or '.' or space
r'\d{4}$'    # last 4 digits
 

让我们把它放到一个代码片段里:

import re
 
numbers = [ "123 555 6789",
            "1-(123)-555-6789",
            "(123-555-6789",
            "(123).555.6789",
            "123 55 6789" ]
 
for number in numbers:
    pattern = re.match(r'^'
                   r'(1[-\s.])?'           # optional '1-', '1.' or '1'
                   r'(\()?'                # optional opening parenthesis
                   r'\d{3}'                # the area code
                   r'(?(2)\))'             # if there was opening parenthesis, close it
                   r'[-\s.]?'              # followed by '-' or '.' or space
                   r'\d{3}'                # first 3 digits
                   r'[-\s.]?'              # followed by '-' or '.' or space
                   r'\d{4}$\s*',number)    # last 4 digits
 
    if pattern:
        print '{0} is valid'.format(number)
    else:
        print '{0} is not valid'.format(number)
 

输出,不带空格:

123 555 6789 is valid
1-(123)-555-6789 is valid
(123-555-6789 is not valid
(123).555.6789 is valid
123 55 6789 is not valid
 

正则表达式是 python 的一个很好的功能,但是调试它们很艰难,而且正则表达式很容易就出错。

幸运的是,python 可以通过对 re.compile 或 re.match 设置 re.DEBUG (实际上就是整数 128) 标志就可以输出正则表达式的解析树。

import re
 
numbers = [ "123 555 6789",
            "1-(123)-555-6789",
            "(123-555-6789",
            "(123).555.6789",
            "123 55 6789" ]
 
for number in numbers:
    pattern = re.match(r'^'
                    r'(1[-\s.])?'        # optional '1-', '1.' or '1'
                    r'(\()?'             # optional opening parenthesis
                    r'\d{3}'             # the area code
                    r'(?(2)\))'          # if there was opening parenthesis, close it
                    r'[-\s.]?'           # followed by '-' or '.' or space
                    r'\d{3}'             # first 3 digits
                    r'[-\s.]?'           # followed by '-' or '.' or space
                    r'\d{4}$', number, re.DEBUG)  # last 4 digits
 
    if pattern:
        print '{0} is valid'.format(number)
    else:
        print '{0} is not valid'.format(number)
 

 

解析树

at_beginning
max_repeat 0 1
  subpattern 1
    literal 49
    in
      literal 45
      category category_space
      literal 46
max_repeat 0 2147483648
  in
    category category_space
max_repeat 0 1
  subpattern 2
    literal 40
max_repeat 0 2147483648
  in
    category category_space
max_repeat 3 3
  in
    category category_digit
max_repeat 0 2147483648
  in
    category category_space
subpattern None
  groupref_exists 2
    literal 41
None
max_repeat 0 2147483648
  in
    category category_space
max_repeat 0 1
  in
    literal 45
    category category_space
    literal 46
max_repeat 0 2147483648
  in
    category category_space
max_repeat 3 3
  in
    category category_digit
max_repeat 0 2147483648
  in
    category category_space
max_repeat 0 1
  in
    literal 45
    category category_space
    literal 46
max_repeat 0 2147483648
  in
    category category_space
max_repeat 4 4
  in
    category category_digit
at at_end
max_repeat 0 2147483648
  in
    category category_space
123 555 6789 is valid
1-(123)-555-6789 is valid
(123-555-6789 is not valid
(123).555.6789 is valid
123 55 6789 is not valid
 

 

贪婪和非贪婪

在我解释这个概念之前,我想先展示一个例子。我们要从一段 html 文本寻找锚标签:

import re
 
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>'
 
m = re.findall('<a.*>.*<\/a>', html)
if m:
    print m
 

结果将在意料之中:

['<a href="http://pypix.com" title="pypix">Pypix</a>']

我们改下输入,添加第二个锚标签:

import re
 
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>' \
       'Hello <a href="http://example.com" title"example">Example</a>'
 
m = re.findall('<a.*>.*<\/a>', html)
if m:
    print m
 

结果看起来再次对了。但是不要上当了!如果我们在同一行遇到两个锚标签后,它将不再正确工作:

['<a href="http://pypix.com" title="pypix">Pypix</a>Hello <a href="http://example.com" title"example">Example</a>']
 

这次模式匹配了第一个开标签和最后一个闭标签以及在它们之间的所有的内容,成了一个匹配而不是两个 单独的匹配。这是因为默认的匹配模式是“贪婪的”。

当处于贪婪模式时,量词(比如 * 和 +)匹配尽可能多的字符。

当你加一个问号在后面时(.*?)它将变为“非贪婪的”。

import re
 
html = 'Hello <a href="http://pypix.com" title="pypix">Pypix</a>' \
       'Hello <a href="http://example.com" title"example">Example</a>'
 
m = re.findall('<a.*?>.*?<\/a>', html)
if m:
    print m
 

现在结果是正确的。

['<a href="http://pypix.com" title="pypix">Pypix</a>Hello <a href="http://example.com" title"example">Example</a>']
 

 

前向界定符和后向界定符

一个前向界定符搜索当前的匹配之后搜索匹配。通过一个例子比较好解释一点。

下面的模式首先匹配 foo,然后检测是否接着匹配 bar

import re
 
strings = [  "hello foo",         # returns False
             "hello foobar"  ]    # returns True
 
for string in strings:
    pattern = re.search(r'foo(?=bar)', string)
    if pattern:
        print 'True'
    else:
        print 'False'
 

这看起来似乎没什么用,因为我们可以直接检测 foobar 不是更简单么。然而,它也可以用来前向否定界定。 下面的例子匹配foo,当且仅当它的后面没有跟着 bar

import re
 
strings = [  "hello foo",         # returns True
             "hello foobar",      # returns False
             "hello foobaz"]      # returns True
 
for string in strings:
    pattern = re.search(r'foo(?!bar)', string)
    if pattern:
        print 'True'
    else:
        print 'False'
 

后向界定符类似,但是它查看当前匹配的前面的模式。你可以使用 (?> 来表示肯定界定,(?<! 表示否定界定。

下面的模式匹配一个不是跟在 foo 后面的 bar

import re
 
strings = [  "hello bar",         # returns True
             "hello foobar",      # returns False
             "hello bazbar"]      # returns True
 
for string in strings:
    pattern = re.search(r'(?<!foo)bar',string)
    if pattern:
        print 'True'
    else:
        print 'False'
 

 

条件(IF-Then-Else)模式

正则表达式提供了条件检测的功能。格式如下:

(?(?=regex)then|else)

条件可以是一个数字。表示引用前面捕捉到的分组。

比如我们可以用这个正则表达式来检测打开和闭合的尖括号:

import re
 
strings = [  "<pypix>",    # returns true
             "<foo",       # returns false
             "bar>",       # returns false
             "hello" ]     # returns true
 
for string in strings:
    pattern = re.search(r'^(<)?[a-z]+(?(1)>)$', string)
    if pattern:
        print 'True'
    else:
        print 'False'
 

在上面的例子中,1 表示分组 (<),当然也可以为空因为后面跟着一个问号。当且仅当条件成立时它才匹配关闭的尖括号。

条件也可以是界定符。

 

无捕获组

分组,由圆括号括起来,将会捕获到一个数组,然后在后面要用的时候可以被引用。但是我们也可以不捕获它们。

我们先看一个非常简单的例子:

import re          
string = 'Hello foobar'         
pattern = re.search(r'(f.*)(b.*)', string)          
print "f* => {0}".format(pattern.group(1)) # prints f* => foo          
print "b* => {0}".format(pattern.group(2)) # prints b* => bar
 

现在我们改动一点点,在前面加上另外一个分组 (H.*)

import re          
string = 'Hello foobar'         
pattern = re.search(r'(H.*)(f.*)(b.*)', string)          
print "f* => {0}".format(pattern.group(1)) # prints f* => Hello          
print "b* => {0}".format(pattern.group(2)) # prints b* => bar
 

模式数组改变了,取决于我们在代码中怎么使用这些变量,这可能会使我们的脚本不能正常工作。 现在我们不得不找到代码中每一处出现了模式数组的地方,然后相应地调整下标。 如果我们真的对一个新添加的分组的内容没兴趣的话,我们可以使它“不被捕获”,就像这样:

import re          
string = 'Hello foobar'         
pattern = re.search(r'(?:H.*)(f.*)(b.*)', string)          
print "f* => {0}".format(pattern.group(1)) # prints f* => foo          
print "b* => {0}".format(pattern.group(2)) # prints b* => bar
 

通过在分组的前面添加 ?:,我们就再也不用在模式数组中捕获它了。所以数组中其他的值也不需要移动。

 

命名组

像前面那个例子一样,这又是一个防止我们掉进陷阱的方法。我们实际上可以给分组命名, 然后我们就可以通过名字来引用它们,而不再需要使用数组下标。格式是:(?Ppattern) 我们可以重写前面那个例子,就像这样:

import re          
string = 'Hello foobar'         
pattern = re.search(r'(?P<fstar>f.*)(?P<bstar>b.*)', string)          
print "f* => {0}".format(pattern.group('fstar')) # prints f* => foo          
print "b* => {0}".format(pattern.group('bstar')) # prints b* => bar
 

现在我们可以添加另外一个分组了,而不会影响模式数组里其他的已存在的组:

import re          
string = 'Hello foobar'         
pattern = re.search(r'(?P<hi>H.*)(?P<fstar>f.*)(?P<bstar>b.*)', string)          
print "f* => {0}".format(pattern.group('fstar')) # prints f* => foo          
print "b* => {0}".format(pattern.group('bstar')) # prints b* => bar          
print "h* => {0}".format(pattern.group('hi')) # prints b* => Hello
 

 

使用回调函数

在 Python 中 re.sub() 可以用来给正则表达式替换添加回调函数。

让我们来看看这个例子,这是一个 e-mail 模板:

import re          
template = "Hello [first_name] [last_name], \          
 Thank you for purchasing [product_name] from [store_name]. \          
 The total cost of your purchase was [product_price] plus [ship_price] for shipping. \          
 You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \          
 Sincerely, \          
 [store_manager_name]"          
# assume dic has all the replacement data          
# such as dic['first_name'] dic['product_price'] etc...          
dic = {          
 "first_name" : "John",          
 "last_name" : "Doe",          
 "product_name" : "iphone",          
 "store_name" : "Walkers",          
 "product_price": "$500",          
 "ship_price": "$10",          
 "ship_days_min": "1",          
 "ship_days_max": "5",          
 "store_manager_name": "DoeJohn"         
}          
result = re.compile(r'\[(.*)\]')          
print result.sub('John', template, count=1)
 

注意到每一个替换都有一个共同点,它们都是由一对中括号括起来的。我们可以用一个单独的正则表达式 来捕获它们,并且用一个回调函数来处理具体的替换。

所以用回调函数是一个更好的办法:

import re          
template = "Hello [first_name] [last_name], \          
 Thank you for purchasing [product_name] from [store_name]. \          
 The total cost of your purchase was [product_price] plus [ship_price] for shipping. \          
 You can expect your product to arrive in [ship_days_min] to [ship_days_max] business days. \          
 Sincerely, \          
 [store_manager_name]"          
# assume dic has all the replacement data          
# such as dic['first_name'] dic['product_price'] etc...          
dic = {          
 "first_name" : "John",          
 "last_name" : "Doe",          
 "product_name" : "iphone",          
 "store_name" : "Walkers",          
 "product_price": "$500",          
 "ship_price": "$10",          
 "ship_days_min": "1",          
 "ship_days_max": "5",          
 "store_manager_name": "DoeJohn"         
}          
def multiple_replace(dic, text):
    pattern = "|".join(map(lambda key : re.escape("["+key+"]"), dic.keys()))
    return re.sub(pattern, lambda m: dic[m.group()[1:-1]], text)     
print multiple_replace(dic, template)
 

 

不要重复发明轮子

更重要的可能是知道在什么时候要使用正则表达式。在许多情况下你都可以找到 替代的工具。

解析 [X]HTML

Stackoverflow 上的一个答案用一个绝妙的解释告诉了我们为什么不应该用正则表达式来解析 [X]HTML。

你应该使用使用 HTML 解析器,Python 有很多选择:

后面两个即使是处理畸形的 HTML 也能很优雅,这给大量的丑陋站点带来了福音。

ElementTree 的一个例子:

from xml.etree import ElementTree          
tree = ElementTree.parse('filename.html')          
for element in tree.findall('h1'):          
   print ElementTree.tostring(element)
 

 

其他

在使用正则表达式之前,这里有很多其他可以考虑的工具

分享到:
评论

相关推荐

    Python使用正则表达式实现爬虫数据抽取

    1. 为什么要使用正则表达式? 首先,大家来看一个例子。一个文本文件里面存储了一些市场职位信息,格式如下所示: Python3 高级开发工程师 上海互教教育科技有限公司上海-浦东新区2万/月02-18满员 测试开发工程师...

    三十三、Python之正则表达式介绍

    【3】为了快速方便的解决上述问题,产生了正则表达式技术 简介  1. 定义  即文本的高级匹配模式,提供搜索,替换等功能。其本质是由一系列字符和特殊符号构成的字串,这个字串即正则表式。  2. 原理  通过...

    Python企业级全技术栈开发.zip

    从Python基础课程,PYTHON0递归 函数 生成器,面向对象,MySQL数据库,PYTHON多线程,socket 编程 多线程,正则表达式基础,MONGODB,以及Python web的全系列课程。在后续的高级课程中对Django框架、tornado框架等也...

    Python企业级全技术栈开发 高级爬虫实战+机器学习+人脸识别+语音识别+金融项目实战等

    从Python基础课程,PYTHON0递归 函数 生成器,面向对象,MySQL数据库,PYTHON多线程,socket 编程 多线程,正则表达式基础,MONGODB,以及Python web的全系列课程。在后续的高级课程中对Django框架、tornado框架等也...

    Python企业级全技术栈开发视频教程.rar

    从Python基础课程,PYTHON0递归 函数 生成器,面向对象,MySQL数据库,PYTHON多线程,socket 编程 多线程,正则表达式基础,MONGODB,以及Python web的全系列课程。在后续的高级课程中对Django框架、tornado框架等也...

    Data Analysis and Visualization Using Python: Analyze Data to Create

    然后,您将使用Python中的文件I / O和正则表达式,然后收集和清理数据。继续探索和分析数据,您将了解Python中的高级数据结构。然后,您将深入研究数据可视化技术,通过Python中的许多绘图系统。

    [2017.02] 精通Python网络爬虫 核心技术、框架与项目实战

    技术维度:详细讲解了Python网络爬虫实现的核心技术,包括网络爬虫的工作原理、如何用urllib库编写网络爬虫、爬虫的异常处理、正则表达式、爬虫中Cookie的使用、爬虫的浏览器伪装技术、定向爬取技术、反爬虫技术,...

    使用 Python 进行 Web 抓取实践:使用有效的 Python 技术从 Web 中提取高质量数据

    Hands-On Web Scraping with Python 构建带有详细说明的网络抓取项目的...您还将掌握高级主题,例如安全 Web 处理、Web API、用于 Web 抓取的 Selenium、PDF 提取、正则表达式、数据分析、EDA 报告、可视化和机器学习

    Python高级编程

    文本处理:python提供的re模块能支持正则表达式,还提供SGML,XML分析模块,许多程序员利用python进行XML程序的开发。 数据库编程:程序员可通过遵循Python DB-API(数据库应用程序编程接口)规范的模块与Microsoft ...

    Python 快速入门 (英文)

    Python快速入门的书籍,介绍了python常用的数据结构,python程序编写,图形界面编程,以及正则表达式等高级技术

    python网页文本爬虫

    网络爬虫(又被称为网页蜘蛛,网络机器人),是...以小说网站爬取为例,首先需要掌握python基础,比如urllib使用,python进行字符串操作,复杂一点使用正则表达式。还有就是基本的程序逻辑。具备这三点就能开始爬小说。

    Python 备忘单是一页展示 Python 3 编程语言精华的参考表.rar

    最有经验的开发人员也很容易忘记如何对列表进行切片、为正则表达式创建各种字符类或创建循环。 这就是 Python 备忘单发挥作用的地方。这些备忘单为初学者和高级开发人员提供了简单的参考,同时降低了新手的入门门槛...

    《Python3程序开发指南》第二版

    目录 1 过程型程序设计快速入门 2 数据类型 3 组合类型 4 控制结构与函数 5 模块 6 OOP 7 文件处理 8 高级程序设计技术 8.1 FP 9 调试、测试与Profiling ...13 正则表达式 14 分析简介 14.1 PyParsing 14.2 PLY 15 GUI

    Python3.5基础教程-廖雪峰

    由技术作家,著有《Spring 2.0核心技术与最佳实践》的廖...格式为PDF,含有Python基础、函数、高级特性、函数式编程、模块、面向对象编程、面向对象高级编程、错误调试和测试、IO编程、进程和线程、正则表达式等章节。

    Python3完全零基础入门精讲超清教程.txt

    面向对象编程OOP 03.mp419-异常处理及测试.mp420-unittest单元测试.mp421-数值、日期与时间.mp422-对象持久化.mp423-字符与编码.mp424-正则表达式.mp425-系统编程.mp426-python并行编程.mp427-函数高级应用及装饰器....

    最新Python3.5零基础+高级+完整项目(28周全)培训视频学习资料

    最新Python3.5零基础+高级+完整项目(28周全)培训视频学习资料;本资料仅用于学习。 【课程内容】 第1周 开课介绍 python发展介绍 第一个python程序 变量 字符编码与二进制 字符编码的区别与介绍 用户交互程序 if ...

    md格式编写的良心教程 Python 100天从新手到大师 共100个完整源文件 含课程源代码.rar

    字符串和正则表达式.md Day01-15\13.进程和线程.md Day01-15\14.网络编程入门和网络应用开发.md Day01-15\15.图像和办公文档处理.md Day16-20\16-20.Python语言进阶.md Day21-30\21-30.Web前端概述.md Day31-35\31-...

    leetcode题库-like:用于收集开发者常用的一些链接--史上最强收藏夹

    可视化正则表达式 正则可视化工具,和上面类似 数据科学 数据科学社区 编程语言 Go Go中国技术社区 GO语言中文网 程序员私活 可以接活 算法&练习 有各种算法问题,可以提高自己的专业水平 里面含有各种题库,包含...

    Tcl_TK编程权威指南pdf

    高级正则表达式(are) 语法总结 regexp命令 rgsub命令 使用regsub将数据转换为程序 其他使用正则表达式的命令 第12章 脚本库及软件包 确定软件包的位置:auto-path变量 使用软件包 对软件包加载的总结 ...

    python网络数据采集 中文版-免费下载

    ................................................................................................................2.4 正则表达式和 BeautifulSoup.............................................................

Global site tag (gtag.js) - Google Analytics