学习笔记 -- Python爬虫 - 数据解析之正则表达式


#内容为视频笔记及个人理解,若有错误还望各位大佬指正

正则表达式


正则表达式可以帮助我们得到网页当中源码的部分信息, 使用正则表达可以锁定到我们想要的位置。

单字符

字符作用
.除换行符以外的所有字符
[][aoe] [a-z] 匹配集合中任意一个字符, 范围内匹配
\d数字 [0-9]
\D非数字的所有字符
\w字母、数字、下划线
\W除 字母、数字、下划线 以外的所有字符
\s所有的空白字符, 空格, 制表符, 换页符等等(\n, \t, \f, \r, \v)
\S非空白的所有字符

多字符

字符作用
*匹配前一个字符任意多次
+匹配前一个字符至少一次或多次
?匹配前一个字符可有可无
{m}匹配前一个字符规定次数
{m, }匹配前一个字符至少次
{m, n}匹配前一个字符m-n次
  • * 示例

     .* 
    匹配前一个字符, 任意多个除换行符以外的字符
    
  • + 示例

     .+
    匹配前一个字符, 一个或以上的除换行符以外的字符
    
  • ? 示例

     1?
    匹配前一个字符 1, 1 可有可无, 若有只能有一个
    
  • {} 示例

    1{2}
    匹配连续出现两次的前一个字符 1
    
  • {m,} 示例

    1{5,}
    匹配前一个字符 1, 至少出现5次或以上
    
  • {m,n} 示例

    1{5,10}
    匹配前一个字符 1, 出现次数不得小于5 不得大于10
    

开头和结尾

字符作用
^匹配开头的字符
$匹配末尾的字符
[^1-3]匹配不包含 1 或 2 或 3 的字符
  • ^ 示例

    ^[0-9]
    匹配开头以 0-9之间 的字符串
    
  • $ 示例

    [a-z]$
    匹配结尾以 a-z之间 的字符串
    
  • [^] 示例

    [^0-9]
    不匹配 0-9之间 的字符
    

匹配分组 “|”

字符作用
|匹配 | 两侧的表达式 (或)
(ab)将括号中的字符作为一个分组来匹配
\引用分组内的内容
?P<name>给分组起别名, ?P<>为格式, <>内为名称
(?P=name)引用别名, (?P= )为格式, =后为名称
  • | 示例

    ^[0-9]?[0-9]$ | ^100$
    匹配 开头为 0-9 的第一位数字(可有可无), 第二位以 0-9 结尾 或 100
    
  • () 示例

    \w{4,20}@(163\|126\|qq)\.com$
    匹配地址为 163 或 126 或 qq
    
  • \ 示例

    <([a-zA-Z]+)><([a-zA-Z0-9]+)>.*</\2></\1>
    \1 指向 第一个分组([a-zA-Z]+)
    \2 指向 第二个分组([a-zA-Z0-9]+)
    
    引用符号 并非引用分组的表达式 而是具体的内容是否一致
    例:
    	<html><h1>text</h1></html> 上述表达式成立
    	<htmla><h1>text</h1></html> 上述表达式不成立
    	<html> 和 <htmla> 都满足分组 ([a-zA-Z]+) 
    	但 分组1 的内容和 引用\1 的内容不一致 判定不等
    	
    在 Python 中一个 \ 是转义字符, 所以要使用 \\ 来转义转义字符
    
  • ?P<>(?P=) 示例

    <(?P<name1>[a-zA-Z]+)><(?P<name2>[a-zA-Z0-9]+)>.*</(?P=name2)></(?P=name1)>
    
    起别名时在分组的开头写入, 引用时要注意有一对括号
    好比在 ?P 中存储值, 在引用时使用 (?P=) 来获取相应的值
    

注: 在使用 re 模块的 match() 时,如果表达式中含有分组符号 "()" ,返回的对象在调用group()时, 可以传入一个区号来获取指定括号内的内容
import re

# (163|126|sina)的区号为 1 , (com|cn|net)的区号为 2
match_obj = re.match("\w{4,20}@(163|126|sina).(com|cn|net)$", "iterma@sina.cn")
if match_obj:
    print(match_obj.group())
    print(match_obj.group(1)) # 获取区号 1 的内容
    print(match_obj.group(2)) # 获取区号 2 的内容
else:
    print("匹配失败")

结果:

iterma@sina.cn
sina
cn

贪婪和非贪婪模式


在大多数语言中默认的情况下都是贪婪模式, 只有极少数的默认情况为非贪婪模式

  • 贪婪模式 : 在满足条件的情况下, 尽可能多的取值
  • 非贪婪模式 : 在满足条件的情况下, 尽可能少的取值
import re

str_ = "aaa123456"
exp = "aaa\\d+"

result = re.search(exp, str_)
if result:
    print(result.group())
else:
    print("匹配失败")

结果: aaa123456

\d+ 可以取多个数字的值, 这是贪婪模式的取值模式

import re

str_ = "aaa123456"
exp = "aaa\\d+?"

result = re.search(exp, str_)
if result:
    print(result.group())

else:
    print("匹配失败")

结果: aaa1

当我们在 \d+ 后面添加一个 ? 时, 取值不再是取最多的值, 而是取最少的值, 因为 + 号的最小范围是 1 所有只显示了一个数字, 当然如果非贪婪模式只是为了取一个值, 那就显的用处并不大了


非贪婪模式, 在我们需要爬取网页的图片时经常被用到

图片文件经常保存在 src 的标签中, 因此我们只需要把表达式定位在 src 内部的链接地址上就可以获取这个图片的地址(不要忘了在前面加上 https: )

import re

str_ = 'src="//pic.qiushibaike.com/system/pictures/12368/123686643/medium/S40V6I0VQFEN8SFV.jpg" alt="糗事#123686643" class="illustration" width="100%" height="auto">'
exp = 'src="(.*?)" '

result = re.search(exp, str_)
if result:
    print("https:" + result.group(1))

else:
    print("匹配失败")

结果: https://pic.qiushibaike.com/system/pictures/12368/123686643/medium/S40V6I0VQFEN8SFV.jpg

完整的链接地址:
https://pic.qiushibaike.com/system/pictures/12368/123686643/medium/S40V6I0VQFEN8SFV.jpg (在edge上可以不加 https , 但是使用 chrome 无法直接打开, 因此为了保险还是加上了)



src="(.*?)"

我自己刚开始也搞不懂这段表达式, 所以现在趁热打铁, 希望也能帮助到别人

1. src=" 限制了源码中的前5个字符
2. 末尾的 " 号出现的第一次的位置之间才是我们想要的字符串, 但是如果不使用贪婪模式, " 号会直接匹配到最末尾的 " 号
3. (.*?)" 在末尾为 " 号的条件下, 我们取中间最少的值, 即头部的 " 号 和 末尾的第一个 " 号之间的值



re模块


re.match(pattern, string, flag=0)


使用正则表达式从头匹配一个符合规则的字符串, 从起始位置开始匹配, 匹配成功返回一个 对象 , 未匹配成功返回 None

  • pattern : 正则表达式
  • string : 要匹配的字符串
  • flag : 匹配模式

这个方法并不是完全匹配, 当pattern结束时若string还有剩余字符, 仍然视为成功, 想要完全匹配, 可以在表达式末尾加上边界匹配符 “$”


match() 方法一旦匹配成功, 就是一个match object对象, 而match object对象有以下方法:

  • group() 返回被 RE 匹配的字符串
  • start() 返回匹配开始的位置
  • end() 返回匹配结束的位置
  • span() 返回一个元组包含匹配 (开始, 结束) 的位置
import re

match_obj = re.match("[iterma]{4,20}@163\.com$", "iterma@163.com")
print(match_obj.group())
print(match_obj.span())

输出结果:
iterma@163.com
(0, 14)


re.search(pattern, string, flag=0)


re.search()函数会在字符串内以查找模式匹配只要找到第一个匹配然后返回, 如果字符串中没有匹配, 返回None

search 和 match 的区别

import re

exp = "<(?P<name1>[a-zA-Z]+)><(?P<name2>[a-zA-Z0-9]+)>text</(?P=name2)></(?P=name1)>"
str_ = "<html><h1>text</h1></html>"
# 第一次测试
match_obj = re.match(exp, str_)
if match_obj:
    print(match_obj.group())
    print(match_obj.span())
else:
    print("匹配失败")

# 第二次测试
match_obj = re.match(exp, "_"+str_)
if match_obj:
    print(match_obj.group())
    print(match_obj.span())
else:
    print("匹配失败")
# 第三次测试
match_obj = re.search(exp, "_"+str_)
if match_obj:
    print(match_obj.group())
    print(match_obj.span())
else:
    print("匹配失败")

结果:
	# 第一次结果
	<html><h1>text</h1></html>
	(0, 26)
	# 第二次结果
	匹配失败
	# 第三次结果
	<html><h1>text</h1></html>
	(1, 27)

对比 span() 可以看出字符串只要包含表达式 那么search就会返回结果, 而match的表达式必须在字符串的开头



findall(pattern, string, flag=0)


re.findall() 可以查找字符串中所有满足表达式的结果, 并保存在一个列表中返回

import re

exp = "\\d+"
str_ = "查找次数:2099, 阅读次数:2094, 评论次数:1233, 转发次数:500"
match_obj = re.findall(exp, str_)
if match_obj:
    print(match_obj)
else:
    print("匹配失败")

结果:
	['2099', '2094', '1233', '500']

re.findall() 函数返回的是一个列表!!!



sub(pattern, repl, string, count, flag=0)


re.sub() 与字符串中的 replace() 类似, 查找满足表达式的内容, 并替换为新的内容, 返回一个新的字符串

  • repl : 要替换的内容
  • count : 要替换的次数

import re

exp = "\\d+"
str_ = "查找次数:2099, 阅读次数:2094, 评论次数:1233, 转发次数:500"
match_obj = re.sub(pattern=exp, repl="10000", string=str_, count=3)
if match_obj:
    print(match_obj)
else:
    print("匹配失败")

结果:
	查找次数:10000, 阅读次数:10000, 评论次数:10000, 转发次数:500

re.sub() 函数返回的是一个新的字符串!!!



split(pattern, string, maxsplit, flag=0)


re.split() 与字符串的 split() 类似, 以满足表达式的内容来拆分字符串, 并返回一个新的字符串

  • maxsplit : 拆分的最大次数

import re

str_ = "sing:我是一只小青龙,小青龙,我有一个小秘密,小秘密"
exp = ":|,"
match_obj = re.split(exp, str_, maxsplit=3)
if match_obj:
    print(match_obj)
else:
    print("匹配失败")

结果:
	['sing', '我是一只小青龙', '小青龙', '我有一个小秘密,小秘密']

re.split() 返回的是一个列表!!!




#第一次学爬虫, 此文章内容为个人理解, 希望不要误导跟我一样初入爬虫的朋友们

正则表达式 (案例)


要求: 爬取糗事百科的图片

import requests
import re
import os

def get_pic(num_):
    for num in range(1, num_+ 1):
        url = "https://www.qiushibaike.com/imgrank/page/%d" % num
        header = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Firefox/38.0', 
        }
        response_obj = requests.get(url=url, headers=header)
        response_txt = response_obj.text

        exp = '<img src="(.*?)" alt="(.*?)"' # 表达式获取图片地址和图片的名称
        url_list = re.findall(exp, response_txt)

        try:
            os.mkdir("pic_items")
        except:
            pass

        head = "https:"
        for i in url_list:
            if ("糗事" in i[1]):    # 过滤不相关的图片
                new = head + i[0]  # 完整的图片地址
                pic_url = requests.get(url=new, headers=header)
                pic = pic_url.content
                with open("pic_items\\%s.jpg" % i[1], "wb") as pc:
                    pc.write(pic)

def main(num_=1):
    get_pic(num_)

if __name__ == "__main__":
    main(2)



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值