Python re module for regular expression

python Doc:

re --- 正则表达式操作 — Python 3.9.5 文档

正则表达式的大致匹配过程是:

1.依次拿出表达式和文本中的字符比较,
2.如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。
3.如果表达式中有量词或边界,这个过程会稍微有一些不同。

r 取消转义字符:在带有 'r' 前缀的字符串字面值中,反斜杠不必做任何特殊处理。 因此 r"\n" 表示包含 ' \ ' 和 'n' 两个字符的字符串,而 "\n" 则表示只包含一个换行符的字符串。

re模块的使用:import re

编译正则表达式

re.compile 函数

compile 函数用于编译正则表达式,生成一个正则表达式( Pattern )对象,可以使用其他应用匹配函数比如match, search, findall等等。

prog = re.compile(pattern) # prog为一个模式对象
result = prog.match(string)

# 等价于

result = re.match(pattern, string)

也可以不必创建模式对象并调用其方法;re 模块还提供了顶级函数 match()search()findall()sub() 等等。 这些函数采用与相应模式方法相同的参数,并将正则字符串作为第一个参数添加,并仍然返回 None匹配对象 实例。

print(re.match(r'From\s+', 'Fromage amk'))
# None
re.match(r'From\s+', 'From amk Thu May 14 19:12:10 1998')  
# <re.Match object; span=(0, 5), match='From '>

本质上,这些函数只是为你创建一个模式对象,并在其上调用适当的方法。 它们还将编译对象存储在缓存中,因此使用相同的未来调用将不需要一次又一次地解析该模式。

如果你正在循环中访问正则表达式,预编译它将节省一些函数调用。 在循环之外,由于有内部缓存,没有太大区别

原始字符串表示法

反斜杠灾难

如前所述,正则表达式使用反斜杠字符 ('\') 来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义。 这与 Python 在字符串文字中用于相同目的的相同字符的使用相冲突。

假设你想要编写一个与字符串 \section 相匹配的正则,它可以在 LaTeX 文件中找到。 要找出在程序代码中写入的内容,请从要匹配的字符串开始。 接下来,您必须通过在反斜杠前面添加反斜杠和其他元字符,从而产生字符串 \\section。 必须传递给 re.compile()的结果字符串必须是 \\section。 但是,要将其表示为 Python 字符串文字,必须 再次 转义两个反斜杠。

解决方案是使用 Python 的原始字符串表示法来表示正则表达式. r"\n" 是一个包含 '\''n' 的双字符字符串,而 "\n" 是一个包含换行符的单字符字符串。 正则表达式通常使用这种原始字符串表示法用 Python 代码编写。

正则表达式应该在引号之间选择文本 . 如果有嵌套引号,则应选择所有内部嵌套引号而不是外部引号

应用匹配(查找)

方法 / 属性 目的
match() 确定正则是否从字符串的开头匹配。
search() 扫描字符串,查找此正则匹配的任何位置。
findall() 找到正则匹配的所有子字符串,并将它们作为列表返回。
finditer() 找到正则匹配的所有子字符串,并将它们返回为一个 iterator

语法:re.***(pattern, string, flags=0)

pattern 匹配的正则表达式
string 要匹配的字符串
flags 标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

标志 意义
ASCII, A 使几个转义如 \w\b\s\d 匹配仅与具有相应特征属性的 ASCII 字符匹配。
DOTALL, S 使 . 匹配任何字符,包括换行符。
IGNORECASE, I 进行大小写不敏感匹配。
LOCALE, L 进行区域设置感知匹配。表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境
MULTILINE, M 多行匹配,影响 ^$
VERBOSE, X (为 '扩展') 启用详细的正则,可以更清晰,更容易理解。
UNICODE, U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库.re.U 标记依然存在(还有嵌入形式 (?u) ) , 但是这些在 Python 3 是冗余的,因为默认字符串已经是Unicode了(并且Unicode匹配不允许byte出现)

VERBOSE

此标志允许你编写更易读的正则表达式,方法是为您提供更灵活的格式化方式。 指定此标志后,将忽略正则字符串中的空格,除非空格位于字符类中或前面带有未转义的反斜杠;这使你可以更清楚地组织和缩进正则。 此标志还允许你将注释放在正则中,引擎将忽略该注释;注释标记为 '#' 既不是在字符类中,也不是在未转义的反斜杠之前。

例如,这里的正则使用 re.VERBOSE;看看阅读有多容易?:

charref = re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE)

如果没有详细设置,正则将如下所示:

charref = re.compile("&#(0[0-7]+"
                     "|[0-9]+"
                     "|x[0-9a-fA-F]+);")

在上面的例子中,Python的字符串文字的自动连接已被用于将正则分解为更小的部分,但它仍然比以下使用 re.VERBOSE 版本更难理解。

匹配对象

match() 和 search() 返回 None 。如果它们成功, 一个 匹配对象 实例将被返回,包含匹配相关的信息:起始和终结位置、匹配的子串以及其它。

方法 / 属性 目的
group() 返回正则匹配的字符串
start() 返回匹配的开始位置
end() 返回匹配的结束位置
span() 返回包含匹配 (start, end) 位置的元组

group()用来提出分组截获的字符串,()用来分组,group() 同group(0)就是匹配正则表达式整体结果,group(1) 列出第一个括号匹配部分,group(2) 列出第二个括号匹配部分,group(3) 列出第三个括号匹配部分。没有匹配成功的,re.search()返回None。

举例:

import re
result = re.match("itcast","itcast.cn")
result.group()
'itcast

从string头开始匹配pattern完全可以匹配,pattern匹配结束,同时匹配终止,后面的.cn不再匹配,返回匹配成功的信息。

re.match函数

如果 string 开始的0或者多个字符匹配到了正则表达式样式,就返回一个相应的匹配对象。 如果没有匹配,就返回 None ;注意它跟零长度匹配是不同的。

举例:

import re
pattern = re.compile(r'\d+')   
m = pattern.match('one12twothree34four', 3, 10) # 从'1'的位置开始匹配,正好匹配
# 此时m为一个 Match 对象 <_sre.SRE_Match object at 0x10a42aac0>
m.group(0)   # 可省略 0
'12'
m.start(0)   # 可省略 0
3
m.end(0)     # 可省略 0
5
m.span(0)    # 可省略 0
(3, 5)

re.search函数

re.search 扫描整个字符串并返回第一个成功的匹配,如果没有匹配,就返回一个 None。

re.match与re.search的区别:re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配

举例:

import re
ret = re.search(r"\d+", "阅读次数为9999")
print(ret.group())
结果:

9999

re.findall函数

在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表。注意: match 和 search 是匹配一次 findall 匹配所有。

举例:

import re
ret = re.findall(r"\d+", "python = 9999, c = 7890, c++ = 12345")
print(ret)
结果:

['9999', '7890', '12345']

re.finditer函数

和 findall 类似,在字符串中找到正则表达式所匹配的所有子串,并把它们作为一个迭代器返回。

import re
it = re.finditer(r"\d+", "12a32bc43jf3")
for match in it:
    print(match.group())
结果:

12
32
43
3

注意  match/ search 和  findall/ finditer 区别

import re
import requests

url = "https://sc.chinaz.com/tupian"
response = requests.get(url)
pattern1 = r'<img src2="(.*?)" alt=".*美女.*">'
pattern2 = r'<img src2="(.*?)" alt="(.*美女.*)">'


print('*'*30 + '  search  ' + '*' * 30)
print('pattern1')
print(re.search(pattern1, response.content.decode('utf-8')).group())
print('pattern2')
print(re.search(pattern2, response.content.decode('utf-8')).group())

print('*'*30 + '  findall  ' + '*' * 30)
print('pattern1')
print(re.findall(pattern1, response.content.decode('utf-8')))
print('pattern2')
print(re.findall(pattern2, response.content.decode('utf-8')))


结果:
******************************  search  ****************************** # 匹配了第一个整个结果
pattern1
<img src2="//scpic.chinaz.net/Files/pic/pic9/202106/apic33410_s.jpg" alt="春天户外白色连衣裙美女图片">
pattern2
<img src2="//scpic.chinaz.net/Files/pic/pic9/202106/apic33410_s.jpg" alt="春天户外白色连衣裙美女图片">
******************************  findall  ****************************** # 匹配了所有的子结果 同级结果放在了一个结果中
pattern1
['//scpic.chinaz.net/Files/pic/pic9/202106/apic33410_s.jpg', '//scpic1.chinaz.net/Files/pic/pic9/202106/apic33408_s.jpg', '//scpic1.chinaz.net/Files/pic/pic9/202106/apic33405_s.jpg', '//scpic2.chinaz.net/Files/pic/pic9/202106/apic33345_s.jpg', '//scpic3.chinaz.net/Files/pic/pic9/202106/apic33347_s.jpg', '//scpic.chinaz.net/Files/pic/pic9/202106/apic33350_s.jpg', '//scpic.chinaz.net/Files/pic/pic9/202106/apic33400_s.jpg', '//scpic1.chinaz.net/Files/pic/pic9/202106/apic33401_s.jpg', '//scpic3.chinaz.net/Files/pic/pic9/202106/apic33399_s.jpg', '//scpic2.chinaz.net/Files/pic/pic9/202106/apic33402_s.jpg', '//scpic3.chinaz.net/Files/pic/pic9/202106/apic33404_s.jpg']
pattern2
[('//scpic.chinaz.net/Files/pic/pic9/202106/apic33410_s.jpg', '春天户外白色连衣裙美女图片'), ('//scpic1.chinaz.net/Files/pic/pic9/202106/apic33408_s.jpg', '白色卫衣泡面头美女写真图片'), ('//scpic1.chinaz.net/Files/pic/pic9/202106/apic33405_s.jpg', '双马尾黑人美女图片'), ('//scpic2.chinaz.net/Files/pic/pic9/202106/apic33345_s.jpg', '欧美齐肩短发美女黑白写真图片'), ('//scpic3.chinaz.net/Files/pic/pic9/202106/apic33347_s.jpg', '极品性感大美背美女图片'), ('//scpic.chinaz.net/Files/pic/pic9/202106/apic33350_s.jpg', '撩人性感艺术美女人体图片'), ('//scpic.chinaz.net/Files/pic/pic9/202106/apic33400_s.jpg', '美女手提花篮图片'), ('//scpic1.chinaz.net/Files/pic/pic9/202106/apic33401_s.jpg', '单手托着下巴的美女图片'), ('//scpic3.chinaz.net/Files/pic/pic9/202106/apic33399_s.jpg', '秋季文艺风亚洲美女写真图片'), ('//scpic2.chinaz.net/Files/pic/pic9/202106/apic33402_s.jpg', '欧美丰满美女黑白图片'), ('//scpic3.chinaz.net/Files/pic/pic9/202106/apic33404_s.jpg', '手持睫毛刷的欧美美女图片')]

应用匹配(替换)

re.sub函数

sub是substitute的所写,表示替换,将匹配到的数据进⾏替换。

语法:re.sub(pattern, repl, string, count=0, flags=0)

参数 描述
pattern 必选,表示正则中的模式字符串
repl 必选,就是replacement,要替换的字符串,也可为一个函数
string 必选,被替换的那个string字符串
count 可选参数,count 是要替换的最大次数,必须是非负整数。如果省略这个参数或设为 0,所有的匹配都会被替换
flag 可选参数,标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

举例:将匹配到的阅读次数加1

方法一:

import re
ret = re.sub(r"\d+", '998', "python = 997")
print(ret)
结果:python = 998

方法二:

import re
def add(temp):
    #int()参数必须是字符串,类似字节的对象或数字,而不是“re.Match”
    strNum = temp.group()
    num = int(strNum) + 1
    return str(num)
ret = re.sub(r"\d+", add, "python = 997")
print(ret)
ret = re.sub(r"\d+", add, "python = 99")
print(ret)
结果;

python = 998
python = 100

re.subn函数

行为与sub()相同,但是返回一个元组 (字符串, 替换次数)。

re.subn(pattern, repl, string[, count])

返回:(sub(repl, string[, count]), 替换次数)

import re
pattern = re.compile(r'(\w+) (\w+)')
s = 'i say, hello world!'
print(re.subn(pattern, r'\2 \1', s))
def func(m):
    return m.group(1).title() + ' ' + m.group(2).title()
print(re.subn(pattern, func, s))

output

('say i, world hello!', 2)

('I Say, Hello World!', 2)

re.split函数

根据匹配进⾏切割字符串,并返回⼀个列表。

re.split(pattern, string, maxsplit=0, flags=0)

参数 描述
pattern 匹配的正则表达式
string 要匹配的字符串
maxsplit 分隔次数,maxsplit=1 分隔一次,默认为 0,不限制次数

举例:

import re
ret = re.split(r":| ","info:xiaoZhang 33 shandong")
print(ret)
结果:['info', 'xiaoZhang', '33', 'shandong']

常见问题

正则表达式对于某些应用程序来说是一个强大的工具,但在某些方面,它们的行为并不直观,有时它们的行为方式与你的预期不同。 本节将指出一些最常见的陷阱。

使用字符串方法

有时使用 re 模块是一个错误。 如果你匹配固定字符串或单个字符类,并且你没有使用任何 re 功能,例如 IGNORECASE 标志,那么正则表达式的全部功能可能不是必需的。 字符串有几种方法可以使用固定字符串执行操作,它们通常要快得多,因为实现是一个针对此目的而优化的单个小 C 循环,而不是大型、更通用的正则表达式引擎。

一个例子可能是用另一个固定字符串替换一个固定字符串;例如,你可以用 deed 替换 wordre.sub 看起来像是用于此的函数,但请考虑 replace() 方法。 注意 replace()也会替换单词里面的word,把swordfish变成sdeedfish,但简单的正则word也会这样做。 (为了避免对单词的部分进行替换,模式必须是\bword\b,以便要求 word在任何一方都有一个单词边界。这使得工作超出了replace()` 的能力。)

另一个常见任务是从字符串中删除单个字符的每个匹配项或将其替换为另一个字符。 你可以用 re.sub('\n', ' ', S) 之类的东西来做这件事,但是 translate() 能够完成这两项任务,并且比任何正则表达式都快。

简而言之,在转向 re 模块之前,请考虑是否可以使用更快更简单的字符串方法解决问题。

有时你会被诱惑继续使用 re.match(),只需在你的正则前面添加 .* 。抵制这种诱惑并使用 re.search() 代替。 正则表达式编译器对正则进行一些分析,以加快寻找匹配的过程。 其中一个分析可以确定匹配的第一个特征必须是什么;例如,以 Crow 开头的模式必须与 'C' 匹配。 分析让引擎快速扫描字符串,寻找起始字符,只在找到 'C' 时尝试完全匹配。

添加 .* 会使这个优化失效,需要扫描到字符串的末尾,然后回溯以找到正则的其余部分的匹配。 使用 re.search() 代替。

处理html

(请注意,使用正则表达式解析 HTML 或 XML 很痛苦。快而脏的模式将处理常见情况,但 HTML 和 XML 有特殊情况会破坏明显的正则表达式;当你编写正则表达式处理所有可能的情况时,模式将非常复杂。使用 HTML 或 XML 解析器模块来执行此类任务。)

使用 re.VERBOSE

到目前为止,你可能已经注意到正则表达式是一种非常紧凑的表示法,但它们并不是非常易读。 具有中等复杂度的正则可能会成为反斜杠、括号和元字符的冗长集合,使其难以阅读和理解。

对于这样的正则,在编译正则表达式时指定 re.VERBOSE 标志可能会有所帮助,因为它允许你更清楚地格式化正则表达式。

re.VERBOSE 标志有几种效果。 正则表达式中的 不是 在字符类中的空格将被忽略。 这意味着表达式如 dog | cat 等同于不太可读的 dog|cat ,但 [a b] 仍将匹配字符 'a''b' 或空格。 此外,你还可以在正则中放置注释;注释从 # 字符扩展到下一个换行符。 当与三引号字符串一起使用时,这使正则的格式更加整齐:

这更具有可读性:

pat = re.compile(r"\s*(?P<header>[^:]+)\s*:(?P<value>.*?)\s*$")

pat = re.compile(r"""
 \s*                 # Skip leading whitespace
 (?P<header>[^:]+)   # Header name
 \s* :               # Whitespace, and a colon
 (?P<value>.*?)      # The header's value -- *? used to
                     # lose the following trailing whitespace
 \s*$                # Trailing whitespace to end-of-line
""", re.VERBOSE)

懒加载

在一些网站或者app上会看到 鼠标或手势过快,而图片没有加载出来由色块或其他图片代替的情况,当图片出现在我们看到的视图中,再迅速将占位图片换成我们真正想展示的图片,这里使用了一种技术,图片懒加载

为什么要使用懒加载

当你打开一个网站时,浏览器会做许多工作,这其中包括下载各种可能用到的资源,然后渲染呈现在你面前,假设你的网站有大量的图片,那么加载的过程是很耗时的,尤其像那些新闻资讯类需要大量图片的网站,可想而知,网站的初始加载时间会很长,再加上网络等其它影响,用户体验会很差。我们都希望一输入网址,页面立马就呈现在眼前。
既然想要页面立马呈现在面前,那势必要减少浏览器的负荷,优化代码,减少一些不必要的请求和不必要资源的加载,因为你打开网站的时候,浏览器会把所有可能的资源都下载好,而实际上有些资源你并不需要用到,这就造成了浪费。所以有必要在一些资源上做下优化,提高网站加载速度。

爬取图片懒加载的网站

以站长素材为例

站长素材 (chinaz.com)

分析爬虫页面

url很容易确认, 当前的网址就是爬取的url

进入页面后, 我们可以打开控制台(F12), 并选中图片, 可以看到每一张图片就是一个div

我们点击div, 逐层点开, 可以看到内层的img标签就是存放图片链接的

这时, 我们不要往下滑动, 将这个div收起来, 可以看到有多个div, 往下面滚动, 点击倒数的任意一个div, 然后再逐层点开, 我们会发现和第一个div看到的有所不同

此处的img中的src属性变成了src2, 这就是图片懒加载技术, 当图片没有正常渲染到页面之前, 所有的src都被替换成了src2, 只有当图片渲染到页面上, src2才会变成真正的src, 当然, 每个网站使用替换src的名称都不同, 可能别的网站是src100、src5…

不过无论替换的名称是什么, 只要掌握了原理, 我们就可以正常爬取

对分析后的页面进行爬取并解析数据

爬取时正则pattern就用懒加载的src2匹配