💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
![](https://i-blog.csdnimg.cn/direct/ff169577db1c4a128c6c5ae56d3c6949.gif)
推荐:Linux运维老纪的首页,持续学习,不断总结,共同进步,活到老学到老
导航剑指大厂系列:全面总结 运维核心技术:系统基础、数据库、网路技术、系统安全、自动化运维、容器技术、监控工具、脚本编程、云服务等。
常用运维工具系列:常用的运维开发工具, zabbix、nagios、docker、k8s、puppet、ansible等
数据库系列:详细总结了常用数据库 mysql、Redis、MongoDB、oracle 技术点,以及工作中遇到的 mysql 问题等
懒人运维系列:总结好用的命令,解放双手不香吗?能用一个命令完成绝不用两个操作
数据结构与算法系列:总结数据结构和算法,不同类型针对性训练,提升编程思维,剑指大厂
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。💝💝💝 ✨✨ 欢迎订阅本专栏 ✨✨
Python之 正则表达式
技能目标
- 掌握 Python 正则表达式 re 模块
字符串是编写程序时用到最多的一种数据类型,对字符串进行处理也是经常需要做的工
作。比如判断字符串是否是合法的 Email 地址、电话号码、身份证号等。虽然可以使用字符
串的处理方法进行判断,但验证代码编写起来非常麻烦,也不利于代码的复用。正则表达式
是一种用来匹配字符串的非常方便的工具,它的设计思想是用一种描述性的语言来给字符串
定义一个规则。凡是符合规则的字符串,就认为它“匹配”了;否则,该字符串就是不合法的。
6.1 re 模块
正则表达式并不是 Python 的一部分,在大部分编程语言中都有对其调用的方法,以协
助程序员快速的完成字符串的匹配功能。它是用于处理字符串的强大工具,拥有自己独特的
语法以及一个独立的处理引擎。在提供了正则表达式的编程语言里,正则表达式的语法都是
一样的,只是不同的编程语言支持的语法数量不同,但是不被支持的语法通常是不常用的部
分。如果能够在 Python 中熟悉掌握正则表达式,那么在学习其它编程语言时,也可以正确
的使用正则表达式。
图 6.1 展示了使用正则表达式进行字符串匹配的流程。
图 6.1 正则表达式匹配流程
从图 6.1 中可以看到使用正则表达式完成匹配的步骤。
(1)依次拿出表达式和文本中的字符比较。
(2)如果每一个字符都能匹配,则匹配成功。
(3)如果有匹配不成功的字符,则匹配失败。
6.1.1 元字符
正则表达式是使用字符串表示的,需要了解如何用元字符来描述字符串。在正则表达式
中,如果直接给出字符,就是精确匹配。如表 6-1 列出了 Python 支持的正则表达式元字符,
表 6-2 是 Python 正则表达式的分组语法。
表 6-1 元字符
字符 | |
元字符 | 描述 |
. |
匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,使用像“[\s\S]”的模
式。
|
|
将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转
义符。例如,“\\n”匹配\n,“\n”匹配换行符,序列“\\”匹配“\”而“\(”则匹配“(”,即相当于多种
编程语言中都有的“转义字符”的概念。
|
[xyz]
|
字符集合。匹配所包含的任意一个字符,例如,“[abc]”可以匹配“plain”中的“a”。
|
^xyz]
|
负值字符集合。匹配未包含的任意字符,例如,“[^abc]”可以匹配“plain”中的“plin”。
|
[a-z]
|
字符范围。匹配指定范围内的任意字符,例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写
字母字符。
注意:只有连字符在字符组内部且出现在两个字符之间时,才能表示字符的范围。
|
[^a-z]
|
负值字符范围。匹配任何不在指定范围内的任意字符,例如,“[^a-z]”可以匹配任何不在“a”
到“z”范围内的任意字符。
|
预定义字符集(可以写在字符集[…]中)
| |
元字符 | 描述 |
\d
|
匹配一个数字字符。等价于[0-9],例如”a\dc”可以匹配”a2c”。
|
\D
|
匹配一个非数字字符。等价于[^0-9],例如”a\dc”可以匹配”abc”。
|
\s
|
匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
|
\S
|
匹配任何可见字符。等价于[^ \f\n\r\t\v]。
|
\w
|
匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用
Unicode 字符集。
|
\W
|
匹配任何非单词字符。等价于“[^A-Za-z0-9_]”
|
数量词(用在字符或(…)之后) | |
* | 匹配前面的子表达式任意次。例如:zo*能匹配“z”,也能匹配“zo”以及“zoo”。 |
+ |
匹配前面的子表达式一次或多次(大于等于 1 次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
|
?
|
匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。
|
{n}
|
n 是一个非负整数,匹配确定的 n 次。例如,
“
o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”
中的两个 o。
|
{n,}
|
n 是一个非负整数,至少匹配 n 次。例如,
“
o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”
中的所有 o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
|
{n,m}
|
m 和 n 均为非负整数,其中 n<=m,最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}”将匹
配“fooooood”中的前三个 o。“o{0,1}”等价于“o?”。
注意:在逗号和两个数之间不能有空格。
|
边界匹配(不消耗待匹配字符串中的字符) |
|
^ |
匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,
^
也匹配“\n”或“\r”
之后的位置。
|
$ |
匹配输入字符串的结束位置。如果设置了 RegExp 对象的 Multiline 属性,$也匹配“\n”或“\r”
之前的位置。
|
\b |
匹配一个单词边界,是指单词和空格间的位置。即正则表达式的“匹配”有两种概念,一种
是匹配字符,一种是匹配位置,这里的\b 就是匹配位置的。例如:“er\b”可以匹配“never”
中的“er”,但不能匹配“verb”中的“er”。
|
\B | 匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。 |
\A | 仅匹配字符串开头。例如,”\Aabc”可以匹配”abcdef”。 |
\Z | 仅匹配字符串末尾。例如,”abc\Z”可以匹配”defabc” |
表 6-2 分组语法
逻辑分组
| |
(… ) |
将( 和 ) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存
到一个临时区域(一个正则表达式中最多可以保存 9 个),它们可以用 \1 到\9 的
符号来引用。
|
(?P<name>...) |
分组,除了原有的编号外再指定一个额外的别名。例如,”(?P<id>abc){2}”可以匹
配”abcabc”。
|
|
引用编号为<number>的分组匹配到的字符串。例如,”(\d)abc\1”可以匹配“1abc1”。
|
(?P=name)
|
引用别名为<name>的分组匹配到的字符串。例如,”(?P<id>\d)abc(?P=id)”,可以
匹配“1abc1”。
|
l |
将两个匹配条件进行逻辑“或
”
(Or)运算。例如正则表达式(him|her) 匹配"it belongs
to him"和"it belongs to her",但是不能匹配"it belongs to them."。
注意:这个元字符不是所有的软件都支持的。
|
特殊构造(不作为分组)
特殊构造(不作为分组) | |
(?:pattern) |
非获取匹配。匹配 pattern 但不获取匹配结果,不进行存储供以后使用。在使用或
字符“(|)”来组合一个模式的各个部分时很有用。例如,“Word(?:2010|2013)”比
"Word2010|Word2013"表述简略。
|
(?=pattern) |
非获取匹配,正向肯定预查,在任何匹配 pattern 的字符串开始处匹配查找字符串,
该 匹 配 不 需 要 获 取 供 以 后 使 用 。 例 如 , “Word(?=2007|2010|2013)” 能 匹 配
“Word2010”中的“Word”,但不能匹配“Word2003”中的“Word”。预查不消耗字符,
即:在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是
从包含预查的字符之后开始。
|
(?!pattern) |
非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,
“(?<=2007|2010|2013)Word” 能 匹 配 “2010Word” 中 的 “Word” , 但 不 能 匹 配
“2003Word”中的“Word”。
|
(?<!pattern) |
非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,
“(?<=2007|2010|2013)Word” 能 匹 配 “2010Word” 中 的 “Word” , 但 不 能 匹 配
“2003Word”中的“Word”。
|
(?<!pattern) |
非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如
“(?<!2007|2010|2013)Word” 能 匹 配 “2003Word” 中 的 “Word” , 但 不 能 匹 配
“2010Word”中的“Word”。
|
下面通过示例来解读一个比较复杂的正则表达式,示例代码如下所示。
\d{3}\s+\d{5,8}
对上述代码,从左到右解读如下:
\d{3}表示匹配 3 个数字,例如'010';
\s 可以匹配一个空格(也包括 Tab 等空白符),所以\s+表示至少有一个空格,例如匹
配' ',' '等;
\d{5,8}表示 5-8 个数字,例如'1234567'。
由此可以看出,上面例子中给出的正则表达式可以匹配以任意个空格隔开的、带区号的
电话号码。
6.1.2 匹配字符进阶
要做更精确地匹配,可以用“[]”表示取值范围,例如:
[0-9a-zA-Z\_]:可以匹配一个数字、字母或者下划线。
[0-9a-zA-Z\_]+:可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'c123',
'5_b','Python'等等。
[a-zA-Z\_][0-9a-zA-Z\_]*:可以匹配由字母或下划线开头,后接任意个由一个数字、字
母或者下划线组成的字符串,也就是 Python 合法的变量名。
[a-zA-Z\_][0-9a-zA-Z\_]{0, 9}:更精确地限制了变量的长度是 1-10 个字符(前面 1 个
字符+后面最多 9 个字符)。
另外,还有|、^、$字符也有其特定的匹配对象。例如:
| 表示逻辑或的关系,如:A|D 可以匹配 A 或 D; [P|p]ython 可以匹配'Python'或者
'python'。
^ 表示行的开头,^\d 表示必须以数字开头。
$ 表示行的结束,\d$表示必须以数字结束。
py 也可以匹配'python',但是加上^py$就变成了整行匹配,就只能匹配'py'了。
6.2 Python 正则表达式示例
正则表达式是一个特殊的字符序列,它能帮助程序员方便的检查一个字符串是否与某种
模式匹配。Python 自 1.5 版本起增加了 re 模块,使 Python 语言拥有全部的正则表达式
功能。
6.2.1 反斜杠
与大多数编程语言相同,正则表达式里使用"\"作为转义字符,这就可能造成反斜杠困扰,
例如字符串'Python\-001'需要用'Python\\-001'表示。
使用 Python 的 r 前缀,就可以不用考虑转义的问题了,它标识了字符串是一个原生的
字符串,例如:以上字符串可以表示为 r'Python\-001'。
同样,匹配一个数字的"\\d"可以写成 r"\d"。有了原生字符串,再也不用担心是不是漏写
了反斜杠,写出来的表达式也更直观。
下面介绍 Python 中常用的正则表达式处理方法。
6.2.2 正则表达式修饰符 - 可选标志
正则表达式可以包含一些可选标志修饰符来控制匹配的模式,修饰符被指定为一个可选
的标志。多个标志可以通过按位 OR(|)指定,如 re.I | re.M 被设置成 I 和 M 标志。常
用修饰符如表 6-3 所示。
表 6-3 修饰符
修饰符 | 描述 |
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响 ^ 和 $ |
re.S | 使 . 匹配包括换行符在内的所有字符 |
re.U | 根据 Unicode 字符集解析字符,这个标志影响\w、\W、\b、\B |
re.X | 该标志通过给予更灵活的格式以便将正则表达式写得更易于理解 |
6.2.3 re.match()方法
re.match()方法尝试从字符串的开始匹配一个模式。
语法:
re.match(pattern, string[, flags])
参数说明如表 6-4 所示。
表 6-4 match()参数说明
参数 | 描述 |
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
flags |
可选参数,标志位,用于控制正则表达式的匹配方式。如:是否区分大
小写,多行匹配等等
|
如果匹配成功,re.match()方法返回一个匹配的 match 对象;否则返回 None。
示例 1:使用 re.match()方法匹配字符串
示例代码如下所示。
import re
s = "Hello world!"
print (re.match("world",s))
print (re.match("hello",s))
print (re.match("Hello",s))
print (re.match("hello",s,re.I))
执行结果如下:
None
None
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
<_sre.SRE_Match object; span=(0, 5), match='Hello'>
6.2.4 group() 或 groups()方法
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用 group()表示
的就是要提取的分组(Group)。使用 group(num) 或 groups() 匹配对象方法可以获取匹
配表达式,如表 6-5 所示。
表 6-5 group()方法说明
匹配对象方法 | 描述 |
group(num=0) |
匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这
种情况下它将返回一个包含那些组所对应值的元组
|
groups() | 返回一个包含所有小组字符串的元组,从 1 到 所含的小组号 |
注意:group(0)永远是原始字符串,group(1)、group(2)……表示第 1、2、……个子串。
示例 2:使用 re.match()方法和 group()方法获取匹配对象
示例代码如下所示。
#!/usr/bin/python
import re
line ="Cats are smarter than Pigs"
matchObj = re.match( r'(.*) are (.*) .*', line, re.M|re.I)
if matchObj:
print ("matchObj.group() : ", matchObj.group())
print ("matchObj.group(1) : ", matchObj.group(1))
print ("matchObj.group(2) : ", matchObj.group(2))
else:
print ("No match!!")
执行结果如下:
matchObj.group() : Cats are smarter than Pigs
matchObj.group(1) : Cats
matchObj.group(2) : smarter than
6.2.5 re.search()方法
re.search()方法尝试从字符串的任意位置匹配一个模式。
语法:
re.search(pattern, string, flags=0)
参数说明如表 6-6 所示。
表 6-6 rearch()参数说明
参数 | 描述 |
pattern | 匹配的正则表达式 |
string | 要匹配的字符串 |
flags |
标志位,用于控制正则表达式的匹配方式,如:是否
区分大小写、多行匹配等等
|
如果匹配成功,re.search 方法返回一个匹配的对象;否则返回 None。
示例 3:使用 re.search ()方法和 group()方法获取匹配对象
示例代码如下所示。
#!/usr/bin/python
import re
line = "Cats are smarter than Pigs";
matchObj = re.match(r'dogs', line, re.M|re.I)
if matchObj:
print ("match --> matchObj.group() : ", matchObj.group())
else:
print ("No match!!")
matchObj = re.search(r'Pigs', line, re.M|re.I)
if matchObj:
print ("search --> matchObj.group() : ", matchObj.group())
else:
print ("No match!!")
执行结果如下:
No match!!
search --> matchObj.group() : Pigs
从执行结果可以发现 re.match()与 re.search()的区别:
r
e.match()只匹配字符串的开始。
如果字符串开始不符合正则表达式,则匹配失败,方法返回 None;而 re.search()匹配整个
字符串,直到找到一个匹配。
6.2.6 re.sub()方法
Python 的 re 模块提供了 re.sub()方法用于替换字符串中的匹配项。
语法:
re.sub(pattern, repl, string, count =0)
re.sub()方法返回的字符串是在字符串中用 RE 最左边不重复的匹配来替换。如果模式
没有发现,字符将被没有改变地返回。
可选参数 count 是模式匹配后替换的最大次数;count 必须是非负整数。缺省值是 0
表示替换所有的匹配。
示例 4:使用 sub()方法删掉“2020-07-06”字符串中的字符“-”
示例代码如下所示。
#!/usr/bin/python
import redate = "2020-07-06 "
print ("Date: ", date)
num = re.sub(r'-', "", date)
print ("Date num: ", num)
执行结果如下:
Date : 2020-07-06
Date Num : 20200706
6.2.7 re.split()方法
re 模块中 re.split()方法类似字符串内置函数 split(),二者的区别在于:内置函数 split()
以参数确定字符串分割,而正则 split 函数以正则表达式分割字符串。
语法:
re.split(pattern, string[, maxsplit=0])
示例 5:使用 split()方法匹配并分割字符串
示例代码如下所示。
#!/usr/bin/python
import re
print (re.split('\W+', 'Words, words, words.'))
print (re.split('(\W+)', 'Words, words, words.'))
print (re.split('\W+', 'Words, words, words.', 1))
print (re.split('(\W+)', '...words, words...'))
print (re.split('x*', 'foo'))
#零长度
执行结果如下所示:
['Words', 'words', 'words', '']
['Words', ', ', 'words', ', ', 'words', '.', '']
['Words', 'words, words.']
['', '...', 'words', ', ', 'words', '...', '']
/usr/local/python3/lib/python3.6/re.py:212: FutureWarning: split() requires a non-empty pattern
match.
return _compile(pattern, flags).split(string, maxsplit)
['foo']
示例 5 的第 4~6 行代码用匹配 pattern 的子串来分割字符串。如果 pattern 里使用了圆
括号,那么被 pattern 匹配到的串也将作为返回值列表的一部分。如果 maxsplit 不为 0,则
最多被分割为 maxsplit 个子串,剩余部分将被视为一个整体返回。
如果 pattern 中有圆括号且可以匹配到字符串的开始位置时,返回值的第一项,会多出
一个空字符串。
注意:split()方法不会被零长度的 pattern 所分割,在 Python3 版本中会有报错信息提
示。
示例 6:识别字符串中连续的空格,并执行分割。
示例代码如下所示。
#!/usr/bin/python
import re
print (re.split(r'\s+', 'a b
c'))
#字符串中有多个空格
print (re.split(r'[\s\,]+', 'a,b, c d'))
#字符串中有多个,和空格
print (re.split(r'[\s\,\;]+', 'a,b;; c d')) #字符串中含有多个;和空格
执行结果如下:
['a', 'b', 'c']
['a', 'b', 'c', 'd']
['a', 'b', 'c', 'd']
6.2.8 re.findall()方法
Python 程序中,调用 findall()方法可以获得正则表达式在字符串中所有匹配结果的列
表。
语法:
re.findall(pattern, string[, flags])
以列表的形式返回 string 里匹配 pattern 的不重叠的子串。string 会被从左到右依次扫
描,返回的列表也是从左到右一次匹配到的。如果 pattern 里含有组的话,那么会返回匹配
到的组的列表;如果 pattern 里有多个组,那么各组会先组成一个元组,然后返回值将是一
个元组的列表。
findall()方法的返回列表中每个元素包含的信息如下。
(1) 当给出的正则表达式中带有多个括号时,列表的元素为多个字符串组成的 tuple,tuple
中字符串个数与括号对数相同,字符串内容与每个括号内的正则表达式相对应,并且排
放顺序是按括号出现的顺序。
(2) 当给出的正则表达式中带有一个括号时,列表的元素为字符串,此字符串的内容与括号
中的正则表达式相对应(不是整个正则表达式的匹配内容)。
(3) 当给出的正则表达式中不带括号时,列表的元素为字符串,此字符串为整个正则表达式
匹配的内容。
下面通过示例 7 介绍使用正则表达式获得在字符串中所有匹配结果的列表。
示例 7:使用正则表达式查找提取与指定字符串相匹配的数据
示例代码如下所示。
#!/usr/bin/python
import re
str1 = re.findall('\w+', 'hello, Python!')
print (str1)
str2 = re.findall('(\d+)\.(\d+)\.(\d+)\.(\d+)', 'My IP is 192.168.1.1, and your is 192.168.1.6.')
print (str2)
执行结果如下:
['hello', 'Python']
[('192', '168', '1', '1'), ('192', '168', '1', '6')]
示例 8:从给定的小时:分:秒中提取小时、分、秒
示例代码如下所示。
#!/usr/bin/python
import re
t = '13:25:10'
m = re.match(
r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-
9]|4[0-9]|5[0-9]|[0-9])$', t)
print (m.groups())
执行结果如下:
('13', '25', '10')
示例 8 中,通过正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也
无法做到完全验证,比如识别日期,示例代码如下所示。
'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'
对于'2-30','4-31'这样的非法日期,用正则表达式还是识别不了,或者说写出来非常困
难,这时就需要程序配合识别了。
6.2.9 数量词的贪婪模式与非贪婪模式
正则表达式通常用于在文本中查找匹配的字符串。Python 里数量词默认是贪婪的(在
少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符。非贪婪的则相反,总是
尝试匹配尽可能少的字符。例如:正则表达式"ab*"如果用于查找"abbbc",将找到"abbb"。
而如果使用非贪婪的数量词"ab*?",将找到"a"。
示例 9:匹配出数字后面的 9
示例代码如下所示。
#!/usr/bin/python
import re
t1 = re.match(r'^(\d+)(9*)$', '102399').groups()
t2 = re.match(r'^(\d+?)(9*)$', '102399').groups()
print (t1)
print (t2)
执行结果如下:
('102399', '')
('1023', '99')
示例 9 的代码中,由于\d+采用贪婪匹配,示例第 4 行代码直接把后面的 9 全部匹配了,
结果 9*只能匹配空字符串了。
如果想获得数字1023后的的全部9,必须让\d+采用非贪婪匹配(也就是尽可能少匹配),
才能把后面的 9 匹配出来。示例第 5 行代码在 Pattern 中加个?就可以让\d+采用非贪婪匹配。
6.2.10 re.compile()方法
在 Python 中使用正则表达式时,re 模块内部会做两件事情。
(1)编译正则表达式,如果正则表达式的字符串本身不合法,会报错。
(2)用编译后的正则表达式去匹配字符串。
如果一个正则表达式要重复使用几千次,出于效率的考虑,可以预编译该正则表达式。
接下来重复使用时就不需要编译这个步骤了,直接匹配。
re 模块中,compile()方法根据一个模式字符串和可选的标志参数生成一个正则表达式
对象。该对象拥有一系列方法用于正则表达式匹配和替换。
语法:
re.compile(pattern[, flags])
参数说明如表 6-7 所示。
表 6-7 compile()参数说明
参数 | 描述 |
pattern | 匹配的正则表达式 |
flags |
标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,
多行匹配等等。
|
示例 10:使用 re.compile 方法编译成一个正则表达式对象
示例代码如下所示。
#!/usr/bin/python
import re
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
phone1 = re_telephone.match('010-123321').groups()
print (phone1)
phone2 = re_telephone.match('010-62248234').groups()
print (phone2)
执行结果如下:
('010', '123321')
('010', '62248234'')
编译后生成 Regular Expression 对象,由于该对象自己包含了正则表达式,所以调用
对应的方法时不用给出正则字符串。
6.3 Python 脚本编写网络爬虫案例
一般获取数据的过程都是按照发送请求->获得页面反馈->解析并且存储数据这三个主
要步骤来实现的。这个过程其实就是模拟了一个人工浏览网页的过程。在 Python 中爬虫相
关的包很多:urllib、requests、bs4、scrapy、pyspider 等等,而正则表达式在编写爬虫脚
本时会起到非常重要的作用。
6.3.1 爬取网页并存为文件
编写一个爬取网页保存到本地的 Python 脚本,实现以下功能:
爬取的网页为 http://www.kgc.cn
保存的文件名为 kgc.html
实现思路如下:
导入 sys 模块,用 sys.argv 方法获取 get_web 函数实参,让用户在命令行上提供网站
和网页参数,最后调用 get_web 函数。
1.在 VI 编辑器中编写 get_web.py 脚本文件,文件代码如下所示:
import sys
from urllib.request import urlopen
def get_web(url, fname):
html = urlopen(url)
with open(fname, 'wb') as fobj:
while True:
data = html.read(4096)
if not data:
break
fobj.write(data)
html.close()
if __name__ == '__main__':
get_web(sys.argv[1], sys.argv[2])
#让用户在命令行上提供网址和下载数据的保存位置
2.执行测试 Python 脚本并通过 cat 命令查看 kgc.html 文件中爬取到的内容
[root@localhost /]# python3 get_web.py http://www.kgc.cn /tmp/kgc.html
[root@localhost /]# cat /tmp/kgc.html
6.3.2 爬取网页图片
上面的脚本实现的是爬取网页内容,那么如何爬取网页当中的图片文件呢?在 HTMLl
页面中,图片使用<img/>标签,在这个标签里有一个属性 src,而 src 里的链接就是图片的
保存路径,那么其实每个图片也相当于一个 url,此时可以使用 urllib 结合正则表达式来获取
图片数据。
实现思路如下:
获取整个网页的内容,并保存到一个文件当中。然后使用正则表达式从文件内容中挑选
出图片的路径。再使用 urllib 请求图片内容,并下载到一个文件夹中。最后查看保存下来的
图片数据。
1.在 VI 编辑器中编写 download.py 脚本文件,文件代码如下所示:
import sys
import re
import os
from urllib.request import urlopen
def get_url(fname):
#获取图片链接(url)
cpatt = re.compile("(http|https)?:?/+[\w./-]+\.(jpg|png|jpeg|gif)") #正则匹配出图片链接
cpatt2 = re.compile("[\w]+[\w./-]+\.(jpg|png|jpeg|gif)")
#正则用于过滤链接
result = []
#保存链接集
with open(fname) as fobj:
#获取文件内容
for line in fobj:
#循环一行一行读
m = cpatt.search(line)
#第一次匹配
if m:
#如果匹配到进一步处理
m2 = cpatt2.search(m.group())
#第二次匹配,用于过滤链接
https = "https://"+""+m2.group()
#无法判断是 http 还是 https,所以全部保存到集合
http = "http://"+""+m2.group()
result.append(http)
#添加到集合
result.append(https)
return result
def get_web(url,fname):
#下载网页和图片信息
try:
html=urlopen(url)
#下载图片
with open(fname,'wb') as f:
#保存到文件
while True:
data = html.read(4096)
if not data:
break
f.write(data)
html.close()
except:
return
if __name__=="__main__":
get_web('https://www.kgc.cn/', '/mnt/kgc.html')
#下载这个网页的内容,保存到文件
urls = get_url('/mnt/kgc.html')
#获取图片的链接保存到列表
img_dir = '/mnt/images'
#存放图片的目录
if not os.path.exists(img_dir):
#没有该目录就创建
os.mkdir(img_dir)
for url in urls:
fname = os.path.join(img_dir, url.split('/')[-1])
#保存方式目录/图片链接
get_web(url, fname)
#下载图片
2.执行 Python 脚本,成功获取图片后到指定目录查看爬取到的图片。
[root@localhost ~]# python3 download.py
[root@localhost ~]# ls -l /mnt/images/
#查看爬取到的图片
总用量 1664
-rw-r--r--. 1 root root 255759 7 月
7 13:28 1563520059636068.png
-rw-r--r--. 1 root root 258013 7 月
7 13:28 1583226124387214.jpg
-rw-r--r--. 1 root root 389238 7 月
7 13:28 1583226216768520.jpg
-rw-r--r--. 1 root root 348467 7 月
7 13:28 1585898917359113.jpg
-rw-r--r--. 1 root root 95224 7 月
7 13:27 1587119359532888.jpg
-rw-r--r--. 1 root root 259632 7 月
7 13:27 1589012662447665.jpg页
-rw-r--r--. 1 root root
3417 7 月
7 13:21 20160122143014.png
-rw-r--r--. 1 root root
3589 7 月
7 13:21 20170202055332.png
-rw-r--r--. 1 root root
3678 7 月
7 13:21 20170825182409.png
-rw-r--r--. 1 root root
3337 7 月
7 13:21 20180629143127.png
-rw-r--r--. 1 root root
3562 7 月
7 13:21 20180910225243.png
-rw-r--r--. 1 root root
2880 7 月
7 13:21 20181226094407.png
-rw-r--r--. 1 root root
3560 7 月
7 13:21 20190409083420.png
-rw-r--r--. 1 root root
3546 7 月
7 13:21 20190702130839.png
-rw-r--r--. 1 root root
1501 7 月
7 13:21 20190709065359.png
-rw-r--r--. 1 root root
2725 7 月
7 13:21 20191014151338.png
-rw-r--r--. 1 root root
3394 7 月
7 13:21 20191024145142.png
-rw-r--r--. 1 root root
3020 7 月
7 13:21 20191207205350.png
-rw-r--r--. 1 root root
3017 7 月
7 13:21 20200106125509.png
-rw-r--r--. 1 root root
3168 7 月
7 13:21 20200307132541.png
-rw-r--r--. 1 root root
3634 7 月
7 13:21 20200509094357.png
-rw-r--r--. 1 root root
3788 7 月
7 13:21 20200618144706.png
-rw-r--r--. 1 root root
3964 7 月
7 13:21 20200623184258.png
-rw-r--r--. 1 root root
1859 7 月
7 13:21 20200703102717.png
-rw-r--r--. 1 root root
2526 7 月
7 13:21 cnnic.png
-rw-r--r--. 1 root root
297 7 月
7 13:21 icon_rss.gif