本文是网络爬虫的基础文章,介绍了正则表达式的用法,如果你已经掌握就不用往下看了。如果你想看看迪迦、赛文、雷欧奥特曼的故事,可以继续往下看。
想起第一次接触爬虫还是爬取空间说说输出词云,当时刚学Python不久,一句卧槽行天下,看着输出的词云,心里想这就是传说中的人工智障智能吧…后来从光头宝藏博主阿德里安的博客里看到了从谷歌图片自动抓取数据集,抓了抓梅西,于是又带着卧槽上路了…为了以后更好的收集数据分析数据,今天开始准备系统地学一学Python网络爬虫,于是有了本文。
文章目录
1.网络爬虫简介
网络爬虫是一种互联网信息自动化采集程序,主要作用是代替人工对互联网中的数据进行自动采集与整理,以快速地、批量地获取目标数据。网络爬虫可以做如下事情:
- 批量采集某个领域的招聘数据,对某个行业的招聘情况进行分析;
- 批量采集某个行业的电商数据,以分析出具体热销商品,进行商业决策;
- 采集目标客户数据,以进行后续营销;
- 批量爬取腾讯动漫的漫画,以实现脱网本地集中浏览;
- 开发一款火车票抢票程序以实现自动抢票;
…
2. 正则表达式
将网页上的信息爬下来之后,还有一个关键的步骤就是需要对目标信息进行提取,而表达式一般就是用于信息筛选提取的工具。正则表达式是一种功能强大好用的表达式。模式字符串组成了正则表达式。
模式字符串使用特殊的语法来表示一个正则表达式:
- 字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。
- 多数字母和数字前加一个反斜杠时会拥有不同的含义。
- 标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。
- 反斜杠本身需要使用反斜杠转义。
- 模式元素(如
r'/t'
,等价于'//t'
)匹配相应的特殊字符。 - 原子是正则表达式的最基本的组成单位,而且在每个模式中最少包含一个原子。
2.1 Python正则表达式
re
模块使 Python 语言拥有全部的正则表达式功能。
compile
函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。
全局匹配函数使用格式:
源字符串 = ''
正则表达式 = '\原子...'
re.compile(正则表达式).findall(源字符串)
2.2 普通字符作为原子
下表列出了正则表达式中的普通字符及描述。
普通字符(模式) | 描述 |
---|---|
\n |
匹配一个换行符 |
\t |
匹配一个制表符 |
\w |
匹配字母数字及下划线 |
\W |
匹配非字母数字及下划线 |
\s |
匹配任意空白字符,等价于 [\t\n\r\f] |
\S |
匹配任意非空字符 |
\d |
匹配任意数字,等价于 [0-9] |
\D |
匹配任意非数字 |
\A |
匹配字符串开始 |
\Z |
匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串 |
\z |
匹配字符串结束 |
\G |
匹配最后匹配完成的位置 |
\b |
匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’ |
\B |
匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’ |
\1...\9 |
匹配第n个分组的子表达式 |
\10 |
匹配第n个分组的子表达式,如果它经匹配。否则指的是八进制字符码的表达式 |
–分割线– | 以上称为普通字符称为原子 |
[m78nebula] |
原子表,匹配 奥特曼老家M78星云(m78nebula) 中的任意一个 |
[^m78nebula] |
原子表,匹配除 奥特曼老家M78星云(m78nebula) 以外的任意一个字符 |
注意:模式中大小写字母所表示的作用其实是【互补】的关系。
实例1:
import re
string = "csdnblog"
pat = "csdn"
re.compile(pat).findall(string)
'''
输出:['csdn']
'''
实例2:
import re
string_2 = "Ultraman_M78_HappyHometown"
pat_2 = "\w\d\d" #正则表达式,该语句的目的要从目标字符串中找到一个由“字母(或下划线)+数字+数字”的组合
re_obj = re.compile(pat_2).findall(string_2)
print("您搜索到的奥特曼的快乐老家为:%s星云·光之国" % re_obj)
'''
输出:您搜索到的奥特曼的快乐老家为:['M78']星云·光之国
'''
实例3:
import re
string_3 = "Ultraman_Earth_M78_L77_U40"
pat_3 = "[L]\d\d"
pat_4 = "\w\w\w[th][th]"
re_obj_3 = re.compile(pat_3).findall(string_3)
re_obj_4 = re.compile(pat_4).findall(string_3)
print("您搜索到的雷欧奥特曼的快乐老家为:", re_obj_3)
print("您搜索到的迪迦奥特曼的快乐老家为:", re_obj_4)
'''
您搜索到的雷欧奥特曼的快乐老家为: ['L77']
您搜索到的迪迦奥特曼的快乐老家为: ['Earth']
'''
2.3 特殊字符作为原子
特殊字符(模式) | 描述 |
---|---|
^ |
匹配字符串的开头 |
$ |
匹配字符串的末尾 |
. |
匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符 |
[...] |
用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’ |
[^...] |
不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符 |
* |
匹配 0 个或多个的表达式(前一个字符出现 0/1/多次) |
+ |
匹配 1 个或多个的表达式(前一个字符出现 1/多次) |
? |
匹配 0 个或 1 个由前面的正则表达式定义的片段,非贪婪方式(前一个字符出现0/1次) |
{n} |
匹配前一个字符恰好出现 n 次 |
{n,} |
精确匹配 n 个前面表达式(前一个字符至少出现 n 次) |
{n,m} |
匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式(前一个字符出现至少 n,至多 m 次) |
a|b |
| 模式选择符 或,匹配a或b |
() |
模式单元匹配括号内的表达式,也表示一个组 |
(?imx) |
正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域 |
(?-imx) |
正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域 |
(?:re) |
类似 (…), 但是不表示一个组 |
(?imx:re) |
在括号中使用i, m, 或 x 可选标志 |
(?-imx:re) |
在括号中不使用i, m, 或 x 可选标志 |
(?#...) |
注释 |
(?=re) |
前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边 |
(?!re) |
前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功 |
(?>re) |
匹配的独立模式,省去回溯 |
实例1:
import re
string = "csdnblognet"
pat = "csdn...."
re.compile(pat).findall(string)
'''
输出:['csdnblog']
'''
实例2:
import re
string = "csdnblognet"
pat = "^bl.."
re.compile(pat).findall(string)
'''
输出:[] # 说明为None,没有匹配到任何字符,因为bl不是开始位置
'''
实例3:
import re
string = "csdnblognet"
pat = "cs.*"
re.compile(pat).findall(string)
'''
输出:['csdnblognet'] #全部匹配,默认贪婪模式
'''
实例4:
import re
string = "csdnnnbbblognet"
pat = "csdn+b+"
re.compile(pat).findall(string)
'''
输出:['csdnnnbbb']
'''
2.4 贪婪 & 懒惰
贪婪模式:尽可能多的匹配;
懒惰模式:尽可能少的匹配,精准模式;
默认情况下为贪婪模式,以下组合为懒惰模式:
*?
+?
实例:
import re
string = "Ultraman_FatherofUltra_MotherofUltra"
pat_greedy = "[MF]a.*a"
pat_lazy = "[MF]a.*?a"
re_obj_greedy = re.compile(pat_greedy).findall(string)
re_obj_lazy = re.compile(pat_lazy).findall(string)
print("贪婪匹配结果:", re_obj_greedy)
print("懒惰匹配结果:", re_obj_lazy)
输出:
贪婪匹配结果: ['FatherofUltra_MotherofUltra']
懒惰匹配结果: ['FatherofUltra']
2.5 正则表达式(模式)修饰符
正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|)
它们来指定。如 re.I | re.M
被设置成 I
和 M
标志:
re.S
:使.
匹配包括换行在内的所有字符;re.I
:使匹配对大小写不敏感;re.L
:做本地化识别(locale-aware)匹配;re.M
:多行匹配,影响^
和$
;re.U
:根据Unicode字符集解析字符。这个标志影响\w
,\W
,\b
,\B
;re.X
:该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
实例1:
import re
string = "我是该叫你奥特KITTY呢?还是叫你Hello曼?"
pat = "k...."
re_obj = re.compile(pat,re.I).findall(string) #re.I忽略大小写
print("凯蒂猫的英文名为:%s" % re_obj)
输出:
凯蒂猫的英文名为:['KITTY']
实例2:
import re
string = '''
迪迦奥特曼是“平成三杰”之首,他开创了多种形态的先河。迪迦奥特曼可以变身成三种不同形态,各种形态有不同的特点。
复合型的迪迦奥特曼拥有平衡的力量,力量型全身是红色的,而敏捷型全身是紫色的。
他是《迪迦奥特曼》中登场的光之巨人,人间体是真角大古。
说起迪迦奥特曼的过去,他可是传说中的黑暗奥特曼。
在超远古时期就出现在地球上,后来夺取了好友的力量而可以变成力量形态与敏捷形态。
在幽怜的劝说下,他弃暗投明,成为了光之巨人。
再后来,大古他们发现了在金字塔里的三个巨人,其中一个就是迪迦奥特曼的石像。
在怪兽快要将迪迦奥特曼的石像粉碎时 ,大古与他合二为一了。
大古与迪迦奥特曼合二为一后,他们就成为了好搭档,一起保护着地球,维护着人类和平。
大家熟知的迪迦奥特曼便是《迪迦奥特曼》里面的那个正义的光之巨人。
迪迦奥特曼改邪归正后一直都是为了人类不断奋战。
与怪兽战斗的过程中,人类的勇气与光芒都是迪迦的力量来源。
而打败最后boss的那份力量就是来自全人类的希望之光。" #文自百家号:书生漫 2019-08-10
'''
pat = "大家熟知的迪迦奥特曼.*?全人类的希望之光"
re_obj = re.compile(pat,re.S).findall(string) #re.S实现让.匹配到多行的字符
print("迪迦奥特曼概论:\n%s" % re_obj)
输出:
迪迦奥特曼概论:
['大家熟知的迪迦奥特曼便是《迪迦奥特曼》里面的那个正义的光之巨人。\n迪迦奥特曼改邪归正后一直都是为了人类不断奋战。\n与怪兽战斗的过程中,人类的勇气与光芒都是迪迦的力量来源。\n而打败最后boss的那份力量就是来自全人类的希望之光']
注意是希望之光不是民族之光luckin…
3. match() 和 search() 方法
3.1 re.match() 方法
re.match()
尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()
就返回 None
。
re.match(pattern, string, flags=0)
参数说明:
pattern
匹配的正则表达式;string
要匹配的字符串;flags
标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等;
re.match()
方法若匹配成功,返回一个匹配的对象,否则返回 None
。可以用 group(num)
或 groups()
匹配对象函数来获取匹配表达式。匹配对象方法如下:
group(num=0)
:匹配的整个表达式的字符串,group()
可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。groups()
:返回一个包含所有小组字符串的元组,从1到所含的小组号。
实例:
import re
line = "Cats are smarter than dogs"
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 dogs
matchObj.group(1) : Cats
matchObj.group(2) : smarter
3.2 re.search() 方法
re.search()
扫描整个字符串并返回第一个成功的匹配。
re.search(pattern, string, flags=0)
参数说明:
pattern
:匹配的正则表达式;string
:要匹配的字符串;flags
:标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等;
re.search()
方法若匹配成功,返回一个匹配的对象,否则返回 None
。可以用 group(num)
或 groups()
匹配对象函数来获取匹配表达式。参数说明同上,实例如下:
import re
print(re.search('blog', 'blog.csdn.net').span()) # 在起始位置匹配
print(re.search('csdn', 'blog.csdn.net').span()) # 不在起始位置匹配
print(re.search('net', 'blog.csdn.net').span()) # 不在起始位置匹配
输出:
(0, 4)
(5, 9)
(10, 13)
实例2:
import re
line = "Cats are smarter than dogs"
matchObj = re.search( 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 dogs
matchObj.group(1) : Cats
matchObj.group(2) : smarter
3.3 re.match() 与 re.search() 的区别
re.match()
只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回 None
;而 re.search()
匹配整个字符串,直到找到一个匹配。实例如下:
line = "Cats are smarter than dogs"
matchObj = re.match( r'dogs', line, re.M|re.I)
if matchObj:
print("match --> matchObj.group() : ", matchObj.group())
else:
print("match --> No match!!")
matchObj = re.search( r'dogs', line, re.M|re.I)
if matchObj:
print("search --> matchObj.group(): ", matchObj.group())
else:
print("search --> No match!")
输出:
match --> No match!
search --> matchObj.group(): dogs
基础知识还是很多的,包括奥特曼,正则表达式、compile()方法、match()、search()方法,其中正则表达式中的原子及作用要掌握。
实战下篇见~
参考:
https://edu.aliyun.com/course/1994
https://edu.aliyun.com/course/505
https://docs.python.org/3.7/library/re.html?highlight=re#module-re
https://baijiahao.baidu.com/s?id=1641401189459092938&wfr=spider&for=pc