版权声明:本文为博主原创文章,转载请附带原文网址http://www.cnblogs.com/wbchanblog/p/7411750.html ,谢谢!

提示:本文主要是讲解零宽断言,所以阅读本文需要有一定的正则表达式基础。

概念

  我们知道元字符“\b”、“^”、“$”匹配的是一个位置,而且这个位置需要满足一定的条件(比如“\b”表示单词的边界),我们把这个条件称为断言或零宽度断言。这里有很重要的两个信息:一是断言实际上是某种条件;二是它不占字符宽度,只是一个位置,并不匹配任何字符。

  零宽断言一共分为正向反向两类,每类又分为预测先行回顾后发两种:

  §零宽度正预测先行断言,简称正向先行断言,语法是(?=exp),它断言此位置的后面能匹配表达式exp。

  §零宽度正回顾后发断言,简称正向后发断言,语法是(?<=exp),它断言此位置的前面能匹配表达式exp。

  §零宽度负预测先行断言,简称反向先行断言,语法是(?!exp),它断言此位置的后面不能匹配表达式exp。

  §零宽度负回顾后发断言,简称反向后发断言,语法是(?<!exp),它断言此位置的前面不能匹配表达式exp。

  好了,说到这里你一定感觉云里雾里,讲道理我刚看到这官方定义也是一脸懵逼,下面就结合例子来帮助理解一下什么是断言。做过python爬虫的朋友一定做过提取html标签内容的工作吧,比如有<div>hello world</div>,我们要把div标签里面的‘hello world’提取出来,用断言就是如下这样:

正则表达式:(?<=<div>).*(?=</div>) 
匹配字符串:<div>hello world</div>
匹配结果: hello world

  我们结合这段表达式来看,我们前后用了(?<=<div>)(?=</div>)两个断言。

  先来看第一个断言(?<=<div>),看形式,是不是跟断言语法中的(?<=exp)一样,没错,这个就是正向后发断言,这里的exp就是<div>,它断言此位置的前面能匹配表达式<div>,这样说其实很不好理解,关键在于此位置这三个字不知道代表什么,实际上,这个此位置可以替换成目标字符串,也就是我们需要提取出来的内容,替换之后就变成了:它断言目标字符串的前面能匹配表达式<div>,换个更形象的说法:我断言,我所要提取的目标字符串,它前面的内容一定要匹配表达式<div>。单靠这个条件,去匹配<div>hello world</div>,可以得到结果hello world</div>

  再来看第二个断言(?=</div>),看形式,跟断言语法中的(?=exp)一样,那么这个就是正向先行断言,这里的exp就是</div>,它就代表:我断言,我所要提取的目标字符串,它后面的内容一定要匹配表达式</div>。根据这个条件,结合上一段得到的hello world</div>,我们可以得到匹配结果hello world

  这里安利一个叫Regex Match Tracer的软件,可以帮助我们学习正则表达式:

编写含断言的正则表达式思路

  根据以上所说,当我们需要提取字符串的时候,可以用断言,就比如上述字符串<div>hello world</div>,想得到div标签里面的内容时,我们可以按照以下思路写正则表达式:

  首先,目标字符串是hello world,那么它可以归纳为 .* 

  其次,目标字符串前面有<div>,既然是前面有,那么根据四种断言的含义,容易得出用正向后发断言(?<=exp),将它放在目标字符串前面,得到(?<=<div>).*,进一步可以将div归纳为[a-zA-Z]+,从而得到(?<=<[a-zA-Z]+>).*

  最后,目标字符串后面有</div>,既然是后面有,那么根据四种断言的含义,容易得出用正向先行断言(?=exp),将它放在目标字符串后面,从而得到(?<=<[a-zA-Z]+>).*(?=</[a-zA-Z]+>)

  进一步的,我们发现前后两个断言中都有[a-zA-Z]+,可以使用分组来避免书写重复的内容:(?<=<([a-zA-Z]+)>).*(?=</\1>),当然也可以使用命名分组,这里就不展开了。

  说到这里,我归纳出了几句书写断言的口诀:

    前面有,正向后发(?<=exp),放前面;

    后面有,正向先行(?=exp),放后面;

    前面无,反向后发(?<!exp),放前面;

    后面无,反向先行(?!exp),放后面。

  请记住,这个前面和后面是针对目标字符串,也就是你要提取出来的字符串而言的。

Python中断言的应用

  前面说了这么多, 都是就正则表达式本身而言的,我们知道不同编程语言都有自己对正则表达式的扩展,python也不例外。来看下面一段代码:

import re
pattern = re.compile(r'(?<=<([a-zA-Z]+>)).*(?=</\1>)')
s = '<html>hello world</html>'
ret = re.search(pattern, s)
print(ret.group()) #得到结果:
#Traceback (most recent call last):
# raise error("look-behind requires fixed-width pattern")
#sre_constants.error: look-behind requires fixed-width pattern

  我们看到python解释器报错了,怎么回事?别急,接着看:

import re
pattern = re.compile(r'(?<=<([a-zA-Z]+>)).*')
s = '<html>hello world</html>'
ret = re.search(pattern, s)
print(ret.group()) #得到结果:
#Traceback (most recent call last):
# raise error("look-behind requires fixed-width pattern")
#sre_constants.error: look-behind requires fixed-width pattern
import re
pattern = re.compile(r'.*(?=</[a-zA-Z]+>)')
s = '<html>hello world</html>'
ret = re.search(pattern, s)
print(ret.group()) #得到结果:
#<html>hello world

  看明白了吗?将上面第二第三段分别跟第一段代码对比,我们看到第二段相对于第一段的正则表达式去掉了正向先行断言,仍然报错;第三段相对于第一段的正则表达式去掉了正向后发断言(当然用到分组的地方已经手动补全了),却匹配到了结果。再结合错误信息“sre_constants.error: look-behind requires fixed-width pattern”,我们可以得出python的re模块并不支持变长的后发断言,只支持定长的后发断言。

  那咋办?难不成就不能提取html标签里的内容了?别急,请看下面代码:

import re
pattern = re.compile(r'<([a-zA-Z]+)>(.*)</\1>')
s = '<html>hello world</html>'
ret = re.search(pattern, s)
print('re.group()→', ret.group())
print('re.group(2)→', ret.group(2)) #运行结果
#re.group()→ <html>hello world</html>
#re.group(2)→ hello world

  我们可以用分组来提取特定的字符串,上面代码给了.*增加了一个分组,按从左到右是第二个分组,这样我们可以在匹配结果中用.group(2)得到目标字符串。

从零宽断言说起到用python匹配html标签内容的更多相关文章

  1. $python正则表达式系列(5)——零宽断言

    本文主要总结了python正则零宽断言(zero-length-assertion)的一些常用用法. 1. 什么是零宽断言 有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面 ...

  2. Python正则表达式进阶-零宽断言

    1. 什么是零宽断言 有时候在使用正则表达式做匹配的时候,我们希望匹配一个字符串,这个字符串的前面或后面需要是特定的内容,但我们又不想要前面或后面的这个特定的内容,这时候就需要零宽断言的帮助了.所谓零 ...

  3. 正则表达式零宽断言详解(?=,?<=,?!,?<!)

    在使用正则表达式时,有时我们需要捕获的内容前后必须是特定内容,但又不捕获这些特定内容的时候,零宽断言就起到作用了 正则表达式零宽断言: 零宽断言是正则表达式中的难点,所以重点从匹配原理方面进行分析.零 ...

  4. 零宽断言 -- Lookahead/Lookahead Positive/Negative

    http://www.vaikan.com/regular-expression-to-match-string-not-containing-a-word/ 经常我们会遇到想找出不包含某个字符串的文 ...

  5. crawler_正则表达式零宽断言

    在使用正则表达式时,有时我们需要捕获的内容前后必须是特定内容,但又不捕获这些特定内容的时候,零宽断言就起到作用了. (?=exp):零宽度正预测先行断言,它断言自身出现的位置的后面能匹配表达式exp. ...

  6. js正则:零宽断言

    JavaScript正则表达式零宽断言 var str="abnsdfZL1234nvcncZL123456kjlvjkl"var reg=/ZL(\d{4}|\d{6})(?!\ ...

  7. Python爬虫学习(4): python中re模块中的向后引用以及零宽断言

    使用小括号的时候,还有很多特定用途的语法.下面列出了最常用的一些: 表4.常用分组语法 分类 代码/语法 说明 捕获 (exp) 匹配exp,并捕获文本到自动命名的组里 (?<name>e ...

  8. python 正则表达式之零宽断言

    零宽断言:用于查找特定内容之前或之后的内容,但并不包括特定内容本身.对于零宽断言来说,我认为最重要的一个概念是位置,零宽断言用于指定一个位置,这个位置应该满足一定的条件(它附近满足什么表达式),并且这 ...

  9. python中的re模块中的向后引用和零宽断言

    1.后向引用 pattern = re.compile(r"(\w+)")#['hello', 'go', 'go', 'hello'] # pattern = re.compil ...

随机推荐

  1. SQL Server分页查询方法整理

    SQL Server数据库分页查询一直是SQL Server的短板,闲来无事,想出几种方法,假设有表ARTICLE,字段ID.YEAR...(其他省略),数据53210条(客户真实数据,量不大),分页 ...

  2. MongoDB入门解析【学习记录】

    刚开始学习mongodb,对笔记做了一个整理.是基于nodejs来学习的. 1.mongodb介绍 mongodb 是C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添 ...

  3. 【SignalR学习系列】4. SignalR广播程序

    创建项目 创建一个空的 Web 项目,并在 Nuget 里面添加 SignalR,jQuery UI 包,添加以后项目里包含了 jQuery,jQuery.UI ,和 SignalR 的脚本. 服务端 ...

  4. 【javascript】继承

    1. js 其实是一个非面向对象的语言,通过对象的深浅复制完成继承 2. 继承方法 继承的方法有两种 1)prototype 原型模式 举个例子 var Animal = function () { ...

  5. zookeeper单机模式安装

    zookeeper单机模式安装 更多文章:http://blogxinxiucan.sh1.newtouch.com/2017/07/26/zookeeper单机模式安装/ 下载zookeeper u ...

  6. Ztree _ 横向显示子节点、点击文字勾选、去除指定元素input的勾选状态

    前些天项目需要树结构表现数据,需求ztree就能满足所以直接使用ztree只是踩了些小坑... 1.ztree子节点横向显示(下图): 效果说明:第三级子节点按需求横向显示其他竖向显示,每行最多显示5 ...

  7. Java项目 打war包方法

    我们可以运用DOS命令来手工打war包: 首先,打开DOS命令行,敲入"jar",我们发现它提示不是内部或外部的命令这样的错误,这时八成是你的JAVA环境没有配置好,我们可以用JA ...

  8. [补档][Tyvj 1518]CPU监控

    [Tyvj 1518]CPU监控 题目 Bob需要一个程序来监视CPU使用率.这是一个很繁琐的过程,为了让问题更加简单,Bob会慢慢列出今天会在用计算机时做什么事. Bob会干很多事,除了跑暴力程序看 ...

  9. 1059. C语言竞赛

    C 语言竞赛是浙江大学计算机学院主持的一个欢乐的竞赛.既然竞赛主旨是为了好玩,颁奖规则也就制定得很滑稽: 冠军将赢得一份"神秘大奖"(比如很巨大的一本学生研究论文集--). 排名为 ...

  10. CentOS 常用命令及快捷键整理

    常用命令: 文件和目录: # cd /home                        进入 '/home' 目录 # cd ..                                ...