当你发现隔壁实习生用三行代码扒光了整个网站的数据,而自己还在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字
七、正则表达式的"最佳拍档"
虽然正则很强大,但在实际爬虫项目中,我们通常会搭配其他工具:
- BeautifulSoup + 正则:先用BeautifulSoup定位大区块,再用正则精细提取
- XPath + 正则:XPath处理结构,正则处理文本内容
- 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不再有"!
(完)
附:正则表达式调试技巧
- 使用在线工具测试:regex101.com
- 分步调试:复杂正则拆解成小段测试
- 多多注释:用(?#注释)语法在正则中添加说明
- 单元测试:为正则函数编写测试用例

被折叠的 条评论
为什么被折叠?



