从零开始的爬虫生涯(五):re正则表达式详解

前言

工欲善其事,必先利其器。这篇文章我们将一起学习处理爬取到数据的一种强大的工具:正则表达式。


正则表达式简介

正则表达式是对字符串(包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。(来自百度百科

要学习正则表达式的具体用法,最好的方式是参阅re库官方中文文档

示例

import re

regex = 'abc'
string = 'www.abc.com'

# 预编译正则表达式(预编译的好处是,可以多次调用)
# re.compile('string') ==>子串
p = re.compile(regex)  # p是.pattern对象的实例,是一个缓存
a = p.search(string)  # 调用这个实例的方法search('string')==>母串
print(a)  # <_sre.SRE_Match object; span=(4, 7), match='abc'>
print(a.span())  # span()返回匹配的位置
print(a.group())  # group()返回匹配的组

# 不使用预编译
a = re.search(regex, string)
print(a)

1. re库的函数

1.1 match 和 search

match :re.match(pattern,string,flags=0),从开始位置匹配,不成功返回none,成功返回一个Match对象
search :re.search(pattern,string,flags=0),扫描整个string,不成功返回none,成功返回match对象

>>> import re
>>> string = 'www.abc.com'
>>> regex = 'www'
>>> m1 = re.match(regex, string)
>>> m1
<re.Match object; span=(0, 3), match='www'>
>>> m2 = re.search(regex, string)
>>> m2
<re.Match object; span=(0, 3), match='www'>
>>> regex = 'abc'
>>> m3 = re.match(regex, string)
>>> m3
# 返回了none 
>>> m4 = re.search(regex, string)
>>> m4
<re.Match object; span=(4, 7), match='abc'>

1.2 findall 和 finditer

findall:re.findall(pattern,string,flags=0),扫描整个字符串,返回所有匹配正则的列表
finditer:re.finditer(pattern,string,flags=0),扫描整个字符串,返回所有匹配正则的子串的迭代器(迭代器概念请参考:这篇文章

>>> import re
>>> string = 'love u, love u'
>>> regex = 'love'
>>> m1 = re.findall(regex, string)
>>> m1
['love', 'love']
>>> m2 = re.finditer(regex, string)
>>> m2
<callable_iterator object at 0x000001EA241C6240>
>>> for m2_ in m2:
... 	print(m2_)
...
<re.Match object; span=(0, 4), match='love'>
<re.Match object; span=(8, 12), match='love'>

1.3 fullmatch 和 sub

fullmatch:re.fullmatch(pattern,string,flags=0),如果整个 string 匹配到正则表达式样式,就返回一个相应的 匹配对象 。 否则就返回一个 None

sub:re.sub(pattern, repl, string, count=0, flags=0),返回通过使用 repl 替换在 string 最左边非重叠出现的 pattern 而获得的字符串。 如果样式没有找到,则不加改变地返回 string。可选参数 count 是要替换的最大次数;count 必须是非负整数。如果省略这个参数或设为 0,所有的匹配都会被替换。

subn:re.subn(pattern, repl, string, count=0, flags=0),行为与 sub() 相同,但是返回一个元组 (字符串, 替换次数).

>>> import re
>>> m1 = re.fullmatch('hello','hello world,hello python')
>>> m1
None
>>> m2 = re.sub('-','/','2021-9-5')
>>> m2
2021/9/5
>>> m7 = re.sub('-','/','2021-9-5',1)
>>> m7
2021/9-5

repl也可以接受一个函数(这个函数只能有一个匹配对象参数,并返回一个替换后的字符串),此时其会对每个非重复的 pattern 的情况调用。

>>> import re
>>> def dashrepl(matchobj):
...    if matchobj.group(0) == '-': return ' '
...    else: return '-'
...    
>>> re.sub('--', dashrepl, 'pro----gram-files')
'pro--gram-files'
>>> re.sub('-', dashrepl, 'pro----gram-files')
'pro    gram files'

1.4 split

split:re.split(pattern, string, maxsplit=0, flags=0),以pattern为分隔标志来分开string(Split string by the occurrences of pattern)。

re.split( )和string.split( )基本一致

>>> import re
>>> re.split('and','apple and banana and orange')
['apple ', ' banana ', ' orange']
>>> 'apple and banana and orange'.split('and')
['apple ', ' banana ', ' orange']
>>> re.split('and','apple and banana and orange',1)
['apple ', ' banana and orange']

1.5 compile 和 purge

compile:re.compile(pattern, flags=0),将正则表达式的样式编译为一个 正则表达式对象 (正则对象),可以用于匹配。如果需要多次使用这个正则表达式的话,使用 re.compile() 和保存这个正则对象以便复用,可以让程序更加高效。

purge:re.purge(),在不同的Python版本中,缓存中已编译过的正则表达式对象的数目可能不同,而且没有文档记录。re.purge()函数能够用于清除这些缓存。

对正则表达式对象(Regular Expression Objects)可以使用前面提到的所有方法,通用的使用方法为:Pattern.function(string[, pos[, endpos]]),其中可选的第二个参数 pos 给出了字符串中开始搜索的位置索引;默认为 0,它不完全等价于字符串切片; ‘^’ 样式字符匹配字符串真正的开头,和换行符后面的第一个字符,但不会匹配索引规定开始的位置。可选参数 endpos 限定了字符串搜索的结束;它假定字符串长度到 endpos , 所以只有从 pos 到 endpos - 1 的字符会被匹配。如果 endpos 小于 pos,就不会有匹配产生;另外,如果 rx 是一个编译后的正则对象, rx.search(string, 0, 50) 等价于 rx.search(string[:50], 0)。

>>> import re
>>> pattern = re.compile("d")
>>> pattern.search("dog")     # Match at index 0
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1)  # No match; search doesn't include the "d"

1.6 escape

escape:re.escape(pattern),转义 pattern 中的特殊字符。如果你想对任意可能包含正则表达式元字符的文本字符串进行匹配,它就是有用的。

>>> import re
>>> re.escape(r'www.study.cn')
'www\\.study\\.cn'

2. 使用组

组的概念在前面特殊符号部分已经提到过。通俗来讲,组就是用括号分隔的正则表达式片段,这些片段存在一个id指示其是第几组,所有的组和组外的片段组成完整的正则表达式。

>>> import re
>>> m = re.search(r'(fkit).(org)',r'www.fkit0org is a good domain')
>>> m.group(0)  # 全部组
'fkit0org'
>>> m.group(1)  # 第一组
'fit'
>>> m.group()  # 返回所有组所匹配的字符串组成的元组
('fkit', 'org')

我们还可以对组进行命名,这将在下一节介绍。

3. re库中的特殊符号

.(点) 在默认模式,匹配除了换行的任意字符。如果指定了标签 DOTALL ,它将匹配包括换行符的任意字符。

^(插入符号) 匹配字符串的开头, 并且在 MULTILINE 模式也匹配换行后的首个符号。

$匹配字符串尾或者在字符串尾的换行符的前一个字符,在 MULTILINE 模式下也会匹配换行符之前的文本。

*对它前面的正则式匹配0到任意次重复, 尽量多的匹配字符串。 ab* 会匹配 ‘a’,‘ab’,或者 ‘a’ 后面跟随任意个 ‘b’。

+对它前面的正则式匹配1到任意次重复。 ab+ 会匹配 ‘a’ 后面跟随1个以上到任意个 ‘b’,它不会匹配 ‘a’。

?对它前面的正则式匹配0到1次重复。 ab? 会匹配 ‘a’ 或者 ‘ab’。

{m}对其之前的正则式指定匹配 m 个重复;少于 m 的话就会导致匹配失败。比如, a{6} 将匹配6个 ‘a’ , 但是不能是5个。

{m, n}对正则式进行 m 到 n 次匹配,在 m 和 n 之间取尽量多。 比如,a{3,5} 将匹配 3 到 5个 ‘a’。忽略 m 意为指定下界为0,忽略 n 指定上界为无限次。 比如 a{4,}b 将匹配 ‘aaaab’ 或者1000个 ‘a’ 尾随一个 ‘b’,但不能匹配 ‘aaab’。逗号不能省略,否则无法辨别修饰符应该忽略哪个边界。

*?+???{m, n}?对字符串进行非贪婪模式(抑或称最小方式)匹配,比如,对于 ‘aaaaaa’, a{3,5} 匹配 5个 ‘a’ ,而 a{3,5}? 只匹配3个 ‘a’。

\转义特殊字符(允许你匹配 ‘*’, ‘?’, 或者此类其他),或者表示一个特殊序列;特殊序列之后进行讨论。强烈建议所有的正则表达式均使用原始字符串(r'raw',以防止re转义和python自身转义造成的的理解困难。

[]用于表示一个字符集合。在一个集合中:

  • 字符可以单独列出,比如 [amk] 匹配 ‘a’, ‘m’, 或者 ‘k’。

  • 可以表示字符范围,通过用 ‘-’ 将两个字符连起来。比如 [a-z] 将匹配任何小写ASCII字符, [0-5][0-9] 将匹配从 00 到 59 的两位数字, [0-9A-Fa-f] 将匹配任何十六进制数位。 如果 - 进行了转义 (比如 [a\-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-]),它就只表示普通字符 ‘-’。

  • 特殊字符在集合中,失去它的特殊含义。比如 [(+*)] 只会匹配这几个文法字符 ‘(’, ‘+’, ‘*’, or ‘)’。

  • 字符类如 \w 或者 \S (如下定义) 在集合内可以接受,它们可以匹配的字符由 ASCII 或者 LOCALE 模式决定。

  • 不在集合范围内的字符可以通过 取反 来进行匹配。如果集合首字符是 ‘^’ ,所有 不 在集合内的字符将会被匹配,比如 [^5] 将匹配所有字符,除了 ‘5’, [^^] 将匹配所有字符,除了 ‘^’. ^ 如果不在集合首位,就没有特殊含义。

  • 在集合内要匹配一个字符 ‘]’,有两种方法,要么就在它之前加上反斜杠,要么就把它放到集合首位。比如, [()[\]{}] 和 [{}] 都可以匹配括号。

|A|B, A 和 B 可以是任意正则表达式,创建一个正则表达式,匹配 A 或者 B. 任意个正则表达式可以用 ‘|’ 连接。一旦 A 匹配成功, B 就不再进行匹配,即便它能产生一个更好的匹配。

(...)(组合),匹配括号内的任意正则表达式,并标识出组合的开始和结尾。匹配完成后,组合的内容可以被获取,并可以在之后用 \number 转义序列进行再次匹配。

(?...)创建内联标记的特殊方法,实际上是一种扩展标记法。

  • (?aiLmsux)( ‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的一个或多个) 这个组合匹配一个空字符串;这些字符对正则表达式设置以下标记 re.A (只匹配ASCII字符), re.I (忽略大小写), re.L (语言依赖), re.M (多行模式), re.S (点dot匹配全部字符), re.U (Unicode匹配), and re.X (冗长模式)。 如果你想将这些标记包含在正则表达式中,这个方法就很有用,免去了在 re.compile() 中传递 flag 参数。标记应该在表达式字符串首位表示。

  • (?:...)正则括号的非捕获版本。 匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。

  • (?aiLmsux-imsx:...)(‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的0或者多个, 之后可选跟随 ‘-’ 在后面跟随 ‘i’ , ‘m’ , ‘s’ , ‘x’ 中的一到多个 .) 这些字符为表达式的其中一部分 设置 或者 去除 相应标记 re.A (只匹配ASCII), re.I (忽略大小写), re.L (语言依赖), re.M (多行), re.S (点匹配所有字符), re.U (Unicode匹配), and re.X (冗长模式)。(3.7版本之前)‘a’, ‘L’ and ‘u’ 作为内联标记是相互排斥的, 所以它们不能结合在一起,或者跟随 ‘-’ 。( 3.7 版之后 )符号 ‘a’, ‘L’ 和 ‘u’ 同样可以用在一个组合内。

  • (?P<name>...)给匹配出的字符组命名(参考:这篇文章

  • (?P=name)反向引用一个命名组合;它匹配前面那个叫 name 的命名组中匹配到的串同样的字串。

  • (?#...)注释,里面的内容会被忽略

  • (?=...)匹配 … 的内容,但是并不消费样式的内容。比如, Isaac (?=Asimov) 匹配 'Isaac ’ 只有在后面是 ‘Asimov’ 的时候。

  • (?!...)匹配 … 不符合的情况。比如说, Isaac (?!Asimov) 只有后面 不 是 ‘Asimov’ 的时候才匹配 'Isaac ’ 。

  • (?<=...)匹配字符串的当前位置,它的前面匹配 … 的内容到当前位置。包含的匹配样式必须是定长的,意思就是 abc 或 a|b 是允许的,但是 a* 和 a{3,4} 不可以。

  • (?<!...)匹配当前位置之前不是 … 的样式。类似正向后视断定,包含的样式匹配必须是定长的。

  • (?(id/name)yes-pattern|no-pattern)如果给定的 id 或 name 存在,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-pattern,no-pattern 可选,也可以被忽略。

由 ‘’ 和一个字符组成的特殊序列在以下列出。 如果普通字符不是ASCII数位或者ASCII字母,那么正则样式将匹配第二个字符。比如,$ 匹配字符 ‘$’。

  • \number匹配数字代表的组合。number相当于组合的id。每个括号是一个组合,组合从1开始编号。比如 (.+) \1 匹配 ‘the the’ 或者 ‘55 55’, 但不会匹配 ‘thethe’ (注意组合后面的空格)。这个特殊序列只能用于匹配前面99个组合。如果 number 的第一个数位是0, 或者 number 是三个八进制数,它将不会被看作是一个组合,而是八进制的数字值。在 ‘[’ 和 ‘]’ 字符集合内,任何数字转义都被看作是字符。

  • \A只匹配字符串开始。

  • \b匹配空字符串,但只在单词开始或结尾的位置。一个单词被定义为一个单词字符的序列。注意,通常 \b 定义为 \w 和 \W 字符之间,或者 \w 和字符串开始/结尾的边界, 意思就是 r’\bfoo\b’ 匹配 ‘foo’, ‘foo.’, ‘(foo)’, ‘bar foo baz’ 但不匹配 ‘foobar’ 或者 ‘foo3’。

    默认情况下,Unicode字母和数字是在Unicode样式中使用的,但是可以用 ASCII 标记来更改。如果 LOCALE 标记被设置的话,词的边界是由当前语言区域设置决定的,\b 表示退格字符,以便与Python字符串文本兼容。

  • \B匹配空字符串,但 不 能在词的开头或者结尾。意思就是 r’py\B’ 匹配 ‘python’, ‘py3’, ‘py2’, 但不匹配 ‘py’, ‘py.’, 或者 ‘py!’. \B 是 \b 的取非,所以Unicode样式的词语是由Unicode字母,数字或下划线构成的,虽然可以用 ASCII 标志来改变。如果使用了 LOCALE 标志,则词的边界由当前语言区域设置。

  • \d 对于 Unicode (str) 样式:匹配任何Unicode十进制数(就是在Unicode字符目录[Nd]里的字符)。这包括了 [0-9] ,和很多其他的数字字符。如果设置了 ASCII 标志,就只匹配 [0-9] 。

    对于8位(bytes)样式:匹配任何十进制数,就是 [0-9]。

  • \D 匹配任何非十进制数字的字符。就是 \d 取非。 如果设置了 ASCII 标志,就相当于 [^0-9] 。

  • \s 对于 Unicode (str) 样式:匹配任何Unicode空白字符(包括 [ \t\n\r\f\v] ,还有很多其他字符,比如不同语言排版规则约定的不换行空格)。如果 ASCII 被设置,就只匹配 [ \t\n\r\f\v] 。

    对于8位(bytes)样式:匹配ASCII中的空白字符,就是 [ \t\n\r\f\v] 。

  • \S 匹配任何非空白字符。就是 \s 取非。如果设置了 ASCII 标志,就相当于 [^ \t\n\r\f\v] 。

  • \w 对于 Unicode (str) 样式:匹配Unicode词语的字符,包含了可以构成词语的绝大部分字符,也包括数字和下划线。如果设置了 ASCII 标志,就只匹配 [a-zA-Z0-9_] 。

    对于8位(bytes)样式:匹配ASCII字符中的数字和字母和下划线,就是 [a-zA-Z0-9_] 。如果设置了 LOCALE 标记,就匹配当前语言区域的数字和字母和下划线。

  • \W 匹配非单词字符的字符。这与 \w 正相反。如果使用了 ASCII 旗标,这就等价于 [^a-zA-Z0-9_]。如果使用了 LOCALE 旗标,则会匹配当前区域中既非字母数字也非下划线的字符。

  • \Z 只匹配字符串尾。

4. 正则表达式旗标(flags)

可以在旗标前加-表示弃用该旗标

re.A re.ASCII 让 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII,而不是Unicode。这只对Unicode样式有效,会被byte样式忽略。相当于前面语法中的内联标志 (?a) 。

re.DEBUG 显示编译时的debug信息,没有内联标记。

re.I re.IGNORECASE 进行忽略大小写匹配;表达式如 [A-Z] 也会匹配小写字符。Unicode匹配(比如 Ü 匹配 ü)同样有用,除非设置了 re.ASCII 标记来禁用非ASCII匹配。当前语言区域不会改变这个标记,除非设置了 re.LOCALE 标记。这个相当于内联标记 (?i) 。注意,当设置了 IGNORECASE 标记,搜索Unicode样式 [a-z] 或 [A-Z] 的结合时,它将会匹配52个ASCII字符和4个额外的非ASCII字符: ‘İ’ (U+0130, 拉丁大写的 I 带个点在上面), ‘ı’ (U+0131, 拉丁小写没有点的 I ), ‘ſ’ (U+017F, 拉丁小写长 s) and ‘K’ (U+212A, 开尔文符号).如果使用 ASCII 标记,就只匹配 ‘a’ 到 ‘z’ 和 ‘A’ 到 ‘Z’ 。

re.L re.LOCALE 由当前语言区域决定 \w, \W, \b, \B 和大小写敏感匹配。这个标记只能对byte样式有效。这个标记不推荐使用,因为语言区域机制很不可靠,它一次只能处理一个 "习惯”,而且只对8位字节有效。Unicode匹配在Python 3 里默认启用,并可以处理不同语言。 这个对应内联标记 (?L) 。

re.M re.MULTILINE 设置以后,样式字符 ‘^’ 匹配字符串的开始,和每一行的开始(换行符后面紧跟的符号);样式字符 ‘$’ 匹配字符串尾,和每一行的结尾(换行符前面那个符号)。默认情况下,’^’ 匹配字符串头,’$’ 匹配字符串尾。对应内联标记 (?m) 。

re.S re.DOTALL 让 ‘.’ 特殊字符匹配任何字符,包括换行符;如果没有这个标记,’.’ 就匹配 除了 换行符的其他任意字符。对应内联标记 (?s) 。

re.X re.VERBOSE 这个标记允许你编写更具可读性更友好的正则表达式。通过分段和添加注释。空白符号会被忽略,除非在一个字符集合当中或者由反斜杠转义,或者在 *?, (?: or (?P<…> 分组之内。当一个行内有 # 不在字符集和转义序列,那么它之后的所有字符都是注释。对应内联标记 (?x) 。

5. 正则表达式实战

5.1 匹配数字

  • 数字 ^[0-9]*$从开始匹配到结尾,匹配0-9且匹配多于0个字符
    ^\d*$原理类似
  • n位数字 ^\d{n}$从开始匹配到结尾,匹配数字且匹配n位
  • 零和非零开头的数字: ^(0|[1-9][0-9]*)$ 从开始匹配到结尾,匹配0,或第一位匹配1-9,第二位开始匹配任意位数字
  • 非零开头的最多带两位小数的数字:^([1-9][0-9]*)(\.[0-9]{1,2})?$匹配一次任意位的数字,匹配0次或多次小数点后面带一到两位数字
  • 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$匹配一个或0个至少一个数字, 一个或0个( . 和至少一个数字)
  • 有1~3位小数的正实数:^[0-9]+(\.[0-9]{1,3})?$ 匹配至少一位0-9的数字和一次或0次( . 和一到三位0-9)
  • 浮点数:^(-?\d+)(\.\d+)?$

5.2 匹配特定字符

  • 汉字:^[\u4e00-\u9fa5]{0,}$
  • 由数字、26个英文字母或者下划线组成的字符串:^\w+$
  • 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
  • 禁止输入含有^ % & ’ , ; = ? $ "等字符:[^%&',;=?$\x22]+

5.3 特殊需求

  • Email地址:^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$
  • InternetURL:[a-zA-z]+://[^\s]*^http(s)?://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$
  • 中国手机号码:^(1[3-9][0-9])\d{8}$
  • 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
  • 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)
  • 日期格式:^\d{4}-1?\d-[1-3]?\d$
  • 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
  • 腾讯QQ号:[1-9][0-9]{4,}(腾讯QQ号从10000开始)
  • 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
  • (<)?(\w+@\w+(?:\.\w+)+)(?(1)>|$) 将匹配 <user@host.com>user@host.com',但不会匹配 <user@host.com ,也不会匹配 user@host.com>
  • 匹配div块<div(([\s\S])*?)</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值