Python爬虫之re正则

1. 基本规则

# 元字符:  
    # . ^ $ * + ? { } [ ] | ( ) \

# 字符类型匹配:
    #  .  表示匹配任意一个字符(换行符除外)
    #  [asdf]  表示匹配中括号里面的任意一个字母一次 
    #  [a-z]  表示匹配a-z中的任意一个字母    [0-9] 表示匹配0-9中的任意一个数字
    #  [^0-9] 中括号中有^符号,表示非,除---之外,这里表示除0-9之外的任意字符

    # \d 匹配数字,即 [0-9]
    # \D 匹配⾮数字,即不是数字 [^0-9]
    # \s 匹配空⽩,即 空格,tab键 [\t\n\r\f\v]
    # \S 匹配⾮空⽩ [^\t\n\r\f\v]
    # \w 匹配单词字符,即a-z、A-Z、0-9、_  [a-zA-Z0-9_]
    # \W 匹配⾮单词字符  [^[a-zA-Z0-9_]]
    # \b 匹配一个特殊字符边界,比如 空格、&、# 等

# 定位:
    #  ^  表示起始定位
    #  $  表示结束定位

# 匹配次数:
    #  *  表示任意次
    #  +  至少1次 [1,+oo]
    #  ?  匹配0次或者1次
    #  {a,b}  匹配指定的次数范围,如 {0,}相当于匹配任意次 ,{6} 表示匹配6次

# 分组 & 后向引用 & 别名:
    # (ab)   将括号中字符作为⼀个分组
    # \num   引⽤分组num匹配到的字符串
    # (?P<name>)  分组起别名
    # (?P=name) 引⽤别名为name分组匹配到的字符

# |  匹配左右任意⼀个表达式

2. findall

2.1 贪婪匹配&惰性匹配

1)贪婪模式

  • findall默认就是贪婪模式,其会尽可能多的匹配
  • findall会将所有匹配符合的内容保存到一个列表中
import re   # 导入re模块

# findall方法第一个参数是匹配的规则,第二个参数是要匹配的字符串
# findall会将所有匹配符合的内容保存到一个列表中
print(re.findall("hgzero", "thisishgzero"))
  # 输出:[hgzero]
data1 = re.findall("hg", "hgzerohgwzh") print(data1)   # 输出:['hg', 'hg']

2)惰性模式

  • 惰性模式就是尽可能少的去匹配
data1 = re.findall("hg*", "hggggg")  # 贪婪模式
data2 = re.findall("hg*?", "hggggg") # 惰性模式,后面的那个问号就表示惰性模式
print(data1)  # 输出:['hggggg']
print(data2)  # 输出:['h']

2.2 字符串转义流程

字符串转义的流程:字符串 --> python解释器转义 --> re模块的转义

# 转义
ret1 = re.findall("www.baidu", "www.baidu")   # 这里面的 . 会代指任意字符(除\n外)
ret1 = re.findall("www\.baidu", "www.baidu")  # 这里面,反斜杠的添加会让 . 符号失去元字符代指的意义,从而使其就表示普通的点 . 符号

# 字符串转义的流程:  字符串---> python解释器转义---> re模块的转义 
ret2 = re.findall(r"I\b", "I hIo Ion")    # 这里面的r ,表示在python层次不使用转义字符,直接将其传递给re模块
ret3 = re.findall("I\\\\b", "I hIo Ion")  # 这里适用4个\ , 表示在python解释器层次转义成2个\ , 然后再将其传入re模块进行转义


re.findall("I\\b", "I what")   # 这样使\\b在python层次被转义成\b传递给re模块
re.findall(r"I\b", "I what")   # 这样在前面加上r ,可以让python不转义字符串内容,而直接传递给re

3. search

 search会将匹配到的结果保存到一个对象中,且只匹配第一个对象。

用search取到的对象必须要用group取值。

# search会将匹配到的结果保存到一个对象中,且只匹配第一个对象
sear = re.search("\d+", "fasdfsaf345kdf89")  # search返回的只是一个对象,且只返回找到的第一个
retu = sear.group()   # 用search取到的对象必须要用group取值

# 可以用?P<name>的形式给某一部分命名别名
re.search("(?P<name>[a-z]+)(?P<age>\d+)", "hgzero21wzh23hg26").group("name")
re.search("(?P<name>[a-z]+)(?P<age>\d+)", "hgzero21wzh23hg26").group("age")

# 可以一次获取多个值
pattern = re.compile(r"(?P<prv>[\d]+[.]?[\d]*)(?P<typ>[-])(?P<after>[\d]+[.]?[\d]*)")
prv, typ, aft = pattern.search(calc_str).group("prv", "typ", "after")  # 获取三个值,这三个值会被保存在一个元组中

4. match

match只从开始开始匹配,且只匹配一次,返回一个对象,若没匹配到则什么都不返回

# match只从开始开始匹配,且只匹配一次,返回一个对象,若没匹配到则什么都不返回
re.match("\d+", "234fda") 

5. split

split会将字符串按照某字符分割,然后保存为一个列表

# split会将字符串按照某字符分割
re.split(" ", "hello abc what")    # 将字符串按照空格分割,保存到一个列表中
re.split("[ |]", "hello welcome|hi hgzero") # 将字符串按照空格或者|进行分割后保存到一个列表中

re.split("[ab]", "abc")  # 先按照a分割,左边形成一个空,然后将得到的bc再按照b分割,左边又得到一个空
# 打印结果为  ['', '', 'c']

6. sub

sub可以完成字符串的替换功能

# sub可以完成字符串的替换功能
re.sub("\d+", "A", "welcome666hgzero987")   # 将第三个参数中的字符串中的数字转换成A
# 这里面的第四个参数可以限定匹配替换的次数

re.subn("\d", "A", "welcome666hgzero987")  
# 将匹配到的内容放在一个元组里,结果中的第二个值为匹配替换的次数
# 打印结果为   ('welcomeAAAhgzeroAAA', 6)
result = re.sub(r"\([\d\+\-\*\/.]*\)", value, calc_str, 1)  # 将calc_str中被正则匹配到的内容替换为value,且只替换第一次匹配到的

7. compile

compile可以事先定义好规则,保存为一个对象,然后后面可以直接使用这个对象而无需再定义规则

# compile可以事先定义好规则,保存一个对象,然后后面可以直接使用这个对象而无需再定义规则
com = re.compile("\d+")
com.findall("welcome666hgzero987")

# 使用示例
pattern = re.compile(r"\((?P<init>[\d\+\-\*\/.]*)\)")
init_str = pattern.search(calc_str).group("init")

# re.S,可以让点号 . 匹配所有特殊字符,包括换行 \n 等
re.compile('<dd>.*?>(?P<index>\d+)<.*?data-src="(?P<imglink>.*?)".*?</dd>', re.S)

8. finditer

finditer可以将得到的数据保存到一个迭代器中

# finditer可以将得到的数据保存到一个迭代器中
ret = re.finditer("\d", "welcome666hgzero987")
next(ret).group()    # 可以通过next函数加上group调用迭代器中的内容


re.findall("www\.(baidu|163)\.com", "www.baidu.com")  # findall会优先将分组中的内容返回
# 这里的返回结果为  ['baidu']
re.findall("www\.(?:baidu|163)\.com", "www.baidu.com")  #  【在分组中加上 ?: 可以去掉分组的优先级】

9. 正则小练习

写一个计算器,思路:

  1. 先去除空格
  2. 对特殊字符报错(字母、加减乘除之外的字符)
  3. 括号都是成对的, 要从最内部的括号开始计算, 由内而外, 计算后得出结果
  4. 将3步骤得到的结果去替换3步骤匹配到的括号及内部的内容, 然后得到一个新的字符串
  5. 重复执行3步骤和4步骤, 最终得到一个不包含任何括号的字符串, 计算这个字符串表达式就是最终的结果
# 计算器  3+ ((1* 5*3) + ( (3* (8/2)/2) +2 ) *2) *2 + 4

import re

def del_space(calc_str):
    """
    去除整个字符串中的空格
    :param calc_str: 原始字符串
    :return: 去除空格后的字符串
    """
    data = calc_str.replace(" ", "")
    return data

def check_special(calc_str):
    """
    检测字符串中是否有字母及非运算的特殊字符,
    :param calc_str: 原始字符串
    :return: True or False, 若为True,则表示存在特殊字符
    """
    pattern = re.compile(r"[^\d\+\-\*\/()]")
    check = pattern.findall(calc_str)
    return check

def calculate(calc_str):
    """
    对字符串中的表达式进行计算
    :param calc_str: 数学计算表达式类型的字符串
    :return: 计算后的结果
    """
    while True:
        if calc_str.find("*") != -1:
            pattern = re.compile(r"(?P<prv>[\d]+[.]?[\d]*)(?P<typ>[*])(?P<after>[\d]+[.]?[\d]*)")
            prv, typ, aft = pattern.search(calc_str).group("prv", "typ", "after")
            # re.sub("%s%s%s" % (prv, typ, aft), str(value), calc_str)
            calc_str = calc_str.replace("%s%s%s" % (prv, typ, aft), str(float(prv) * float(aft)))
            continue
        elif calc_str.find("/") != -1:
            pattern = re.compile(r"(?P<prv>[\d]+[.]?[\d]*)(?P<typ>[/])(?P<after>[\d]+[.]?[\d]*)")
            prv, typ, aft = pattern.search(calc_str).group("prv", "typ", "after")
            calc_str = calc_str.replace("%s%s%s" % (prv, typ, aft), str(float(prv) / float(aft)))
            continue
        elif calc_str.find("+") != -1:
            pattern = re.compile(r"(?P<prv>[\d]+[.]?[\d]*)(?P<typ>[+])(?P<after>[\d]+[.]?[\d]*)")
            prv, typ, aft = pattern.search(calc_str).group("prv", "typ", "after")
            calc_str = calc_str.replace("%s%s%s" % (prv, typ, aft), str(float(prv) + float(aft)))
            continue
        elif calc_str.find("-") != -1:
            pattern = re.compile(r"(?P<prv>[\d]+[.]?[\d]*)(?P<typ>[-])(?P<after>[\d]+[.]?[\d]*)")
            prv, typ, aft = pattern.search(calc_str).group("prv", "typ", "after")
            calc_str = calc_str.replace("%s%s%s" % (prv, typ, aft), str(float(prv) - float(aft)))
            continue
        else:
            break
    return calc_str

def replace_expr(calc_str):
    """
    找到最内部的括号, 计算出值后将值替换到字符串中
    :param calc_str: 纯粹的要计算出结果的字符串
    :return: 当前字符串最内部的值
    """
    try:  # re的search方法如果匹配不到内容就会报错
        pattern = re.compile(r"\((?P<init>[\d\+\-\*\/.]*)\)")
        init_str = pattern.search(calc_str).group("init")
        value = str(calculate(init_str))
        result = re.sub(r"\([\d\+\-\*\/.]*\)", value, calc_str, 1)  # 只替换第一次匹配到的
        return result
    except Exception:
        return None

while True:
    origin_str = input("请输入要计算的内容: ")
    calc_str = del_space(origin_str)
    if check_special(calc_str):
        print("存在特殊字符, 请检查后再计算!")
        continue
    while True:
        str_init = replace_expr(calc_str)
        if str_init:
            calc_str = str_init
        else:
            value = calculate(calc_str)
            print("最终的结果为: ", value)
            break

# 测试: calc_strx = "3+ ((1* 5*3) + ( (3* (8/2)/2) +2 ) *2) *2 + 4 "
# 正确结果为:69

10. requests+re正则爬虫实战

requests+re正则爬取猫眼电影Top100电影信息

import re
import requests
import urllib3
import json

# 忽略警告
urllib3.disable_warnings()

def get_one_page(url, headers):
    try:
        response = requests.get(url, headers=headers, verify=False)
        if response.status_code == 200:
            return response.text
        else:
            return None
    except Exception as e:
        print(e)
        return None

def get_video_info(html_text):
    pattern = re.compile('<dd>.*?>(?P<index>\d+)<.*?data-src="(?P<imglink>.*?)".*?data-val.*?">(?P<name>.*?)<.*?'
                         'star">(?P<star>.*?)<.*?releasetime">(?P<releasetime>.*?)<.*?'
                         'integer">(?P<x>.*?)<.*?fraction">(?P<y>.*?)<.*?</dd>', re.S)
    item = re.findall(pattern, html_text)
    for i in item:
        yield {
            "序号": i[0],
            "电影名": i[2],
            "主演": i[3].strip()[3:],
            "上映时间": i[4][5:],
            "评分": i[5]+i[6],
            "图像链接": i[1],
         }

def save_as_file(data):
    with open("movie_info.txt", "a+", encoding="utf-8") as f:
        info = json.dumps(data, ensure_ascii=False)  # 不以ascii码方式显示
        f.write(info + "\n")

def main(n):
    url = "http://maoyan.com/board/4?offset=" + str((n-1)*10)
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
        "Referer": "http://maoyan.com/board",
    }
    html_text = get_one_page(url, headers)
    for i in get_video_info(html_text):
        print(i)
        save_as_file(i)

if __name__ == '__main__':
    for i in range(1, 11):
        main(i)

 

 

 

 

 

 

 

posted @ 2020-12-12 22:42  Praywu  阅读(242)  评论(0编辑  收藏  举报