No.26re模块

No.26

内容概要

  • 转义符
  • re模块
  • 分组
    • 分组命名
    • 引用分组
  • 爬虫案例

内容回顾和补充

  1. 正则表达式

    • 元字符

      • \d \w \s \D \W \S
      • . [ ] [ ^ ]
      • ^ $
      • | ( )
    • 量词

      • ? 0次或1次
      • + 1次或多次
      • * 0次或多次
    • 贪婪匹配

      • 默认在规则和量词范围内尽量多匹配
      • 回溯算发
    • 惰性匹配

      • 尽量少匹配
      • .*?x → 表示匹配任意内容直到遇到一个x就停止。
      # 以a开头,至少一个字母组成的字符串。
      ^a[a-zA-Z]+
      
      # 以1开头,中间3-5个数字,如果中间超过5个数字,则整个字符串不匹配。
      ^1\d{3-5}$
      
  2. 三级菜单

    # 递归
    menu = {
        '北京': {
            '海淀': {
                '五道口': {
                    'soho': {},
                    '网易': {},
                    'google': {}
                },
                '中关村': {
                    '爱奇艺': {},
                    '汽车之家': {},
                    'youku': {},
                },
                '上地': {
                    '百度': {},
                },
            },
            '昌平': {
                '沙河': {
                    '老男孩': {},
                    '北航': {},
                },
                '天通苑': {},
                '回龙观': {},
            },
            '朝阳': {},
            '东城': {},
        },
        '上海': {
            '闵行': {
                "人民广场": {
                    '炸鸡店': {}
                }
            },
            '闸北': {
                '火车战': {
                    '携程': {}
                }
            },
            '浦东': {},
        },
        '山东': {},
    }
    
    def menu_func(menu):
    
        while True:
            for key in menu:
                print(key)
            inp = input('>>>').strip()
            if inp.upper() == 'Q':      # 直接退出函数
                return 'q'
            if inp.upper() == 'B':      # 返回上一级
                return
            if menu.get(inp):
                flag = menu_func(menu[inp])
                if flag == 'q':
                    return 'q'
    
    menu_func(menu)
    
    # 栈
    lst = [menu]
    while lst :
        for key in lst[-1]:  # 列表索引取的就是字典本身
            print(key)
        inp = input('>>>')
        if inp == 'q':
            break
        if inp == 'b':
            lst.pop()
        if lst[-1].get(inp):         # 字典中取到下一层字典
            lst.append(lst[-1][inp]) # 把下一层字典加入列表
    
    # 计算多层文件夹中所有文件的总大小
    import os
    
    path = r'D:\Python视频'
    lst = [path]
    size = 0
    while lst:
        path = lst.pop()
        name_lst = os.listdir(path)
        for name in name_lst:
            full_path = os.path.join(path,name)
            if os.path.isdir(full_path):   # 判断是不是文件夹
                lst.append(full_path)
            elif os.path.isfile(full_path):  # 判断是不是文件
                size += os.path.getsize(full_path)
    
    print(size)
    

内容详细

1.转义符

print('\n')
print('\\n')
print(r'\n')
print(r'\\n')

2.re模块

  • findall
    • 会匹配字符串中所有符合规则的项并返回一个列表,如果未匹配到则返回空列表
import re
v  = re.findall('\d', 'alex68')
print(v)
结果:['6', '8']

v  = re.findall('^\d', 'alex68')
结果:空列表
  • search
    • 只会匹配字符串中第一个符合条件的项
    • 如果匹配到了,返回一个对象,用group方法取值
    • 如果未匹配到,返回None,不能用group方法取值
import re
v = re.search('\d+', 'alex68')  # 返回一个对象
print(v)             
print(v.group())            # 调用对象的方法获取结果

v = re.search('\d', 'alex68')
print(v)       
print(v.group()) 
# 结果6,只会匹配字符串中符合正则条件的第一项

v = re.search('^\d', 'alex68')
print(v)
print(v.group())     
# 未匹配到返回None,用group取值会报错。
  • match
    • 尝试从字符串的起始位置匹配,如果起始位置匹配失败,match就返回none。
    • 如果匹配成功,会返回一个对象,用group方法取值。
    • match = search + ^正则
import re
v = re.match('\d','allex68')
print(v)  
# 没有结果,永远从头开始匹配,可以认为search方法中正则规则前加了^号。
  • 进阶方法

    1. 时间复杂度(效率)

    2. 空间复杂度(内存占用率)

    3. 用户体验

      • finditer

        • 在查询结果超过一个的情况下,能够有效的节省内存,降低空间复杂度,从而也降低了时间复杂度。
        import re
        v1 = re.findall('\d', 'abc678'*20000000)
        print(v1)   # 效率慢
        
        v2 = re.finditer('\d', 'abc678'*20000000) # 返回迭代器
        print(v2)
        for i in v2:  # 迭代出来的每一项都是一个对象
            print(i.group()) # 通过group取值
        
      • compile

        • 在同一个正则表达式重复使用多次的时候使用,能够减少时间的开销。
        把正则表达式编译成python代码
        ret = re.compile('\d+')        # 把正则表达式'd+'先编译成python代码。
        v1 = ret.findall('alex678')    
        print(v1)
        v2 = ret.search('alex678')
        print(v2)
        v3 = ret.finditer('alex678')
        print(v3)
        
        # 如果正则表达式很长,而且需要重复使用,通过compile可以先编译正则表达式,这样就节省了每次使用正则表达式时,python内部都要将正则表达式先编译所花费的时间。
        s = '<li class="list-item" data-from="">.*?<div class="house-title">.*?title=(?P<title>.*?)href.*?>.*?</a>.*?<span class="broker-name broker-text">(?P<name>.*?)</span>'
        
      • split

        • 用正则规则作切分
        import re
        v1 = re.split('\d+', 'alex66eric88')
        print(v1)
        结果:['alex', 'eric', '']
        
        v2 = re.split('(\d+)', 'alex66eric88') # 默认自动保留分组中的内容
        print(v2)
        结果:['alex', '66', 'eric', '88', '']
        
      • sub

        • 用正则规则作替换
        v1 = re.sub('\d','X','alex66eric88')  # 替换
        print(v1)
        结果:alexXXericXX  
        
        v2 = re.sub('\d','X','alex66eric88',1)
        print(v2)
        结果:alexX6eric88 # 只替换字符串中符合规则的第一个字符
        
      • subn

        v = re.subn('\d','X','alex66eric88')
        print(v)
        ('alexXXericXX', 4) # 返回元组,第二个元素表示替换了几个。
        

3.方法总结

  • findall 找所有
  • search 找第一个
    • 返回对象,group取值
  • match 从头开始找第一个
    • 返回对象,group取值
  • finditer 返回迭代器
    • 迭代的是对象,通过group取值
  • compile
    • 同一个正则表达式多次使用时避免重复编译
  • sub / subn
  • split

记忆方法:参数顺序,数据类型,返回值类型。

参数顺序 + 数据类型:str(正则表达式),str(待匹配字符串)

4.分组

  • group配合分组使用
import re
s1 = '<h1>wahaha</h1>'
v1 = re.search('<(\w+)>(.*?)</\w+>', s1)

print(v1)
结果:<re.Match object; span=(0, 15), match='<h1>wahaha</h1>'>

print(v1.group(0)) # group参数默认为0,表示取整个正则匹配到的结果
结果:s1 = '<h1>wahaha</h1>'

print(v1.group(1)) # 取第一个分组中的内容
结果:h1

print(v1.group(2)) # 取第二个分组中的内容
结果:wahaha
  • 分组命名
# (?P<名字>正则表达式)
s2 = '<a>wahaha ya wahaha</a>'
v2 = re.search('<(?P<name1>\w+)>(?P<name2>.*?)</\w+>', s2)

print(v2.group('name1'))  # 通过分组名取值
print(v2.group('name2'))
  • 引用分组
# 分组命名:(?P<名字>正则表达式)
# 引用分组:(?P=组名) 这个组中的内容必须完全和之前已经存在的组匹配到的内容一模一样

引用分组方式一
s3 = '<h1>jiaduobao</h2>' # h1 和 h2 不一样
v3 = re.search('<(?P<tag>\w+)>.*?</(?P=tag)>', s3)
print(v3)
结果:None

引用分组方式二
s3 = '<h1>jiaduobao</h1>' 
v3 = re.search(r'<(\w+)>.*?</\1>', s3) # \1表示引用第一个分组中的内容,不推荐使用。
print(v3)       

总结:

  • 分组命名
    • (?P<名字>正则) search group("组名")
  • 引用分组
    • (?P=组名) 表示这个组中的内容必须和之前已经存在的一组匹配到的内容完全一致。
  • split遇到分组
    • 会保留分组中本来应该被切割掉的内容
  • findall遇到分组
    • 默认findall优先显示分组中的内容
    • 取消分组优先显示(?:正则)
import re
# search方法得到的是一个对象,需要通过group方法取值
s1 = '<h1>jiaduobao</h1>'
v1 = re.search(r'<(\w+)>.*?</\1>', s1)
print(v1.group())   # 匹配到符合正则表达式的字符串,默认整个为一组全部显示。
结果:<h1>jiaduobao</h1>
print(v1.group(1))  # 匹配到符合正则表达式的字符串,只显示其中第一个分组。
结果:h1

# findall方法正则表达式中的分组,会优先显示分组中的内容。
v2 = re.findall('\d(\d)', '6alex18')
print(v2)
结果:['8']

# 对于findall方法,取消优先显示分组中的内容
v3= re.findall('\d+(?:\.\d+)?', '1.78 + 2')
print(v3)
['1.78', '2']  # ?:取消优先显示分组中的内容

习题

# 匹配:1 - 2*(60+(-40.35/5)-(-4*3))中的所有整数

import re
v = '1 - 2*(60+(-40.35/5)-(-4*3))'
ret = re.findall(r'\d+\.\d+|(\d+)', v)
print(ret)
['1', '2', '60', '', '5', '4', '3']
ret.remove('')
['1', '2', '60', '5', '4', '3']
print(ret)

5.爬虫

import requests
import json
import re

pattern = '<div class="item">.*?<em.*?>(?P<id>\d+)</em>.*?<span class="title">(?P<title>.*?)</span>.*?<span class="rating_num".*?>(?P<score>.*?)</span>.*?<span>(?P<comment_num>.*?)人评价</span>'
par = re.compile(pattern, flags=re.S)

def parser_page(par, content):
    res = par.finditer(content)
    for i in res:
        yield {'id': i.group('id'),
               'title': i.group('title'),
               'score': i.group('score'),
               'com_num': i.group('comment_num')}

def get_page(url):
    ret = requests.get(url)
    return ret.text

def write_file(name):
    with open(name, 'w', encoding='utf-8') as f:
        while True:
            dic = yield
            f.write('%s\n' % json.dumps(dic, ensure_ascii=False))

num = 0
f = write_file('movie_info')
next(f)
for i in range(10):
    content = get_page('https://movie.douban.com/top250?start=%s&filter=' %num)
    g = parser_page(par, content)
    for dic in g:
        f.send(dic)
    num += 25
posted @ 2020-03-28 10:42  Sco_Lunatic  阅读(200)  评论(0)    收藏  举报