来源:互联网转载 | 更新日期:2023-09-07 04:18:03
正好我最近想接触下爬虫,打算整理一下正则表达式,后面会用到。
本文基于python的howto-regex 文档,总结如下:
另外,官方指南:https://docs.python.org/zh-cn/3/library/re.html#module-re
使用正则表达式可以为匹配的可能字符串集指定规则;可以判断此字符串是否与模式匹配等问题。还可以使用正则表达式修改字符串或以各种方式将其拆分。
正则表达式模式被编译成一系列字节码,然后由用C 编写的匹配引擎执行。对于高级用途,可能需要特别注意引擎如何执行给定的正则,并将正则写入以某种方式生成运行速度更快的字节码。
我们首先要了解最简单的正则表达式。由于正则表达式用于对字符串进行操作,因此我们将从最常见的任务开始:匹配字符。
元字符的完整列表如下:
. ^ $ * + ? { } [ ] \ | ( )
这些元字符有的可以用来表示匹配字符,有的可以用来表示字符重复次数,下面依次介绍这些元字符。
1.[ ]:用于指定字符类,里面放你希望匹配的一组字符。既可以单独列出字符,也可以通过给出两个字符并用’-'标记将他们分开来表示一系列字符。
例如:
2. ^:用于匹配字符类未列出的字符。通过包含^作为作为字符类的第一个字符来表示。
例如:
3.‘\’字符:后面可以跟各种字符,以指示各种特殊序列
4. ’ . '字符:用来匹配除换行符之外的任何内容。但是有一个可选模式(re.DOTALL)可以匹配换行符
匹配不同的字符集是正则表达式可以做的第一件事情,另一个功能是可以指定正则表达式的某些部分必须重复一定次数。
5. ’ * '字符:它指定前一个字符可以匹配零次或多次。
6. ’ + '字符:它指定前一个字符可以匹配一次或多次。
7. ’ ? '字符:它指定前一个字符可以匹配一次或零次。
7. ’ ?{m, n}'字符:这个是最复杂的重复限定符是,其中m 和n 是十进制整数。这个限定符意味着必须至少重复m 次,最多重复n 次。
有了上面的了解,下面介绍在python中实际使用它们,re模块提供了正则表达式引擎的接口,允许你将正则编译成对象,然后用它们进行匹配。
正则表达式被编译成模式对象,模式对象具有各种操作的方法,例如搜索模式匹配或执行字符串替换。
>>> import re >>> p = re.compile('ab*') >>> p re.compile('ab*')re.compile() 也接受一个可选的flags 参数,用于启用各种特殊功能和语法变体。后面会介绍可用的设置,但现在只举一个例子
>>> p = re.compile('ab*', re.IGNORECASE)正则作为字符串传递给re.compile() 。正则被处理为字符串,因为正则表达式不是核心Python语言的一部分,并且没有创建用于表达它们的特殊语法。
将正则放在字符串中可以使Python 语言更简单,但有一个缺点就是反斜杠灾难。
正则表达式使用反斜杠字符(’\’) 来表示特殊形式或允许使用特殊字符而不调用它们的特殊含义。
当编译正则表达式之后就得到了模式对象,下面介绍最终的模式对象的方法和属性:
| match() | 从字符串的开头匹配。没有找到匹配,返回None,成功则返回匹配对象的实例,包含匹配相关的信息:起始和终止位置、匹配的子串等的。 |
| search() | 扫描字符串,查找此正则匹配的任何位置。没有找到匹配,返回None,成功则返回匹配对象的实例,包含匹配相关的信息:起始和终止位置、匹配的子串等的。 |
| findall() | 找到正则匹配的所有子字符串,并将它们作为列表返回。 |
| finditer() | 找到正则匹配的所有子字符串,并将它们返回为一个iterator。 |
示例:
>>> import re >>> p = re.compile('[a-z]+') >>> p re.compile('[a-z]+')你可以尝试匹配正则[a-z]+ 的各种字符串。空字符串根本不匹配,因为+ 表示“一次或多次重复”。
match() 在这种情况下应返回None,这将导致解释器不打印输出。你可以显式打印match() 的结果。
现在,让我们尝试一下它应该匹配的字符串,例如tempo。在这个例子中match() 将返回一个匹配对象,因此你应该将结果储存到一个变量中以供稍后使用。
>>> m = p.match('tempo') >>> m <re.Match object; span=(0, 5), match='tempo'>你可以检查匹配对象以获取有关匹配字符串的信息。匹配对象实例也有几个很重要的方法和属性:
| group() | 返回正则匹配的字符串 |
| start() | 返回匹配的开始位置 |
| end() | 返回匹配的结束位置 |
| span() | 返回包含匹配(start, end)位置的元组 |
group() 返回正则匹配的子字符串。start() 和end() 返回匹配的起始和结束索引。span() 在单个元组
中返回开始和结束索引。由于match() 方法只检查正则是否在字符串的开头匹配,所以start() 将始终为零。但是,模式的search() 方法会扫描字符串,因此在这种情况下匹配可能不会从零开始。
在实际程序中,最常见的样式是在变量中存储匹配对象,然后检查它是否为None
p = re.compile( ... ) m = p.match( 'string goes here' ) if m:print('Match found: ', m.group()) else:print('No match')两种模式方法返回模式的所有匹配项。
findall() 返回匹配字符串的列表:
>>> p = re.compile(r'\d+') >>> p.findall('12 drummers drumming, 11 pipers piping, 10 lords a-leaping') ['12', '11', '10']在这个例子中需要r 前缀,使字面为原始字符串字面,因为普通的“加工”字符串字面中的转义序列不能被Python 识别为正则表达式,导致DeprecationWarning 并最终产生SyntaxError.
findall() 必须先创建整个列表才能返回结果。finditer() 方法将一个匹配对象的序列返回为一个iterator
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...') >>> iterator <callable_iterator object at 0x...> >>> for match in iterator: ... print(match.span()) ... (0, 2) (22, 24) (29, 31)编译标志允许你修改正则表达式的工作方式。标志在re 模块中有两个名称,长名称如IGNORECASE 和一个简短的单字母形式,例如I。(如果你熟悉Perl 的模式修饰符,则单字母形式使用和其相同的字母;例如,re.VERBOSE 的缩写形式为re.X。)
多个标志可以通过按位或运算来指定它们;例如,re.I | re.M 设置I 和M 标志。
下面是可用标志表,以及每个标志的更详细说明。
| ASCII, A | 使几个转义如\w、\b、\s 和\d 匹配仅与具有相应特征属性的ASCII 字符匹配。 |
| DOTALL, S | 使. 匹配任何字符,包括换行符。 |
| IGNORECASE, I | 进行大小写不敏感匹配。 |
| LOCALE, L | 进行区域设置感知匹配。 |
| MULTILINE, M | 多行匹配,影响^ 和$。 |
| VERBOSE, X(为’ 扩展’) | 启用详细的正则,可以更清晰,更容易理解。 |
IGNORECASE, I
LOCALE, L
MULTILINE, M
DOTALL, S
ASCII, A
VERBOSE, X
到目前为止,只介绍了正则表达式的一部分功能。在本节中,将介绍一些新的元字符,以及如何使用组来检索匹配的文本部分。
要讨论的其余一些元字符是零宽度断言。它们不会使解析引擎在字符串中前进一个字符;相反,它们根本不占用任何字符,只是成功或失败。
例如,\b 是一个断言,指明当前位置位于字边界;这个位置根本不会被\b改变。这意味着永远不应重复零宽度断言,因为如果它们在给定位置匹配一次,它们显然可以无限次匹配。
1.| 或者“or”运算符
2.^:当该字符不在字符类中,表示在行的开头匹配
3.$ 匹配行的末尾,定义为字符串的结尾,或者后跟换行符的任何位置
>>> print(re.search('}$', '{block}')) <re.Match object; span=(6, 7), match='}'> >>> print(re.search('}$', '{block} ')) None >>> print(re.search('}$', '{block}\n')) <re.Match object; span=(6, 7), match='}'>4.\A 仅匹配字符串的开头
5. \Z 只匹配字符串尾
6. \b 字边界
7. \B 另一个零宽度断言,这与\b 相反,仅在当前位置不在字边界时才匹配。
通常,你需要获取更多信息,而不仅仅是正则是否匹配。正则表达式通常用于通过将正则分成几个子组来解析字符串,这些子组匹配不同的感兴趣组件。例如,RFC-822 标题行分为标题名称和值,用’:’ 分隔,如下所示:
From: author@example.com User-Agent: Thunderbird 1.5.0.9 (X11/20061227) MIME-Version: 1.0 To: editor@example.com这可以通过编写与整个标题行匹配的正则表达式来处理,并且具有与标题名称匹配的一个组,以及与标题的值匹配的另一个组.
组由’(’,’)’ 元字符标记。
解释一下:上面的模式指的是:首先找到一个前后是空格的单词,然后后向引用子组1,也就是子组1的内容和前面单词的相同,最后加空格。只有’the the’满足条件。
精心设计的正则可以使用许多组,既可以捕获感兴趣的子串,也可以对正则本身进行分组和构建。在复杂的正则中,很难跟踪组号。有两个功能可以帮助解决这个问题。它们都使用常用语法进行正则表达式扩展。
Perl 5 以其对标准正则表达式的强大补充而闻名。对于这些新功能,Perl 开发人员无法选择新的单键击元字符或以\ 开头的新特殊序列,否则Perl 的正则表达式与标准正则容易混淆。例如,如果他们选择& 作为一个新的元字符,旧的表达式将假设& 是一个普通字符,并且不会编写& 或[&]。
Perl 开发人员选择的解决方案是使用(?..) 作为扩展语法。括号后面的? 是一个语法错误,因为? 没有什么可重复的,所以这并没有引入任何兼容性问题。紧跟在? 之后的字符表示正在使用什么扩展名,所以(?=foo) 是一个东西(一个正向的先行断言)和(?:foo) 是其它东西(包含子表达式foo 的非捕获组)。
Python 支持一些Perl 的扩展,并增加了新的扩展语法用于Perl 的扩展语法。如果在问号之后的第一个字符为P,即表明其为Python 专属的扩展。
现在已经了解了一般的扩展语法,回到简化复杂正则中组处理的功能。
有时会想要使用组来表示正则表达式的一部分,但是对检索组的内容不感兴趣。可以通过使用非捕获组来显式表达这个事实: (?: … ),你可以用任何其他正则表达式替换 ‘…’ 。
>>> m = re.match("([abc])+", "abc") >>> m.groups() ('c',) >>> m = re.match("(?:[abc])+", "abc") >>> m.groups() ()更重要的功能是命名组:不是通过数字引用它们,而是可以通过名称引用组。
检索m.group(‘zonem’) 显然要容易得多,而不必记住检索第9 组
另一个零宽度断言是前向断言。前向断言以正面和负面形式提供,如下所示:
(?=⋯) 正向前向断言。如果包含的正则表达式,由… 表示,在当前位置成功匹配,则成功,否则失败。但是,一旦尝试了包含的表达式,匹配的引擎就不会前进;模式其余的部分会在在断言开始的地方尝试。
(?!⋯) 负向前向断言。这与正向前向断言相反;如果包含的表达式在字符串中的当前位置不匹配,则成功。
例子说明:
更具体一些,考虑一个简单的模式来匹配文件名并将其拆分为基本名称和扩展名,用. 分隔。例如,在news.rc 中,news 是基本名称,rc 是文件名的扩展名。
与此匹配的模式非常简单: .*[.].*$
请注意,. 需要特别处理,因为它是元字符,所以它在字符类中只能匹配特定字符。还要注意尾随的$;添加此项以确保扩展名中的所有其余字符串都必须包含在扩展名中。这个正则表达式匹配foo.bar、autoexec.bat、sendmail.cf 和printers.conf。
现在,考虑使更复杂一点的问题;如果你想匹配扩展名不是bat 的文件名怎么办?一些错误的尝试:
.*[.][^b].*$ 上面的第一次尝试试图通过要求扩展名的第一个字符不是b 来排除bat。这是错误的,因为模式也与foo.bar 不匹配。
当你尝试通过要求以下一种情况匹配来修补第一个解决方案时,表达式变得更加混乱:扩展的第一个字符不是b。第二个字符不a;或者第三个字符不是t。
.*[.]([^b]..|.[^a].|..[^t])$这接受foo.bar 并拒绝autoexec.bat,但它需要三个字母的扩展名,并且不接受带有两个字母扩展名的文件名,例如sendmail.cf。为了解决这个问题,我们会再次使模式复杂化。
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$在这次尝试中,第二个和第三个字母都是可选的,以便允许匹配的扩展名短于三个字符,例如sendmail.cf。
模式现在变得非常复杂,这使得它难以阅读和理解。更糟糕的是,如果问题发生变化并且你想要将bat 和
exe 排除为扩展,那么该模式将变得更加复杂和混乱。
负面前向消除了所有这些困扰:
.*[.](?!bat$)[^.]*$ 负向前向意味着:如果表达式bat 此时不匹配,请尝试其余的模式;如果bat$匹配,整个模式将失败。尾随的$ 是必需的,以确保允许像sample.batch 这样的扩展只以bat 开头的文件能通过。[^.]* 确保当文件名中有多个点时,模式有效。
现在很容易排除另一个文件扩展名;只需在断言中添加它作为替代。以下模块排除以bat 或exe:.*[.](?!bat$|exe$)[^.]*$
到目前为止,我们只是针对静态字符串执行搜索。正则表达式通常也用于以各种方式修改字符串,使用以下模式方法:
| split() | 将字符串拆分为一个列表,在正则匹配的任何地方将其拆分 |
| sub() | 找到正则匹配的所有子字符串,并用不同的字符串替换它们 |
| subn() | 与sub() 相同,但返回新字符串和替换次数 |
模式的split() 方法在正则匹配的任何地方拆分字符串,返回一个片段列表。它类似于split() 字符串方法,但在分隔符的分隔符中提供了更多的通用性;字符串的split() 仅支持按空格或固定字符串进行拆分。当然,还有一个模块级re.split() 函数。
.split(string[, maxsplit=0 ])
通过正则表达式的匹配拆分字符串。如果在正则中使用捕获括号,则它们的内容也将作为结果列表的一部分返回。如果maxsplit 非零,则最多执行maxsplit 次拆分。
有时你不仅对分隔符之间的文本感兴趣,而且还需要知道分隔符是什么。如果在正则中使用捕获括号,则它们的值也将作为列表的一部分返回。比较以下调用:
>>> p = re.compile(r'\W+') >>> p2 = re.compile(r'(\W+)') >>> p.split('This... is a test.') ['This', 'is', 'a', 'test', ''] >>> p2.split('This... is a test.') ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']模块级函数re.split() 添加要正则作为第一个参数,但在其他方面是相同的
>>> re.split(r'[\W]+', 'Words, words, words.') ['Words', 'words', 'words', ''] >>> re.split(r'([\W]+)', 'Words, words, words.') ['Words', ', ', 'words', ', ', 'words', '.', ''] >>> re.split(r'[\W]+', 'Words, words, words.', 1) ['Words', 'words, words.']另一个常见任务是找到模式的所有匹配项,并用不同的字符串替换它们。sub() 方法接受一个替换值,可以是字符串或函数,也可以是要处理的字符串。
.sub(replacement, string[, count=0 ])
返回通过替换replacement 替换string 中正则的最左边非重叠出现而获得的字符串。如果未找到模式,则string 将保持不变。
可选参数count 是要替换的模式最大的出现次数;count 必须是非负整数。默认值0 表示替换所有。
这是一个使用sub() 方法的简单示例。它用colour 这个词取代颜色名称:
>>> p = re.compile('(blue|white|red)') >>> p.sub('colour', 'blue socks and red shoes') 'colour socks and colour shoes' >>> p.sub('colour', 'blue socks and red shoes', count=1) 'colour socks and red shoes'subn() 方法完成相同的工作,但返回一个包含新字符串值和已执行的替换次数的2 元组:
>>> p = re.compile('(blue|white|red)') >>> p.subn('colour', 'blue socks and red shoes') ('colour socks and colour shoes', 2) >>> p.subn('colour', 'no colours at all') ('no colours at all', 0)仅当空匹配与前一个空匹配不相邻时,才会替换空匹配。
>>> p = re.compile('x*') >>> p.sub('-', 'abxd') '-a-b--d-'如果replacement 是一个字符串,则处理其中的任何反斜杠转义。也就是说,\n 被转换为单个换行符,\r 被
转换为回车符,依此类推。诸如& 之类的未知转义是孤立的。后向引用,例如\6,被替换为正则中相应组
匹配的子字符串。这使你可以在生成的替换字符串中合并原始文本的部分内容。
这个例子匹配单词section 后跟一个用{,} 括起来的字符串,并将section 改为subsection
>>> p = re.compile('section{ ( [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First} section{second}') 'subsection{First} subsection{second}'还有一种语法用于引用由(?P…) 语法定义的命名组。\g 将使用名为name 的组匹配的子字符串,\g 使用相应的组号。因此\g<2> 等同于\2,但在诸如\g<2>0 之类的替换字符串中并不模糊。( \20 将被解释为对组20 的引用,而不是对组2 的引用,后跟字面字符’0’。) 以下替换都是等效的,但使用所有三种变体替换字符串。
>>> p = re.compile('section{ (?P<name> [^}]* ) }', re.VERBOSE) >>> p.sub(r'subsection{\1}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<1>}','section{First}') 'subsection{First}' >>> p.sub(r'subsection{\g<name>}','section{First}') 'subsection{First}'replacement 也可以是一个函数,它可以为你提供更多控制。如果replacement 是一个函数,则为pattern 的每次非重叠出现将调用该函数。在每次调用时,函数都会传递一个匹配的匹配对象参数,并可以使用此信息计算所需的替换字符串并将其返回。
在以下示例中,替换函数将小数转换为十六进制
>>> def hexrepl(match): ... "Return the hex string for a decimal number" ... value = int(match.group()) ... return hex(value) ... >>> p = re.compile(r'\d+') >>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') 'Call 0xffd2 for printing, 0xc000 for user code.'使用模块级别re.sub() 函数时,模式作为第一个参数传递。图案可以作为对象或字符串提供;如果需要指
定正则表达式标志,则必须使用模式对象作为第一个参数,或者在模式字符串中使用嵌入式修饰符,例如:
sub("(?i)b+", "x", "bbbb BBBB") 返回’x x’,其中的(?i)表示扩展的忽略大小写的表示方法。
正则表达式对于某些应用程序来说是一个强大的工具,但在某些方面,它们的行为并不直观,有时它们的行为方式与你的预期不同。本节将指出一些最常见的陷阱。
有时使用re 模块是一个错误。如果你匹配固定字符串或单个字符类,并且你没有使用任何re 功能,例如
IGNORECASE 标志,那么正则表达式的全部功能可能不是必需的。字符串有几种方法可以使用固定字符串执行操作,它们通常要快得多,因为实现是一个针对此目的而优化的单个小C 循环,而不是大型、更通用的正则表达式引擎。
一个例子:用另一个固定字符串替换一个固定字符串;例如,你可以用deed 替换word 。re.sub()看起来像是用于此的函数,但请考虑replace() 方法。
注意replace() 也会替换单词里面的word ,把swordfish 变成sdeedfish ,但简单的正则word 也会这样做。(为了避免对单词的部分进行替换,模式必须是bword\b,以便要求word 在任何一方都有一个单词边界。这使得工作超出了replace() 的能力。)
另一个常见任务是从字符串中删除单个字符的每个匹配项或将其替换为另一个字符。你可以用re.sub(’\n’, ’ ', S) 之类的东西来做这件事,但是translate() 能够完成这两项任务,并且比任何正则表达式都快。
简而言之,在转向re 模块之前,请考虑是否可以使用更快更简单的字符串方法解决问题。
match()仅仅从字符串start=0处去进行匹配,如果要匹配的对象start不为0,则不会匹配到。
>>> print(re.match('super', 'superstition').span()) (0, 5) >>> print(re.match('super', 'insuperable')) Nonesearch() 将向前扫描字符串,报告它找到的第一个匹配项
>>> print(re.search('super', 'superstition').span()) (0, 5) >>> print(re.search('super', 'insuperable').span()) (2, 7)有时可能会想继续使用re.match() ,只需在你的正则前面添加.* 。但是建议使用re.search()直接解决这个问题。
这主要是因为:正则表达式编译器对正则进行一些分析,以加快寻找匹配的过程。其中一个分析可以确定匹配的第一个特征必须是什么;例如,以Crow 开头的模式必须与’C’ 匹配。分析让引擎快速扫描字符串,寻找起始字符,只在找到’C’ 时尝试完全匹配。添加.* 会使这个优化失效,需要扫描到字符串的末尾,然后回溯以找到正则的其余部分的匹配。
当重复一个正则表达式时,就像在a* 中一样,最终的动作就是消耗尽可能多的模式。当你尝试匹配一对对称分隔符,例如HTML 标记周围的尖括号时,这个事实经常会让你感到困惑。因为.* 的贪婪性质,用于匹配单个HTML 标记的简单模式不起作用
>>> s = '<html><head><title>Title</title>' >>> len(s) 32 >>> print(re.match('<.*>', s).span()) (0, 32) >>> print(re.match('<.*>', s).group()) <html><head><title>Title</title>正则匹配’<’ 中的’’ 和.* 消耗字符串的其余部分。正则中还有更多的剩余东西,并且> 在字符
串的末尾不能匹配,所以正则表达式引擎必须逐个字符地回溯,直到它找到匹配> 。最终匹配从’‘中的’<’ 扩展到’’ 中的’>’ ,而这并不是你想要的结果。
在这种情况下,解决方案是使用非贪婪的限定符*? 、+? 、?? 或{m,n}? ,匹配为尽可能少的文字。在上面的例子中,在第一次’<’ 匹配后立即尝试’>’ ,当它失败时,引擎一次前进一个字符,每一步都重试’>’。这产生了正确的结果:
>>> print(re.match('<.*?>', s).group()) <html>(请注意,使用正则表达式解析HTML 或XML 很痛苦。快而脏的模式将处理常见情况,但HTML 和XML有特殊情况会破坏明显的正则表达式;当你编写正则表达式处理所有可能的情况时,模式将非常复杂。使用HTML 或XML 解析器模块来执行此类任务。)
到目前为止,你可能已经注意到正则表达式是一种非常紧凑的表示法,但它们并不是非常易读。具有中等复杂度的正则可能会成为反斜杠、括号和元字符的冗长集合,使其难以阅读和理解。
对于这样的正则,在编译正则表达式时指定re.VERBOSE 标志可能会有所帮助,因为re.VERBOSE允许你更清楚地格式化正则表达式。
re.VERBOSE 标志有几种效果。
肝了很长时间,终于肝完了。
都看到这了,确定不三连?
Copyright © 网站出售-网站交易平台 版权信息
网站备案号:黔ICP备2023004141号