Python爬虫-正则表达式re

  随着大数据乃至人工智能的迅猛发展,数据变得越来越重要,甚至已成为很多企业赖以生存的根基。而想要获取数据,爬虫是必备工具之一。而正则表达式在爬虫抓取内容中扮演中重要角色,要想成为一个合作的爬虫工程师,就必须熟练的掌握正则表达式的知识。
  正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。re模块使 Python 语言拥有全部的正则表达式功能。本文主要是记录一下本人学习Python的正则表达式re模块常用处理函数的笔记,言归正传,下面进入正文。
re.match函数
  re.match(pattern, string, flags=0),尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none。
    1.pattern 匹配的正则表达式
    2.string 要匹配的字符串
    3.flags标志位,用于控制正则表达式的匹配方式,修饰符
其中修饰符如下:

image.png

如下,匹配到起始位置的:

#!/usr/bin/python
import re
line = "Cats are smarter than dogs"
matchObj = re.match( r'(.*) are (?P<value>.*?) .*', line, re.M|re.I)
print(matchObj)

打印结果为

<_sre.SRE_Match object; span=(0, 26), match='Cats are smarter than dogs'>

如上,返回一个re.MatchObject对象,该对象有如下的方法:
   1.group(num=0),0可以不传入,返回字符串,group(num=1)返回正则第一分组匹配的字符串,group(name)返回匹配指定分组的字符串
   2.groups(),以元组返回规则里面所有括号里匹配的字符串
   3.start(num=0),0可以不传入,返回匹配的起始位置,start(name)返回匹配指定分组的字符串起始位置
   4.end(num=0),0可以不传入,返回匹配的结束位置,end(name)返回匹配指定分组的字符串结束位置
   5.span(num=0),0可以不传入,返回匹配的字符串的(start, end),span(name)返回匹配指定分组的字符串的(start, end)
如下,代码接上面:

if matchObj:
   print("matchObj.group('value') : ", matchObj.group('value'))
   print("matchObj.group(1) : ", matchObj.group(1))
   print("matchObj.group(2) : ", matchObj.group(2))
   print("matchObj.groups() : ",matchObj.groups())
   print("matchObj.start(2) : ",matchObj.start(2))
   print("matchObj.end(2): ",matchObj.end(2))
   print("matchObj.span(): ",matchObj.span())
else:
   print("No match!!")

打印结果为

matchObj.group('value') :  smarter  #获取正则表达式指定分组(?P<value>.*?)匹配的字符串
matchObj.group(1) :  Cats  #获取正则表达式第一分组(.*)匹配匹配的字符串
matchObj.group(2) :  smarter  #获取正则表达式第二分组(.*)匹配匹配的字符串
matchObj.groups() :('Cats', 'smarter')  #获取全部分组匹配的字符串,以元组形式返回
matchObj.start(2) :9 #返回正则表达式第二分组(.*)的起始位置
matchObj.end(2): 16 #返回正则表达式第二分组(.*)的结束位置
matchObj.span():(0, 26) #返回匹配到字符串的起始和结束位置

如果,没有匹配到起始位置,则会返回None,如下:

print(re.match('com', 'www.baidu.com'))   #没有匹配起始位置,打印结果为None

re.search函数
  re.search(pattern, string, flags=0),扫描整个字符串并返回第一个成功的匹配;
    1.pattern 匹配的正则表达式
    2.string 要匹配的字符串
    3.flags标志位,用于控制正则表达式的匹配方式,修饰符 (见图image.png)
如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*- 
import re
print(re.search('www', 'www.runoobwww.com').span()) # 在起始位置匹配
print(re.search('com', 'www.runoobcom.com').span())    # 不在起始位置匹配

打印结果如下,可以匹配字符串的任意位置,匹配到后直接返回结果,不继续进行匹配:

(0, 3)
(10, 13)

该方法会返回一个对象格式的数据<class '_sre.SRE_Match'>,具有如下的方法:
   1.group(num=0),0可以不传入,返回字符串,group(num=1)返回正则第一分组匹配的字符串,group(name)返回匹配指定分组的字符串
   2.groups(),以元组返回规则里面所有括号里匹配的字符串
   3.start(num=0),0可以不传入,返回匹配的起始位置,start(name)返回匹配指定分组的字符串起始位置
   4.end(num=0),0可以不传入,返回匹配的结束位置,end(name)返回匹配指定分组的字符串结束位置
   5.span(num=0),0可以不传入,返回匹配的字符串的(start, end),span(name)返回匹配指定分组的字符串的(start, end)
具体的用法如下:

#!/usr/bin/python
import re

line = "Cats are smarter than dogs";
searchObj = re.search( r'(.*) are (.*?) .*', line, re.M|re.I)
if searchObj:
    print("searchObj.groups() : ", searchObj.groups()) 
    print("searchObj.group(1) : ", searchObj.group(1))
    print("searchObj.group(2) : ", searchObj.group(2))
    print("searchObj.start(2) :",searchObj.start(2))
    print("searchObj.end(2): ", searchObj.end(2))
    print("searchObj.span(): ", searchObj.span())
else:
    print("Nothing found!!")

打印结果为

searchObj.groups() :  ('Cats', 'smarter')   #获取全部分组匹配的字符串,以元组形式返回
searchObj.group(1) :  Cats #获取正则表达式第一分组(.*)匹配匹配的字符串
searchObj.group(2) : smarter #获取正则表达式第二分组(.*)匹配匹配的字符串
searchObj.start(2) : 9 #返回正则表达式第二分组(.*)的起始位置
searchObj.end(2): 16 #返回正则表达式第二分组(.*)的结束位置
searchObj.span(): (0, 26)  #返回匹配到字符串的起始和结束位置

如果,没有匹配任何字符串,则会返回None,如下:

print(re.match('c1om', 'www.baidu.com'))   #没有匹配起始位置,打印结果为None

re.sub函数
  re.sub(pattern, repl, string, count=0, flags=0)方法用来替换字符串;
   1.pattern 匹配的正则表达式
   2.repl 替换的字符串,也可以为一个函数
   3.string 要匹配的字符串
   4.count 模式匹配后替换的最大次数,0的情况下是全部替换
   5.flags 标志位,用于控制正则表达式的匹配方式,修饰符 (见图image.png)
如下:

#!/usr/bin/python
import re

s = '2004-959-559 # 这是一个国外电话号码'
phone = re.sub('#\D+', '', s )  #使用空字符替换后面的'# 这是一个国外电话号码'
print(phone) 
print(re.sub('\-', '', phone, 1)) # count =1,替换一次匹配到的字符串

打印结果如下:

2004-959-559  #替换#+后面的字符串后,得到前面的电话号码
2004959-559  #替换一次'-'后的结果

repl为函数的情况下,代码如下

#!/usr/bin/python
import re

def double(matched):
  value = int(matched.group('name'))  #获取匹配组name的字符串,对其进行取int
  return str(value*2)

s = 'A23G4HFD567'
print(re.sub('(?P<name>\d+)', double, s)) 

打印如下:

A46G8HFD1134 #匹配出数字字符,对其int后进行*2操作后,替换原来的数字字符

   这里穿插说下,正则表达式分组命名的问题,格式为(?P<name>...),如上面(?P<name>\d+),就是将这个分组命名为name,这个就可以通过调用group('name')方法获取这个分组匹配的字符串。

re.compile 函数
 re.compile(pattern[, flags]),用于编译正则表达式,生成一个正则表达式( Pattern )对象;
  1.pattern 匹配的正则表达式
  2.flags标志位,用于控制正则表达式的匹配方式,修饰符 (见图image.png)
如下:

#!/usr/bin/python
import re

pattern = re.compile(r'(.*) are (?P<value>.*?) .*') 

如上所示,返回re.RegexObject 对象,具有如下的常用方法:
  1.findall(string[, pos[, endpos]]),参数如下:
   1.string 待匹配的字符串。
   2.pos 可选参数,指定字符串的起始位置,默认为 0
   3.endpos 可选参数,指定字符串的结束位置,默认为字符串的长度
findall()方法,返回字符串所有非重叠匹配的列表,如果没有匹配到,则返回空列表,如下:

#!/usr/bin/python
import re

pattern = re.compile(r'\d+')  #查找数字
result1 = pattern.findall('runoob 123 google 456')
result2 = pattern.findall('run88oob123google456', 0, 10)
result3 = pattern.findall('runoobgoogle')
print(result1)
print(result2)

打印结果如下:

['123', '456'] #找出这个字符串中所有的数字字符
['88', '12'] #字符串索引0到10之间,查找所有的数字字符
[] #没有匹配任何内容,返回空的列表

另外,对于findall()方法,如果匹配的正则表达式中有分组的话,则以列表的形式返回分组匹配到字符串,如果有多个分组,则返回元组列表,如下只有一个分组的情况:

#!/usr/bin/python
import re

s = "Cats are smarter than dogs"
pattern = re.compile(r'(.*) are .*', re.M|re.I)
print(pattern.findall(s))

打印结果如下:

['Cats'] # 找出分组(.*)匹配的字符串

有多个分组的情况下,如下:

#!/usr/bin/python
import re

s = "Cats are smarter than dogs"
pattern = re.compile(r'(.*) are (.*?) .*', re.M|re.I)
print(pattern.findall(s))

打印结果如下:

[('Cats', 'smarter')]  #返回一个list,元素为元组

这个有个问题,如果pattern = re.compile(r'(.) are (.) .*', re.M|re.I),去掉中间分组那个?,打印出来的结果跟上面不一样,如下

#!/usr/bin/python
import re

s = "Cats are smarter than dogs"
pattern = re.compile(r'(.*) are (.*) .*', re.M|re.I)
print(pattern.findall(s))

打印结果如下:

[('Cats', 'smarter than')] 

 这到底是为什么呢,这个问题困扰了我很久,原来在正则表达式中*是贪婪匹配,要尽可能多的匹配,所以第二分组贪婪模式下,匹配的字符串为smarter than,上面的处理是在正则表达式外面加?,使用贪婪模式转换成非贪婪模式。所以使用正则表达式进行匹配字符串的时候,尽量使用非贪婪模式,比如用".*?"代替".*",为了防止贪婪模式的情况下,导致获取的字符串缺失。但是如果".*?"出现结尾的话,就会匹配不到任何的内容,因为它会匹配尽可能少的字符,这是需要使用".*".
 另外,findall()方法匹配的字符串只是正则表达式中分组匹配的内容,有的时候跟我们想要获取的字符串不一致,那么我们想获取全部正则匹配的内容,该这么办呢,下面举例说明一下这个问题:

#!/usr/bin/python
import re

s = "Cats are smarter than dogs"
pattern = re.compile(r'(?:.*) are (?:.*) .*', re.M|re.I)  #在分组正则表达式前面(括号里)加'?:',使得捕获组变成非捕获组
print(pattern.findall(s))

打印结果如下:

['Cats are smarter than dogs'] #设置成非捕获组后,匹配出全部的内容。

  2.match(string[, pos[, endpos]])参数如下:
   1.string 待匹配的字符串。
   2.pos 可选参数,指定字符串的起始位置,默认为 0
   3.endpos 可选参数,指定字符串的结束位置,默认为字符串的长度
如下:

#!/usr/bin/python
import re

pattern = re.compile(r'\d+')
s = pattern.match('one12twothree34four', 3, 10)  #传入pos和endpos参数,从pos位置进行匹配,endpos位置结束
print(s)

打印结果如下:

<_sre.SRE_Match object; span=(3, 5), match='12'> #指定从字符1进行匹配,能够匹配到数据字符串。

该方法返回MatchObject 对象,有group(),groups(),start(),end()和span()方法,具体的用法见上面。

re.finditer 函数
 re.finditer(pattern, string, flags=0),与findall类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回,每个迭代都是一个MatchObject 对象;
  1.pattern 匹配的正则表达式
  2.string 要被匹配的字符串
  3.flags 标志位,用于控制正则表达式的匹配方式,修饰符 (见图image.png)
如下:

#!/usr/bin/python
import re

s = '2004-959-559 # 这是一个国外电话号码'
pattern = re.compile(r'\d+')
iter = pattern.finditer(s)  #返回一个迭代器
for i in iter:
         print(i.group()) #调用group()方法获取匹配的内容

打印结果如下:

#打印出匹配的全部数字字符
2004
959
559

re.split 函数
 re.split(pattern, string[, maxsplit=0, flag=0]),按照能够匹配的子串将字符串分割后返回列表;
  1.pattern 匹配的正则表达式
  2.string 要被匹配的字符串
  3.maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数。
  4.flags 标志位,用于控制正则表达式的匹配方式,修饰符 (见图image.png)
如下:

#!/usr/bin/python
import re

s = 'runoob, runoob, runoob'
print(re.split('\W+', s))
print(re.split('(\W+)', s)) 
print(re.split('\W+', s), 1) #传入maxsplit=1,分隔一次

打印结果如下:

['runoob', 'runoob', 'runoob'] 
['runoob', ', ', 'runoob', ', ', 'runoob']
['runoob', 'runoob, runoob']  #传入maxsplit=1,分隔一次

这里有个疑问,第一个print打印的内容,连','都做了分隔项了,跟上面的区别是,对正常表达式做了捕获组,希望亲爱的您能够帮忙我解答一下,谢谢
  断断续续的,终于这个总结给写完了,对我自己来说,又巩固了一下正则方面的知识点,加深了印象,也希望能够给亲爱的您带来帮助。

本篇如果对您有帮助,请点个👍,谢谢。

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

推荐阅读更多精彩内容