学习笔记,续上篇
八、正则表达式解析HTML网页
8.1 正则表达式介绍
什么是正则表达式:
通俗理解:按照一定的规则,从某个字符串中匹配出想要的数据,这个规则就是正则表达式。
标准答案:https://baike.baidu.com/item/正则表达式/1700215?fr=aladdin
一个端子:
世界上分为两种人:一种是懂正则表达式的,一种是不懂正则表达式的。
8.2 正则表达式常用匹配规则(匹配某个字符串):
导入包:
import re
8.2.1、匹配某个字符串
text = "hello ahhe"
ret = re.match('he',text)
print(ret.group())
结果:
he
2.点(.)匹配任意的字符串
# text = 'ab'
text = '+ab'
ret = re.match('.',text)
print(ret.group())
结果:
+
但是点(,)匹配不到换行符:
text = "\n"
ret = re.match('.',text)
print(ret.group())
结果:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-7-67cceb5a14c3> in <module>()
2 text = "\n"
3 ret = re.match('.',text)
----> 4 print(ret.group())
AttributeError: 'NoneType' object has no attribute 'group'
8.2.3.\d:匹配任意的数字(0~9):
text = "12477490ahahah"
ret = re.match('\d',text)
print(ret.group())
结果;
12477490
8.2.4、\D:匹配任意的非数字:
text = '~s+22626'
ret = re.match('\D',text)
print(ret.group())
结果:
~s+
8.2.5、\s匹配空白字符(\n,\t,\r,空格)
text = ' '
ret = re.match('\s',text)
print(ret.group())
结果:
8.2.6、\w:匹配的是a-z,A-Z,数字和下划线
text = '_ajakak'
ret = re.match('\w',text)
print(ret.group())
结果:
_
8.2.7、\W匹配的是和\w相反的
text = '~'
ret = re.match('\W',text)
print(ret.group())
结果:`
~
8.2.8、组合
组合的方式,只要满足括号中的某一个字符,就可以匹配,其中,特殊字符需要加’'进行转译
text = 'a881234'
ret = re.match('[a1]',text)
print(ret.group())
结果:
a88
8.3 匹配规则用中括号代替
之前讲到的几种匹配规则,其实可以使用中括号的形式来代替:
- \d:[0-9]
- \D:[^0-9]
- \w:[0-9a-zA-Z_]
- \W:[^0-9a-zA-Z_]
8.3.1 中括号的形式代替\d:
text = '1747839-36'
ret = re.match('[0-9]+',text)
print(ret.group())
结果:
1747839
8.3.2 中括号的形式代替\D
text = "0731-88888888"
ret = re.match('[\d\-]+',text)#特殊字符-需要加\转译,+号表示匹配到多个满足条件的字符,注意:不是整个字符串!
print(ret.group())
结果:
0731-88888888
8.3.3 中括号的形式匹配\w:
text = '_1323afsghsh'
ret = re.match('[0-9a-zA-Z_]+',text)
print(ret.group())
结果:
_1323afsghsh
8.3.4 中括号的形式匹配\W:
text = '@#$'
ret =re.match('[^0-9a-zA-Z]+',text)
print(ret.group())
结果:
@#$
8.4 正则表达式常用匹配规则(匹配多个字符串):
8.4.1 *:
匹配0个(没有也不报错,返回的是空值)或者任意多个字符,示例代码如下:
text = '0731sa33'
ret = re.match('\d*',text)
print(ret.group())
结果:
0731
以上匹配的要求是\d,那么要求是数字,后面跟了一个星号,就可以匹配前面的所有数字
8.4.2 +;
可以匹配一个(没有匹配项就会报错)或多个字符。最少一个,
text = '67801'
ret = re.match('\d+',text)
print(ret.group())
结果:
67801
8.4.3 ?:
匹配一个或者0个(要么没有,要么就只有一个)
text = 'abdc'
ret = re.match('\w?',text)
print(ret.group())
结果:
a
8.4.4 {m}:
匹配m个字符,
text = 'abshsm'
ret = re.match('\w{4}',text)
print(ret.group())
结果:
absh
8.4.5 {m,n}
匹配第m到第n个字符
text = '126266290'
ret = re.match('\d{2,6}',text)
print(ret.group())
结果:
126266
其中,有一点是:上面的m不论是写1、2、3…5,结果都一样,m感觉没什么用。
8.5、小案例
8.5.1 验证手机号码
手机号码的规则是以1开头,第二位可以是34578,后面那9位随意,
text = '15308432166'
ret = re.match('1[34578]\d+',text)
print(ret.group())
结果:
15308432166
8.5…2 验证邮箱
邮箱的规则是邮箱名称用数字、字母、下划线
,然后是@符号,后面就是域名了。
text = '15308432166@@@@@163.com'
ret = re.match('[@\w.]+',text)
print(ret.group())
结果:
15308432166@@@@@163.com
注意:用中括号括起来表示中括号里面的匹配字符是没有顺序的,不加中括号,会严格执行匹配次序
text = '15308432166@@@@@163.com'
ret = re.match('@\w+.',text)
print(ret.group())
结果:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-87-7fe3c9907b95> in <module>()
1 text = '15308432166@@@@@163.com'
2 ret = re.match('@\w+.',text)
----> 3 print(ret.group())
AttributeError: 'NoneType' object has no attribute 'group'
我们匹配数字和第一个@符号:
text = '[email protected]'
ret = re.match('\w+@',text)
print(ret.group())
结果:
15308432166@
另外,.可以表示任意字符,但是把.放到中括号里面只能代表.本身
text = '15308432166@@@@@163.com'
ret = re.match('.+',text)
print(ret.group())
结果:
15308432166@@@@@163.com
匹配.
text = '....15308432166@@@@@163....com'
ret = re.match('[.]+',text)
print(ret.group())
结果:
....
8.5.3 验证url
URl的规则是:URL的前面是http或者https或者ftp然后加一个冒号,再加上一个斜杠,再后面就是可以出现任意非空白字符集了
text = 'http://www.baidu.com'
ret = re.match('[http|https|ftp://\w.]+',text)
print(ret.group())
http://www.baidu.com
8.5.4 验证身份证号
身份证的规则信息是:总共有18位,前面17位都是数字,也可以是小写的x,也可以是大写的X。
text = '31244567892727727X'
ret = re.match('\d{17}[\dxX]',text)
print(ret.group())
结果:
31244567892727727X
8.6 开始、结束、和、或语法
8.6.1 ^(脱字号):表示以…结束
text = 'hello'
ret = re.match('^h',text)
print(ret.group())
结果:
h
如果是在中括号中,代表的是取反操作。
8.6.2 $:表示以…结束:
text = '[email protected]'
ret = re.match('\[email protected]$',text)
print(ret.group())
结果:
[email protected]
其实,不加$也可以匹配上面这个字符串:
text = '[email protected]'
ret = re.match('\[email protected]',text)
print(ret.group())
结果:
[email protected]
加$是为了防止@163.com后面还有其他字符,
text = '[email protected]'
ret = re.match('\[email protected]&',text)
print(ret.group())
结果:
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-126-d916cb2f7064> in <module>()
1 text = '[email protected]'
2 ret = re.match('\[email protected]&',text)
----> 3 print(ret.group())
AttributeError: 'NoneType' object has no attribute 'group'
8.6.3 |:匹配多个字符串或者表达式
text = 'https'
ret = re.match('(http|https|ftp)',text)
print(ret.group())
http
8.6.4 贪婪模式和非贪婪模式
贪婪模式:正则表达式会匹配尽量多的字符,默认是贪婪模式
非贪婪模式:正则表达式会尽量少的匹配字符。
贪婪模式:
text = '<h1>text<h1>'
ret = re.match('<.+>',text)
print(ret.group())
<h1>text<h1>
非贪婪模式:
text = '<h1>text<h1>'
ret = re.match('<.+?>',text)
print(ret.group())
<h1>
8.6.5 0-100之间的数字
text = '98'
ret = re.match('[1-9]\?6$|100',text)
print(ret.group())
9
?号是一个很好的操作,在这道例题中,一位数和两位数都可以表示,相当方便。
8.7、转译字符和原生字符
在正则表达式中,有些字符是有特殊意义的字符。因此如果想要匹配这些字符,那么就必须使用反斜杠来进行转译。比如 ,那么就必须使用$,
text = 'apple price is \$99,orange price is $88'
ret = re.search('\$(\d+)',text)
print(ret.group())
$99
原生字符串:
早正则表达式中,\是专门用来做转译的。在python中,\也是用来做转译的。因此如果想要在普通的字符串中匹配出\,那么要给出四个\。
text = 'apple \c'
ret = re.search('\\\\c',text)
print(ret.group())
\c
r = raw,代表原生的意思,意思是字符串里面本来是什么,加r后,就是什么
print('\\n')
\n
print(r'\n')
\n
print('\n')
所以,使用原生字符串可以表示为下:
text = 'apple \c'
ret = re.search(r'\\c',text)
print(ret.group())
\c
text = '\\n'
ret = re.match(r'\n',text)
print(ret.group())
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-157-965bbb43f52d> in <module>()
1 text = '\\n'
2 ret = re.match(r'\n',text)
----> 3 print(ret.group())
AttributeError: 'NoneType' object has no attribute 'group'
8.8、re模块常用函数
8.8.1 match()
import re
从最开始的位置进行匹配,如果开始的位置没有找到,就直接失败了。
text ='hello'
ret = re.match('h',text)
print(ret.group())
h
如果第一个字母不是h,那么就会失败:
# text = 'ahleel'
ret = re.match('h',text)
print(ret.group())
.匹配不到换行符,如果想要匹配换行的数据,那么要传入一个flag = re.DOTALL,就可以匹配换行符了:
text = 'abc\njja'
ret = re.match('\w+.*\w+',text,re.DOTALL)
print(ret.group())
abc
jja
8.8.2 search()
在字符串中找满足条件的字符。如果找到,就返回。说白了,就是只会找到第一个满足条件的。
text = "apple's price is $99,orange's price is$98"
ret = re.search('\d+',text)
print(ret.group())
99
8.8.3 group()
在正则表达式中,可以对过滤到的字符串进行分组,分组使用圆括号的形式。
- group:和group(0)是等价的,返回的是整个符合字符串的字符
- groups:返回的是里面的子组。索引从1开始。
- group(1):返回的是第一个子组,可以传入多个
text = "apple's price is $99,orange's price is $98"
ret = re.search(r".*(\$\d+).*(\$\d+)",text)
print(ret.group())
apple's price is $99,orange's price is $98
print(ret.group(1))
$99
print(ret.group(2))
$98
print(ret.group(0))
apple's price is $99,orange's price is $98
print(ret.group(1,2))
('$99', '$98')
print(ret.groups())
('$99', '$98')
8.8.4 findall()
找出所有满足条件的,返回的是一个列表:
text = "apple's price is $99,orange's price is $98"
ret = re.findall('\d+',text)
print(ret)
['99', '98']
8.8.5 sub()
用来替换字符串。将匹配到的字符串替换为其他字符串。
text = "apple's price is $99,orange's price is $98"
ret = re.sub('\d+','0',text)
print(ret)
apple's price is $0,orange's price is $0
sub函数的案例,获取拉钩网中的数据:
text = """
<dd class="job_bt">
<h3 class="description">职位描述:</h3>
<div class="job-detail">
<p>工作职责:</p>
<p>1、负责服务端的后台架构的优化、设计与开发;</p>
<p>2、根据产品需求,制定解决方案;</p>
<p>3、负责现有系统的性能调优等的设计与实现;</p>
<p>任职要求:</p>
<p>1、本科以上学历, 计算机或相关专业;</p>
<p>2、基础扎实,熟练掌握Python,2年以上开发经验;</p>
<p>3、熟悉 Linux 开发环境,熟悉 tornado 、django 或 flask 等框架。</p>
<p>4、思路清晰,优秀的分析和解决问题的能力, 具备良好的沟通能力,团队合作意识, 能主动承担,乐于分享;</p>
<p>5、熟悉移动互联网服务及应用开发者优先;</p>
<p>6、熟悉MySQL、MongoDB、Redis数据库和多线程编程;</p>
<p>加分项:</p>
<p>1. 有过高并发服务器设计和架构经验,系统调优相关经验;</p>
<p>2. 简历中最好能提供个人博客地址和 Github 地址。</p>
<p>应聘前请搜索关注“兰心书院”公众号,了解我们的产品</p>
</div>
</dd>
"""
ret = re.sub('<.*?>','',text)
print(ret)
职位描述:
工作职责:
1、负责服务端的后台架构的优化、设计与开发;
2、根据产品需求,制定解决方案;
3、负责现有系统的性能调优等的设计与实现;
任职要求:
1、本科以上学历, 计算机或相关专业;
2、基础扎实,熟练掌握Python,2年以上开发经验;
3、熟悉 Linux 开发环境,熟悉 tornado 、django 或 flask 等框架。
4、思路清晰,优秀的分析和解决问题的能力, 具备良好的沟通能力,团队合作意识, 能主动承担,乐于分享;
5、熟悉移动互联网服务及应用开发者优先;
6、熟悉MySQL、MongoDB、Redis数据库和多线程编程;
加分项:
1. 有过高并发服务器设计和架构经验,系统调优相关经验;
2. 简历中最好能提供个人博客地址和 Github 地址。
应聘前请搜索关注“兰心书院”公众号,了解我们的产品
8.8.6 split()
使用正则表达式来分割字符串
text = 'hello world ni hao'
ret = re.split('\W',text)
print(ret)
['hello', 'world', 'ni', 'hao']
8.8.7 complile()
对于一些经常使用的正则表达式,可以使用compile进行编译,后期再使用的时候可以直接拿过来用,执行效率会更快。而且,compile还可以指定flag = re.BERBOSE,在写正则表达式的时候可以做好注释。
text = 'the number is 20.50'
r = re.compile(r"""
\d+ # 小数点前面数字
\.* # 小数点,加*是因为可以没有小数点
\d* # 小数点后面数字,加*是因为可以没有
""",re.VERBOSE)
ret = re.search(r,text)
print(ret.group())
20.50
九、【实战】正则表达式之古诗文爬虫
官网:https://www.gushiwen.org/default_2.aspx
import requests
import re
def parse_page(url):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
response = requests.get(url, headers)
# print(response.text)
text = response.text
#注意:在使用正则表达式解析网页的时候,原网页代码是被正则表达式看做一个平铺的、没有任何结构的、长的字符串来处理的。
# re.DOTALL可以让.匹配\n
titles = re.findall(r'<div\sclass="cont">.*?<b>(.*?)</b>',text,re.DOTALL)
dynasties = re.findall(r'<p\sclass="source">.*?target="_blank">(.*?)</a>',text,re.DOTALL)
authors = re.findall(r'<p\sclass="source">.*?</a>.*?target="_blank">(.*?)</a>',text,re.DOTALL)
content_tags = re.findall(r'<div\sclass="contson".*?>(.*?)</div>',text,re.DOTALL)
contents = []
for content in content_tags:
x = re.sub(r'<.*?>','',content)
contents.append(x.strip())
poems = []
for value in zip(titles,dynasties,authors,contents):
title,dynasty,author,content = value
poem = {
'title':title,
'dynasty':dynasty,
'author':author,
'content':content
}
poems.append(poem)
for poem in poems:
print(poem)
#print('*'*10)
def main():
url = "https://www.gushiwen.org/default_1.aspx"
for x in range(1,11):
url = "https://www.gushiwen.org/default_%s.aspx" % x
parse_page(url)
if __name__ == '__main__':
main()
结果:
{'title': '黄州', 'dynasty': '宋代', 'author': '陆游', 'content': '局促常悲类楚囚,迁流还叹学齐优。江声不尽英雄恨,天意无私草木秋。万里羁愁添白发,一帆寒日过黄州。君看赤壁终陈迹,生子何须似仲谋!'}
{'title': '题梵隐院方丈梅', 'dynasty': '宋代', 'author': '晏敦复', 'content': '亚槛倾檐一古梅,几番有意唤春回。吹香自许仙人下,照影还容高士来。月射寒光侵涧户,风摇翠色锁阶苔。游蜂野蝶休相顾,本性由来不染埃。'}
{'title': '女冠子·春山夜静', 'dynasty': '五代', 'author': '李珣', 'content': '春山夜静,愁闻洞天疏磬。玉堂虚,细雾垂珠佩,轻烟曳翠裾。对花情脉脉,望月步徐徐。刘阮今何处?绝来书!'}
{'title': '折桂令·荆溪即事', 'dynasty': '元代', 'author': '乔吉', 'content': '问荆溪溪上人家,为甚人家,不种梅花?老树支门,荒蒲绕岸,苦竹圈笆。寺无僧狐狸样瓦,官无事乌鼠当衙。白水黄沙,倚遍栏干,数尽啼鸦。'}
{'title': '把酒问月·故人贾淳令予问之', 'dynasty': '唐代', 'author': '李白', 'content': '青天有月来几时?我今停杯一问之。人攀明月不可得,月行却与人相随。皎如飞镜临丹阙,绿烟灭尽清辉发。但见宵从海上来,宁知晓向云间没。白兔捣药秋复春,嫦娥孤栖与谁邻?今人不见古时月,今月曾经照古人。古人今人若流水,共看明月皆如此。唯愿当歌对酒时,月光长照金樽里。'}
{'title': '城南', 'dynasty': '宋代', 'author': '曾巩', 'content': '雨过横塘水满堤,乱山高下路东西。一番桃李花开尽,惟有青青草色齐。'}
{'title': '国风·郑风·野有蔓草', 'dynasty': '先秦', 'author': '佚名', 'content': '野有蔓草,零露漙兮。有美一人,清扬婉兮。邂逅相遇,适我愿兮。野有蔓草,零露瀼瀼。有美一人,婉如清扬。邂逅相遇,与子偕臧。'}
{'title': '题醉中所作草书卷后', 'dynasty': '宋代', 'author': '陆游', 'content': '胸中磊落藏五兵,欲试无路空峥嵘。酒为旗鼓笔刀槊,势从天落银河倾。端溪石池浓作墨,烛光相射飞纵横。须臾收卷复把酒,如见万里烟尘清。丈夫身在要有立,逆虏运尽行当平。何时夜出五原塞,不闻人语闻鞭声。'}
{'title': '江畔独步寻花·其五', 'dynasty': '唐代', 'author': '杜甫', 'content': '黄师塔前江水东,春光懒困倚微风。桃花一簇开无主,可爱深红爱浅红?'}
{'title': '无闷·催雪', 'dynasty': '宋代', 'author': '吴文英', 'content': '霓节飞琼,鸾驾弄玉,杳隔平云弱水。倩皓鹤传书,卫姨呼起。莫待粉河凝晓,趁夜月、瑶笙飞环佩。正蹇驴吟影,茶烟灶冷,酒亭门闭。 歌丽。泛碧蚁。放绣帘半钩,宝台临砌。要须借东君,灞陵春意。晓梦先迷楚蝶,早风戾、重寒侵罗被。还怕掩、深院梨花,又作故人清泪。'}
{'title': '断句', 'dynasty': '宋代', 'author': '苏麟', 'content': '近水楼台先得月,向阳花木易为春。'}
{'title': '浣溪沙·山色横侵蘸晕霞', 'dynasty': '宋代', 'author': '苏轼', 'content': '山色横侵蘸晕霞,湘川风静吐寒花。远林屋散尚啼鸦。梦到故园多少路,酒醒南望隔天涯。月明千里照平沙。'}
{'title': '樛木', 'dynasty': '先秦', 'author': '佚名', 'content': '南有樛木,葛藟累之。乐只君子,福履绥之。南有樛木,葛藟荒之。乐只君子,福履将之。南有樛木,葛藟萦之。乐只君子,福履成之。'}
{'title': '小松', 'dynasty': '唐代', 'author': '杜荀鹤', 'content': '自小刺头深草里,而今渐觉出蓬蒿。 时人不识凌云木,直待凌云始道高。'}
{'title': '烛影摇红·题安陆浮云楼', 'dynasty': '宋代', 'author': '廖世美', 'content': '霭霭春空,画楼森耸凌云渚。紫薇登览最关情,绝妙夸能赋。惆怅相思迟暮。记当日、朱阑共语。塞鸿难问,岸柳何穷,别愁纷絮。 催促年光,旧来流水知何处。断肠何必更残阳,极目伤平楚。晚霁波声带雨。悄无人、舟横野渡。数峰江上,芳草天涯,参差烟树。'}
End