Python网络爬虫之数据解析(二)

版权声明:本文为博主原创文章,有参考的地方都会在文中给出链接。如有转载,需征求博主同意。 https://blog.csdn.net/striver6/article/details/88761990

在这里插入图片描述

学习笔记,续上篇

八、正则表达式解析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

猜你喜欢

转载自blog.csdn.net/striver6/article/details/88761990