Python爬虫学习笔记 (5) [初级] 学习 re 正则解析库

更新日期: 2021.03.21

本节学习内容 :掌握解析库 re 的常用方法。

1. re 是什么?re 库能完成什么任务?

re 是 Regular Expression (正则表达式) 的缩写,是 Python 内置的标准库。

首先根据目标字符串 (英文,电邮地址,特别的符号等) 的规则设置匹配方法,然后使用 re 模块的功能从文档中获取目标字符串。简单地说,就是大海捞针,精准的获取~~

re 库还可以修改字符串或进行拆分。

2. 学习资料

以下两个官网链接相互指向,A介绍说 “这里只介绍最重要的内容,请参阅B获取完整内容”,B说 “以下只是简要说明,详细的信息和演示参考A”…这两个文档的结构和知识点覆盖面都有区别,两个混搭着看…

3. 基本匹配方法

除了元字符外, 字符(如字母和符号)可用来匹配他们自身。

3.1 转义元字符 \

在元字符前加一个反斜杠来体现(或移除)他们的特殊含义。

反斜杠本身也需要使用反斜杠转义。

3.2 使用原始字符串(避免 / 引发歧义) r

前缀为 ‘r’ 的字符串, 反斜杠不会将其转义。

例如, r"\n" 是一个包含 ‘\’ 和 ‘n’ 的字符串,而 “\n” 是一个包含换行符的单字符字符串。

如需匹配一个字符反斜杠,原始字符串记法使用 r"\\" (r 后面跟两个反斜杠)或者"\\\\"(四个反斜杠)来表示。

3.3 元字符

部分字母和符号前加一个反斜杠将拥有不同的含义。

  • . (点)匹配除换行符之外的任何内容。使用 (re.S) 后点也可以匹配换行符
  • \d 匹配十进制数字 [0-9]
  • \D 匹配非数字字符,等价于匹配 [^0-9]
  • \s 匹配空白字符,等价于类 [ \t\n\r\f\v]
  • \S 匹配非空白字符,相当于类 [^ \t\n\r\f\v]
  • \w 匹配字母与数字字符,相当于类 [a-zA-Z0-9_]
  • \W 匹配非字母与数字字符,相当于类 [^a-zA-Z0-9_]
  • ^ 匹配行开头, 除非设置了 MULTILINE 标志,否则只会在字符串的开头匹配
  • $ 匹配行结尾的元字符, 在 MULTILINE 模式下也可以匹配换行符之前的文本
  • \b确认字符边界, 在 Python 字符串中\b 是退格字符, 需使用原始字符串 r 表达
  • \B 仅在当前位置不在字边界时才匹配
p = re.compile(r'\bAlice\b')
print(p.search('Alice likes apple.')) # <re.Match object; span=(0, 5), match='Alice'>
print(p.search('Alice@')) # <re.Match object; span=(0, 5), match='Alice'>
print(p.search('Aliceia')) # None

3.4 指定需匹配的字符集合的元字符 [ ]

注意:字符集合中的元字符不生效。 例如, [(+*)] 会匹配左右括号, 加号, 反斜杠和星号。

[abc] 匹配 ‘a’ , ‘b’ 或 ‘c’ 中任何字符。

通过 ‘-’ 连接字符范围

  • 如 [a-z] 将匹配任何小写ASCII字符, [0-5][0-9] 将匹配从 00 到 59 的两位数字, [0-9A-Fa-f] 将匹配任何十六进制数位。
  • -如果使用反斜杠对 - 进行了转义 (比如 [a\-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-]),它就只表示普通字符 ‘-’。
  • 不在集合范围内的字符可以通过取反来进行匹配。加上 ‘^’ 作为前缀,所有不在集合内的字符将会被匹配,比如 [^5] 将匹配所有字符,除了 ‘5’;[^^] 将匹配所有字符,除了 ‘^’;^ 如果不在集合首位,就没有特殊含义。

3.5 限定重复匹配次数的元字符

  • * 匹配零至最多次 (贪婪后缀), 例如,ca*t 将匹配 ‘ct’ (0个),‘cat’ 和 ‘caaat’ 等。
  • +匹配一次或多次
  • ? 匹配一次或0次 (非贪婪后缀), 例如,home-?brew 匹配 ‘homebrew’ 或 ‘home-brew’。
  • {m} 匹配至少 m 次, 如果结果少于 m 个会匹配失败。例如,a{6} 不能匹配5个。
  • {m,n} 匹配至少 m 次最多 n 次 (在 m 和 n 之间取尽量多) , 例如,a/{1,2}b 将匹配 ‘a/b’ 和 ‘a///b’ 。
  • {m,n}? 匹配至少 m 次最多 n 次 (在 m 和 n 之间取尽量少) ?, 例如,对于 ‘aaaaaaaa’, a{3,5} 匹配 5个 ‘a’ ,而 a{3,5}? 只匹配3个 ‘a’。

使用 * 进行贪婪匹配的过程

程序将尝试尽可能多地重复,如后续部分不匹配,则回退并以较少的重复次数再尝试。使用正则表达式 a[bcd]*b (用于匹配字母 ‘a’,类 [bcd] 中的零或多个字母,最后以 ‘b’ 结尾) 匹配字符串 ‘abcbd’ 。

* 和 + 修饰符都是贪婪的,它们尽可能多的匹配。例如, <.*> 希望找到 ‘abc’,它会匹配整个字符串 abc。在修饰符之后添加 ? (<.*?> )将使样式以非贪婪方式进行匹配,即匹配尽量少的字符,结果是 a。
在这里插入图片描述

3.6 同时使用多种匹配方法 A|B

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

如果要匹配 ‘|’ 字符,使用 |, 或者把它包含在字符集里,比如 [|]。

3.7 选择性匹配

  • 扩展标记法 (?=…) 正向前向断言。 如果包含的正则表达式,由 … 表示,在当前位置成功匹配,则成功,否则失败。 但是,一旦尝试了包含的表达式,匹配的引擎就不会前进;模式其余的部分会在在断言开始的地方尝试. 例如, Isaac (?=Asimov) 用于匹配 'Isaac ’ ,只有当它后面是 ‘Asimov’ 的时候才会匹配。
  • 扩展标记法 (?!…) 负向前向断言。 如果包含的表达式在字符串中的当前位置不匹配,则成功。例如, Isaac (?!Asimov) 用于匹配 'Isaac ’ ,只有当它后面不是 ‘Asimov’ 的时候才会匹配。

例如,排除文件扩展名;只需在断言中添加它作为替代。 以下模块排除以 bat 或 exe。

r'.*[.](?!bat$|exe$)[^.]*$'
  • 扩展标记法 (?(id/name)yes-pattern|no-pattern)
    如果给定的 id 或 name 存在,将会尝试匹配 yes-pattern ,否则就尝试匹配 no-pattern,no-pattern 可选,也可以被忽略。

4. 使用正则表达式 - 直接解析/匹配 re.

可以使用形式为 re.match(pattern, string, flags=0) 的形式直接解析。第一个参数是用于解析的正则表达式,第二个参数是网页代码。

为了便于理解和编译,推荐先将正则表达式编译为对象,再进行调用。

5. 构造正则表达式对象 - 解析/匹配 pattern.

将正则表达式的编译为一个正则表达式对象,再通过这个对象调用方法如 match(), search() 等进行匹配。形式一般为 Pattern.match(string[, pos[, endpos]])。

5.1 正则匹配标记 Pattern.flags (re.S| re.I |re.M |re. X)

可以组合使用多种编译标志,如,re.I | re.M 。

  • ASCII, A 仅 ASCII 匹配, 使 \w、\W、\b、\B、\s 和 \S 执行仅 ASCII 匹配而不是完整匹配 Unicode 匹配。 这仅对 Unicode 模式有意义,并且对于字节模式将被忽略。
  • DOTALL, S 使 ‘.’ 匹配任何字符,包括换行符
  • IGNORECASE, I 执行不区分大小写的匹配
  • MULTILINE, M 多行匹配, 通常 ^ 只匹配字符串的开头, $ 只匹配字符串的结尾(紧接在字符串末尾的换行符(如果有的话)之前)。 当指定了M标志时,^ 匹配字符串的开头和字符串中每一行的开头,紧跟在每个换行符之后。 类似地,$ 元字符匹配字符串的结尾和每行的结尾。
  • re.DEBUG 显示编译时的debug信息
  • VERBOSE, X 扩展, 允许编写更易读的正则表达式。将忽略正则字符串中的空格,还允许将注释放在正则中。注释标记为 ‘#’ 不可以在字符集合或未转义的反斜杠前。
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)

5.2 匹配第一个目标 .search()

Pattern.search(string[, pos[, endpos]])

并返回一个 匹配对象。如果没有匹配,返回 None。

可选的第二个参数 pos 给出了字符串中开始搜索的位置索引;默认为 0。
‘^’ 样式字符匹配字符串真正的开头,和换行符后面的第一个字符,但不会匹配索引规定开始的位置。

import re

pattern = re.compile("d")
print(pattern.search("dog") )    # <re.Match object; span=(0, 1), match='d'>
print(pattern.search("dog", 1))  # None

5.2 仅匹配开头位置 .match()

Pattern.match(string[, pos[, endpos]])

p = re.compile('[a-z]+')
print(p.match("")) # None, 不匹配空字符串,因为 + 表示匹配一次或多次
print(p.match("alice")) # <re.Match object; span=(0, 5), match='alice'>

pattern = re.compile("o")
print(pattern.match("dog"))    # None
print(pattern.match("dog", 1))   # <re.Match object; span=(1, 2), match='o'>

5.3 完全匹配整个 string .fullmatch()

Pattern.fullmatch (string[, pos[, endpos]])

pattern = re.compile("o[gh]")
print(pattern.fullmatch("dog")) # None
print(pattern.fullmatch("ogre")) # None
print(pattern.fullmatch("doggie", 1, 3)) # <re.Match object; span=(1, 3), match='og'>

5.4 匹配所有目标,返回列表 .findall()

Pattern.findall(string[, pos[, endpos]])
返回一个不重复的 pattern 的匹配列表, 空匹配也会包含在结果里。

5.5 匹配所有目标,返回迭代器 finditer()

返回一个迭代器 iterator, 保存了所有的非重复匹配。

6. 分组返回目标信息 group

6.1 分组返回

Match.group 返回一个或者多个匹配的子组。group(0) 返回所有匹配组。

可使用元组编号返回特定编号的目标信息。

m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
print(m.group(0)) # Isaac Newton
print(m.groups()) # ('Isaac', 'Newton')

print(m.group(1)) # Isaac
print(m.group(2)) # Newton
print(m.group(1,2)) # ('Isaac', 'Newton')

m = re.match(r"(\d+)\.(\d+)", "24.1632")
print(m.groups()) # ('24', '1632')

# 如果一个组匹配成功多次,就只返回最后一个匹配。
m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
print(m.group(0)) # a1b2c3
print(m.group(1)) # c3

# 子组从左到右编号,从 1 向上编号。 组可以嵌套。
p = re.compile('(a(b)c)d')
m = p.match('abcd')
print(m.group(0)) # 'abcd'
print(m.group(1)) # 'abc'
print(m.group(2)) # 'b'

# 使用元组编号返回特定编号的目标信息。
print(m.group(2,1,2)) # ('b', 'abc', 'b')

start() 返回匹配的开始位置
end() 返回匹配的结束位置
span() 返回包含匹配 (start, end) 位置的元组

p = re.compile('[a-z]+')
print(p.match("alice")) # <re.Match object; span=(0, 5), match='alice'>

m = p.match("alice")
print(m.group()) # alice
print(m.start(),m.end()) # 0 5
print(m.span()) # (0, 5)
p = re.compile('[a-z]+')
# print(p.match("alice")) # <re.Match object; span=(0, 5), match='alice'>

6.2 搜索先前捕获的组的相同内容 \n

例如,如果可以在当前位置找到组 1 的确切内容,则 \1 将成功,否则将失败。

>>> p = re.compile(r'\b(\w+)\s+\1\b') # 此处'\1'代表(\w+)已经捕获的内容'the'
>>> p.search('Paris in the the spring').group()
'the the'

6.3 命名组 (?P<name>…)

可以通过名称引用组。命名组的语法是Python特定的扩展之一: (?P<name>…)。

此外,可以通过 groupdict() 将命名分组提取为一个字典。

p = re.compile(r'(?P<word>\b\w+\b)')
m = p.search( '(((( Lots of punctuation )))' )

print(m.group('word')) # 'Lots'
print(m.group(1)) # 'Lots'

m = re.match(r'(?P<first>\w+) (?P<last>\w+)', "Alice Y")
print(m.groupdict()) # {'first': 'Alice', 'last': 'Y'}

同样,可以使用名字在当前位置搜索先前捕获组的某个内容。

p = re.compile(r'\b(?P<word>\w+)\s+(?P=word)\b') 
# (?P=word)代表和前一个'?P<word>'捕获的内容相同的字符
print(p.search('Paris in the the spring').group()) # 'the the'

7. 分割返回的信息 .split (用 pattern 分割 string)

Pattern.split(string, maxsplit=0)

如果 maxsplit 非零, 最多进行 maxsplit 次分隔, 剩下的字符全部返回到列表的最后一个元素。

p = re.compile(r'\W+')

print(p.split('Words, words, words.'))
# ['Words', 'words', 'words', '']
print(p.split('Words, words, words.', 1))
# ['Words', 'words, words.'],返回两个值

p = re.compile('[a-fA-F]+')
print(p.split('0a3B9')) # ['0', '3', '9']

样式的空匹配仅在与前一个空匹配不相邻时才会拆分字符串。

print(re.split(r'\b', 'Words, words, words.')) 
# ['', 'Words', ', ', 'words', ', ', 'words', '.']
print(re.split(r'\W*', '...words...')) 
# ['', '', 'w', 'o', 'r', 'd', 's', '', '']
print(re.split(r'(\W*)', '...words...')) 
# ['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']

8. 其他 - 暂不学习

  • 查找和替换 sub() & subn()
  • 转义 pattern 中的特殊字符 re.escape (pattern)
  • 清除正则表达式的缓存 re.purge()
  • raise 一个例外 exception re.error

9. 总结

预计昨晚看完的…官网文档介绍的顺序和我的预期差别很大,所以时间几乎double。

Anyway,终于看完啦~~

  • 了解了基本的匹配方法,包括怎样使用各种元字符获取目标,限定匹配次数等。
  • 学习了使用正则表达式作为对象进行解析,并进行分组或分割,以精确地返回目标。
  • bs4 解析用于获取标签内的内容,而对于内容的进一步分割和精确获取,就需要使用 re

re 库一些复杂的用法暂时不关注了,等需要的时候再学。

现在,学完了下载网页的 requests, 解析库 bs4 和精确获取目标的 re,打算找个网站练练手啦~~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值