一文读懂正则表达式的基本用法

什么是正则表达式?

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

正则表达式不是Python独有的,目前主流的开发语言都支持正则表达式,在Python中是使用re模块来实现正则表达式。

正则表达式常用符号

符号 含义
* 匹配0个或多个表达式
+ 匹配1个或多个表达式
^ 匹配字符串的开头
$ 匹配字符串的结尾
. 匹配任意单个字符,换行符除外
\ 转义字符,把有特殊含义的字符转换为字面形式
? 匹配0或多个表达式,非贪婪模式
a|b 匹配a或b
() 匹配括号内的表达式, 也表示一个组
[] 匹配其中的任意一个字符
[^] 匹配任意一个不在中括号里的字符
\n 匹配换行符
\t 匹配制表符
\w 匹配任意字母数字及下划线
\s 匹配任意空白字符
\d 匹配任意数字

match函数

re.match会尝试从字符串的起始位置匹配一个字符串表达式,如果匹配成功就返回匹配结果,如果匹配失败就返回None.

比如这里有一个字符串:

    content = 'open the command palette 123456 Hello world!'

我想要匹配出其中的数字,可以这样写:

import re

content = 'open the command palette 123456 Hello world!'

# 在match方法中,第一个参数为正则表达式,第二个参数为待匹配的字符串
result = re.match('^open.*?(\d+).*?world!$', content)
print(result)
print(result.group(1)) 
<_sre.SRE_Match object; span=(0, 44), match='open the command palette 123456 Hello world!'>
123456

这里^open指定了字符串的开头,.*?表示以非贪婪模式匹配0或多个字符,(\d+)表示我们想要提取的是1到多个数字,需要提取的内容一定要放在小括号里,后面接着.*?表示以非贪婪模式匹配0或多个字符,最后的world!$指定了字符串的结尾。

这里group(1)表示提取第一个小括号里的内容,如果有多个小括号,可分别使用group(2),group(3)来提取。

贪婪匹配与非贪婪匹配的区别

在上面的例子中,我们使用了.*?表示以非贪婪模式匹配0或多个字符,这里如果将?去掉,就变成了贪婪模式,它们之间的具体区别是什么呢?看下面这个例子:

import re

content = 'open the command palette 123456 Hello world!'

# 这里将(\d+)前面的?去掉,其他不变。
result = re.match('^open.*(\d+).*?world!$', content)
print(result)
print(result.group(1)) 
<_sre.SRE_Match object; span=(0, 44), match='open the command palette 123456 Hello world!'>
6

如上,匹配结果变成了一个6,而前面的12345不见了,这是因为.*是以贪婪模式匹配,它会匹配尽可能多的字符,紧接着的\d+也就只匹配到了一个数字6

关于包含换行符的匹配

在实际的正则匹配过程中,我们需要匹配的字符串常是很多行,这其中就必定包含了换行符,这个时候就要使用修饰符re.S来匹配包含了换行符的字符串。如下例:

import re

# 有换行的字符串要用三引号''''''引起来
content = '''open the command palette 
            123456 Hello world!'''

# 这里加入修饰符re.S
result = re.match('^open.*?(\d+).*?world!$', content, re.S) 
print(result)
print(result.group(1)) 
<_sre.SRE_Match object; span=(0, 57), match='open the command palette \n            123456 Hel>
123456

在网页匹配中,常用的修饰符有两种:

  • re.S :匹配包含换行符在内的所有字符
  • re.I :使匹配对大小写不敏感

关于转义字符的匹配

上面我们罗列的一堆正则表达式的常用字符,这些字符可以称为特殊字符,如果待匹配的字符串中本身就包含了这些特殊字符,如果不加处理,就会对我们的正则匹配造成干扰,这个时候就需要用反斜杠\来进行转义:

import re

content = 'price is $10.00'
result = re.match('^price.*?\$10\.00$', content)
print(result)
<_sre.SRE_Match object; span=(0, 15), match='price is $10.00'>

这里使用\$\.对字符$.分别进行转义

search函数

上面讲到的match函数是从字符串的开头进行逐个匹配,如果开头不匹配,则匹配失败,它的执行效率有点类似与单链表的查询操作,得从头开始挨着一个一个找,而search函数是首先是扫描整个字符串,然后返回第一个成功匹配的结果。

在很大一堆的字符串中,如果我们只需要其中的一小段字符串,就可以使用search函数进行匹配:

import re 

content = 'open the command palette 123456 Hello world!'
result = re.search('command.*?(\d+).*?world!', content)
print(result)
print(result.group(1))
<_sre.SRE_Match object; span=(9, 44), match='command palette 123456 Hello world!'>
123456

下面来看个例子,这是我在豆瓣读书的网页上复制的一段HTML代码:

HTML = """<div class="pl2">


              <a href="https://book.douban.com/subject/1770782/" onclick="&quot;moreurl(this,{i:'0'})&quot;" title="追风筝的人">
                追风筝的人

                
              </a>



                &nbsp; <img src="https://img3.doubanio.com/pics/read.gif" alt="可试读" title="可试读">

              
                <br>
                <span style="font-size:12px;">The Kite Runner</span>
</div>
<p class="pl">[美] 卡勒德·胡赛尼 / 李继宏 / 上海人民出版社 / 2006-5 / 29.00元</p>
<div class="star clearfix">
                  <span class="allstar45"></span>
                  <span class="rating_nums">8.9</span>

                <span class="pl">(
                    315272人评价
                )</span>
              </div>"""

这里我想用正则表达式匹配这本《追风筝的人》图书的书名,作者,评分和评价人数,我们可以这样匹配:

import re 

result = re.search('<a.*?>(.*?)</a>.*?<p.*?>(.*?)</p>.*?rating_nums.*?>(.*?)</span>.*?>\((.*?)\)</span>', HTML, re.S)
print(result)
print(result.group(1))
print(result.group(2))
print(result.group(3))
print(result.group(4))
<_sre.SRE_Match object; span=(34, 703), match='<a href="https://book.douban.com/subject/1770782/>

                追风筝的人

                
              
[美] 卡勒德·胡赛尼 / 李继宏 / 上海人民出版社 / 2006-5 / 29.00元
8.9

                    315272人评价

我解释一下上面这句正则表达式,<a.*?>(.*?)</a>用来匹配书名,<p.*?>(.*?)</p>用来匹配作者信息, rating_nums.*?>(.*?)</span>用来匹配评分,>\((.*?)\)</span>用来匹配评分人数,这里要将每一个需要提取的信息放在小括号里,以待下一步的输出,然后不同有用信息的正则表达式之间用.*?来连接,最后指定修饰符re.S进行换行匹配

然后调用result.group(1)来输出第一个小括号里的内容,调用result.group(2)来输出第二个小括号里的内容,以此类推,但是从输出结果我们可以看到有多余的空白行和我不想要的信息,比如出版社和图书价格,这里将输出稍微整理一下:

import re 

result = re.search('<a.*?>(.*?)</a>.*?<p.*?>(.*?)</p>.*?rating_nums.*?>(.*?)</span>.*?>\((.*?)\)</span>', HTML, re.S)
print(result)
print(result.group(1).strip())
print(result.group(2).strip()[:12])
print(result.group(3).strip())
print(result.group(4).strip())
<_sre.SRE_Match object; span=(34, 703), match='<a href="https://book.douban.com/subject/1770782/>
追风筝的人
[美] 卡勒德·胡赛尼 
8.9
315272人评价

如上,得到了我想要的信息。

希望这个示例对你有所启发。

findall()函数

在整个网页的正则匹配中,我们想要的信息往往处在一组同名的标签下类似的位置,只是属性值有所不同,想要提取这样的信息,就要使用findall()函数,该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容。相当于search()函数的拓展。

具体示例省略

compile()函数

这个方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中方便调用。例如:

import re 

pattern = re.compile('<a.*?>(.*?)</a>.*?<p.*?>(.*?)</p>.*?rating_nums.*?>(.*?)</span>.*?>\((.*?)\)</span>', re.S)
result = re.search(pattern, HTML)
print(result)
print(result.group(1).strip())
print(result.group(2).strip()[:12])
print(result.group(3).strip())
print(result.group(4).strip())
<_sre.SRE_Match object; span=(34, 703), match='<a href="https://book.douban.com/subject/1770782/>
追风筝的人
[美] 卡勒德·胡赛尼 
8.9
315272人评价

正则表达式的基本用法大致就是这些,下节我们尝试用正则表达式写一个小爬虫。

每天学习一点点,每天进步一点点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容