Python爬虫基础教程(12)Python爬虫常用模块之re模块(正则表达式)操作及实战 :爬虫程序员の必修咒语:re正则表达式从入门到上头实战指南

当你发现隔壁实习生用三行代码扒光了整个网站的数据,而自己还在Ctrl+C/V时,就知道该来补这节正则表达式开车课了

一、为什么说正则像"前任的朋友圈"?

先来看个灵魂拷问:当你在相亲网站看到这样的自我介绍:「身高175+幽默*有房/车」时,你的大脑是不是自动完成了以下解析:

  • 175+ → 可能178也可能穿鞋180
  • 幽默* → 至少会讲冷笑话
  • 有房/车 → 有房或者有车或者都有

恭喜你!你已经掌握了正则表达式的核心思想——用特定符号描述文本模式。而Python的re模块,就是你把这个技能注入代码的"魔法转换器"。

最近我爬取某搞笑段子网站时,面对这样的HTML片段:

<div class="content">哈哈哈这个段子笑死我了<img src="laugh.png" /></div>

如果用普通字符串方法提取文本,可能要经历find()、切片、replace()的连环酷刑。但用正则只需要:

import re
text = '<div class="content">哈哈哈这个段子笑死我了<img src="laugh.png" /></div>'
result = re.search(r'<div class="content">(.*?)<img', text)
print(result.group(1))  # 输出:哈哈哈这个段子笑死我了

是不是像极了用魔法棒点石成金?接下来就进入我们的正题——re模块开车指南

二、re模块五大神技:从"Hello World"到"Hello 数据"

神技1:match()——像门卫大爷般严格
import re
# 只从字符串开头匹配,像极了检查健康码的门卫
result = re.match(r'Hello', 'Hello World')
print(result.group()) if result else print("不匹配开头")

# 试试这个
result2 = re.match(r'World', 'Hello World')
print(result2)  # 输出:None (因为门卫只看出门第一个人的码)
神技2:search()——像在洗衣机里找袜子
# 扫描整个字符串找第一个匹配项
text = "我的手机号是13812345678,备用是15987654321"
phone = re.search(r'1[3-9]\d{9}', text)
print(phone.group())  # 输出:13812345678 (只找到第一只袜子)
神技3:findall()——像超市扫码枪
# 找出所有匹配项,通通装进购物车
phones = re.findall(r'1[3-9]\d{9}', text)
print(phones)  # ['13812345678', '15987654321'] (全部扫出来!)
神技4:sub()——像美颜滤镜
# 替换匹配文本,一键美颜
dirty_text = "这特么也太坑爹了,我艹"
clean_text = re.sub(r'[特么坑爹艹]', '*', dirty_text)
print(clean_text)  # 这**也太**了,我*
神技5:split()——像切西瓜刀
# 按模式分割字符串
data = "张三|25岁|程序员;李四|30岁|产品经理"
items = re.split(r'[|;]', data)
print(items)  # ['张三', '25岁', '程序员', '李四', '30岁', '产品经理']

三、正则语法速成:从懵逼到入门只需5分钟

元字符七龙珠(收藏这张表,面试不慌)

元字符

作用

生活栗子🌰

.

匹配任意单字符

像扑克牌里的癞子

*

前字符0次或多次

像"哈哈哈哈"里的哈

+

前字符1次或多次

像"加油加油"里的加油

?

前字符0次或1次

像"可能吧"里的可能

{n}

前字符n次

像"666"必须是三个6

\d

匹配数字

任何带数字的验证码

\w

匹配字母数字下划线

用户名合法字符

\s

匹配空白符

空格/换行/tab都算

[ ]

字符集合

像选择题的ABCD选项

^

匹配开头

像"首先..."

$

匹配结尾

像"谢谢大家"

实战中的黄金组合
# 匹配邮箱地址
email_pattern = r'\w+@\w+\.\w+'

# 匹配中文(爬虫必备)
chinese_pattern = r'[\u4e00-\u9fa5]+'

# 匹配URL(抓链接神器)
url_pattern = r'https?://[^\s]+'

四、实战演练:表情包战争的秘密数据

现在我们来实战爬取某个表情包网站的图片链接(已脱敏处理):

import re
import requests

def grab_meme_urls(html_content):
    """从网页HTML中提取表情包图片URL"""
    # 观察发现图片URL规律:都以jpg/png/gif结尾,被引号包围
    pattern = r'https?://[^"\']+\.(?:jpg|png|gif)'
    urls = re.findall(pattern, html_content, re.IGNORECASE)
    return list(set(urls))  # 去重

# 模拟网页内容
sample_html = """
<div class="meme-list">
    <img src="https://img.example.com/笑死我了.jpg" alt="搞笑表情">
    <div data-src="http://cdn.example.com/doge.png"></div>
    <a href="https://img.example.com/awesome.gif">动态表情</a>
</div>
"""

urls = grab_meme_urls(sample_html)
for i, url in enumerate(urls, 1):
    print(f"表情包{i}: {url}")

输出结果:

表情包1: https://img.example.com/笑死我了.jpg
表情包2: http://cdn.example.com/doge.png  
表情包3: https://img.example.com/awesome.gif

五、爬虫老司机的避坑指南

坑1:贪婪匹配——像吃货见到自助餐
# 错误示范(贪婪模式)
html = '<div>重要内容1</div><div>重要内容2</div>'
greedy_result = re.findall(r'<div>.*</div>', html)
print(greedy_result)  # ['<div>重要内容1</div><div>重要内容2</div>'] 全吞了!

# 正确姿势(非贪婪模式)
lazy_result = re.findall(r'<div>.*?</div>', html)
print(lazy_result)  # ['<div>重要内容1</div>', '<div>重要内容2</div>'] 细嚼慢咽
坑2:不处理异常——像开车不系安全带
def safe_extract(text, pattern):
    """安全提取函数,带异常处理"""
    try:
        matches = re.findall(pattern, text)
        return matches if matches else []
    except re.error as e:
        print(f"正则表达式有误:{e}")
        return []

# 测试错误正则
result = safe_extract("test", r'[a-z')  # 缺少闭合括号
坑3:忽视编译优化——像每次现拆快递
# 低速版(每次现拆)
for i in range(10000):
    re.findall(r'\d+', text)

# 高速版(先拆包装)
pattern = re.compile(r'\d+')  # 预编译
for i in range(10000):
    pattern.findall(text)  # 速度提升明显

六、综合实战:构建知乎段子爬虫

让我们用刚学的技能构建一个完整的段子采集器:

import re
import requests
from time import sleep

class JokeCrawler:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
    
    def clean_html(self, text):
        """清理HTML标签,提取纯文本"""
        # 移除script和style标签
        text = re.sub(r'<script.*?</script>', '', text, flags=re.DOTALL)
        text = re.sub(r'<style.*?</style>', '', text, flags=re.DOTALL)
        # 移除其他HTML标签
        text = re.sub(r'<[^>]+>', '', text)
        # 合并多个空白字符
        text = re.sub(r'\s+', ' ', text)
        return text.strip()
    
    def extract_jokes(self, html):
        """从HTML中提取段子内容"""
        # 假设段子都在class包含"content"的div中
        pattern = r'<div class="[^"]*content[^"]*">(.*?)</div>'
        raw_jokes = re.findall(pattern, html, re.DOTALL)
        
        cleaned_jokes = []
        for joke in raw_jokes:
            clean_joke = self.clean_html(joke)
            if len(clean_joke) > 10:  # 过滤太短的内容
                cleaned_jokes.append(clean_joke)
        
        return cleaned_jokes
    
    def crawl(self, url):
        """执行爬取任务"""
        try:
            response = self.session.get(url, timeout=10)
            response.encoding = 'utf-8'
            
            if response.status_code == 200:
                jokes = self.extract_jokes(response.text)
                return jokes
            else:
                print(f"请求失败,状态码:{response.status_code}")
                return []
        except Exception as e:
            print(f"爬取出错:{e}")
            return []

# 使用示例
if __name__ == "__main__":
    crawler = JokeCrawler()
    
    # 模拟目标网站(请替换为实际可访问的测试网站)
    test_url = "https://www.zhihu.com/question/xxxxxx"  # 请使用实际URL
    
    jokes = crawler.crawl(test_url)
    
    print(f"共采集到 {len(jokes)} 个段子:")
    for i, joke in enumerate(jokes, 1):
        print(f"\n段子{i}:{joke[:50]}...")  # 只打印前50字

七、正则表达式的"最佳拍档"

虽然正则很强大,但在实际爬虫项目中,我们通常会搭配其他工具:

  1. BeautifulSoup + 正则:先用BeautifulSoup定位大区块,再用正则精细提取
  2. XPath + 正则:XPath处理结构,正则处理文本内容
  3. json + 正则:接口返回JSON时,先用json解析,再用正则处理特定字段
# 组合技示范
from bs4 import BeautifulSoup
import re

def advanced_extract(html):
    soup = BeautifulSoup(html, 'lxml')
    # 先用BeautifulSoup找到目标区域
    target_div = soup.find('div', class_='user-info')
    if target_div:
        # 再用正则提取具体信息
        text = target_div.get_text()
        phone = re.search(r'1[3-9]\d{9}', text)
        email = re.search(r'\w+@\w+\.\w+', text)
        return phone.group() if phone else None, email.group() if email else None
    return None, None

八、结语:正则不是万能,没有正则万万不能

记住这个爬虫界的真理:能用BeautifulSoup/XPath解决的,不要用正则;不得不用正则的,一定要写注释!

正则表达式就像编程界的"瑞士军刀"——不是最专业的工具,但关键时刻总能救急。掌握它之后,你会发现文本处理的世界突然变得清晰起来,那些曾经让你头疼的数据提取任务,现在都能优雅解决。

最后送大家一句话:"正则写得好,下班回家早;模式匹配6,bug不再有"

(完)


附:正则表达式调试技巧

  1. 使用在线工具测试:regex101.com
  2. 分步调试:复杂正则拆解成小段测试
  3. 多多注释:用(?#注释)语法在正则中添加说明
  4. 单元测试:为正则函数编写测试用例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

值引力

持续创作,多谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值