猫眼电影TOP100爬虫:Requests+正则表达式实战

引言

如果你正在学习Python爬虫,那么猫眼电影TOP100榜单一定是绕不开的经典案例。它几乎出现在每一本爬虫入门教材和每一篇爬虫教程中------原因很简单:页面结构清晰、数据规整、反爬策略温和但不失代表性,非常适合初学者理解HTTP请求、HTML解析和反爬应对的完整流程。

然而,理想很丰满,现实往往会在你第一次运行代码时给你一个下马威。当你兴致勃勃地打开浏览器开发者工具,用几行Python代码发起请求时,返回的可能不是预想中的HTML页面,而是一个冷冰冰的验证页面或空白响应。猫眼平台设置了多道反爬虫防线,如果请求头配置不当,服务器会直接拒绝提供正常数据。

本文将从零开始,带你完整走一遍"Requests+正则表达式"爬取猫眼电影TOP100的全过程------从URL分析、请求伪装、正则提取,到反爬应对和隧道代理集成,一篇搞定。

一、URL分析:找到数据入口

1.1 目标站点

猫眼电影TOP100榜单的入口地址是:

复制代码
https://maoyan.com/board/4

打开这个页面,你会看到排名1到10的电影信息------片名、主演、上映时间、评分等。但榜单上明明有100部电影,为什么只显示了10部?

1.2 分页规律

滚动到页面底部,你会发现一个分页导航。点击第2页,观察浏览器地址栏的变化:

复制代码
第1页:https://maoyan.com/board/4
第2页:https://maoyan.com/board/4?offset=10
第3页:https://maoyan.com/board/4?offset=20

规律一目了然:offset参数控制了页面的偏移量。当offset=0时,显示第1-10名;offset=10时,显示第11-20名;以此类推。每页10部电影,总共10页。

所以,要获取完整的TOP100数据,只需要循环请求10次,每次将offset依次设置为0、10、20......90即可。

1.3 页面类型判断

在写爬虫之前,必须先确认页面是服务端渲染(SSR) 还是客户端渲染(CSR) 。打开开发者工具的Network选项卡,刷新页面,查看返回的HTML源码------如果电影信息直接出现在HTML中,说明是服务端渲染,用requests就能拿到数据;如果HTML只有空骨架、数据通过额外的AJAX请求加载,就需要动用Selenium或分析API接口。

猫眼TOP100榜单属于前者------页面是服务端渲染的,所有电影信息都直接包含在HTML源码中。这意味着我们完全可以用requests获取页面,然后用正则表达式从HTML中提取数据,无需动用浏览器自动化工具。

二、Requests请求:伪装成"真人"

2.1 为什么需要伪装?

猫眼平台的第一道反爬虫防线是User-Agent检测 。服务器会检查请求头中的User-Agent字段------如果它看起来不像一个真实的浏览器,服务器就会拒绝返回正常数据。

一个裸奔的requests.get()请求,其默认的User-Agent通常是类似python-requests/2.28.2这样的字符串。这等于直接告诉服务器:"我是爬虫,请拦截我。"

2.2 构造请求头

解决方案很简单------从真实浏览器中复制一个User-Agent,放进请求头里:

复制代码
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}

除了User-Agent,还可以补充Referer、Accept等头部信息,让请求看起来更像一个真实的浏览器访问:

复制代码
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Referer': 'https://www.maoyan.com/',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Connection': 'keep-alive'
}

2.3 发送请求

有了请求头,就可以发送请求了:

复制代码
import requests

def get_one_page(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.text
    return None

2.4 请求频率控制

猫眼平台会检测规律性请求------如果每2秒准时发一次请求,风控系统会标记这个IP为"爬虫模式"。应对策略是在请求之间加入随机延迟,模拟人类浏览的不确定性:

复制代码
import time
import random

time.sleep(random.uniform(1, 3))  # 1到3秒随机延迟

三、正则表达式:从HTML中"淘金"

3.1 为什么选正则表达式?

在解析HTML的诸多工具中------BeautifulSoup、XPath、PyQuery、正则表达式------为什么偏偏选正则?原因很简单:我们还没有系统学习HTML解析库,而正则表达式是Python内置的、无需额外安装的工具。对于结构规整的页面,正则表达式完全够用,而且执行效率更高。

3.2 查看真实源码

重要提醒 :不要直接在Elements选项卡中查看源码------那里的HTML可能经过JavaScript操作,与原始请求返回的内容不同。正确的做法是在Network选项卡中找到页面的主请求,查看Response内容。

3.3 分析HTML结构

在Network中查看源码,你会发现每部电影的信息都包裹在一个<dd>标签中:

复制代码
<dd>
    <i class="board-index board-index-1">1</i>
    <a href="/films/..." title="霸王别姬" class="image-link" data-act="boarditem-click">
        <img src="..." alt="" class="board-img" />
    </a>
    <div class="movie-item-info">
        <p class="name"><a href="/films/..." title="霸王别姬" data-act="boarditem-click">霸王别姬</a></p>
        <p class="star">主演:张国荣,张丰毅,巩俐</p>
        <p class="releasetime">上映时间:1993-01-01(中国大陆)</p>
    </div>
    <div class="movie-item-number score-num">
        <p class="score"><i class="integer">9.</i><i class="fraction">6</i></p>
    </div>
</dd>

3.4 编写正则表达式

根据上面的HTML结构,我们可以逐一写出每个字段的正则表达式:

(1)排名 :在class="board-index"<i>标签中

复制代码
pattern_rank = re.compile(r'<i class="board-index.*?>(.*?)</i>', re.S)

(2)图片地址 :在<img>标签的data-src属性中

复制代码
pattern_img = re.compile(r'<img.*?data-src="(.*?)"', re.S)

(3)电影名称 :在class="name"<p>标签中的<a>标签内

复制代码
pattern_name = re.compile(r'<p class="name"><a.*?>(.*?)</a></p>', re.S)

(4)主演信息 :在class="star"<p>标签中

复制代码
pattern_star = re.compile(r'<p class="star">(.*?)</p>', re.S)

(5)上映时间 :在class="releasetime"<p>标签中

复制代码
pattern_time = re.compile(r'<p class="releasetime">(.*?)</p>', re.S)

(6)评分 :由integerfraction两部分组成

复制代码
pattern_score = re.compile(r'<i class="integer">(.*?)</i>.*?<i class="fraction">(.*?)</i>', re.S)

3.5 将正则组合在一起

更高效的做法是一次性匹配整个<dd>区块,然后从中提取各个字段:

复制代码
import re

pattern = re.compile(
    r'<dd>.*?board-index.*?>(.*?)</i>.*?'
    r'data-src="(.*?)".*?'
    r'name.*?>(.*?)</a>.*?'
    r'star.*?>(.*?)</p>.*?'
    r'releasetime.*?>(.*?)</p>.*?'
    r'integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>',
    re.S
)

items = re.findall(pattern, html)

这样一次正则匹配就能拿到排名、图片、名称、主演、上映时间、整数部分和小数部分------评分由整数部分和小数部分拼接而成。

四、完整代码实现

4.1 基础版

将以上所有环节组合起来,就得到了一个完整的猫眼TOP100爬虫:

复制代码
import requests
import re
import time
import random
import json

def get_one_page(url):
    """获取单页HTML"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Referer': 'https://www.maoyan.com/',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
    }
    try:
        response = requests.get(url, headers=headers, timeout=10)
        if response.status_code == 200:
            return response.text
    except Exception as e:
        print(f'请求异常: {e}')
    return None

def parse_one_page(html):
    """解析单页HTML,提取电影信息"""
    pattern = re.compile(
        r'<dd>.*?board-index.*?>(.*?)</i>.*?'
        r'data-src="(.*?)".*?'
        r'name.*?>(.*?)</a>.*?'
        r'star.*?>(.*?)</p>.*?'
        r'releasetime.*?>(.*?)</p>.*?'
        r'integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>',
        re.S
    )
    items = re.findall(pattern, html)
    
    for item in items:
        yield {
            'rank': item[0],
            'image': item[1],
            'title': item[2].strip(),
            'star': item[3].strip() if item[3] else '',
            'release_time': item[4].strip() if item[4] else '',
            'score': item[5] + item[6]  # 拼接整数部分和小数部分
        }

def save_to_file(data, filename='maoyan_top100.txt'):
    """保存数据到文件"""
    with open(filename, 'w', encoding='utf-8') as f:
        for item in data:
            f.write(json.dumps(item, ensure_ascii=False) + '\n')
    print(f'数据已保存到 {filename}')

def main():
    """主函数"""
    all_movies = []
    base_url = 'https://maoyan.com/board/4'
    
    for offset in range(0, 100, 10):
        url = f'{base_url}?offset={offset}'
        print(f'正在爬取第 {offset//10 + 1} 页 (offset={offset})...')
        
        html = get_one_page(url)
        if not html:
            print(f'第 {offset//10 + 1} 页获取失败')
            continue
        
        movies = list(parse_one_page(html))
        all_movies.extend(movies)
        print(f'第 {offset//10 + 1} 页获取 {len(movies)} 部电影,累计 {len(all_movies)} 部')
        
        # 随机延迟,模拟人类浏览行为
        time.sleep(random.uniform(1, 3))
    
    save_to_file(all_movies)
    print(f'爬取完成!共 {len(all_movies)} 部电影')

if __name__ == '__main__':
    main()

4.2 代码结构解析

这段代码遵循了爬虫的经典三层架构:

模块 函数 职责
HTML下载器 get_one_page() 发送HTTP请求,获取页面源码
HTML解析器 parse_one_page() 用正则表达式提取结构化数据
数据存储器 save_to_file() 将数据持久化到文件

4.3 运行效果

运行上述代码,你会看到类似如下的输出:

复制代码
正在爬取第 1 页 (offset=0)...
第 1 页获取 10 部电影,累计 10 部
正在爬取第 2 页 (offset=10)...
第 2 页获取 10 部电影,累计 20 部
...
爬取完成!共 100 部电影
数据已保存到 maoyan_top100.txt

打开maoyan_top100.txt,每一行都是一部电影的JSON数据,包含排名、片名、主演、上映时间、评分等信息。

五、反爬应对与隧道代理

5.1 猫眼的反爬体系

猫眼平台部署了多层次的反爬机制,主要包括:

反爬手段 说明 应对策略
User-Agent检测 检查请求头中的UA字段 伪装真实浏览器UA
请求频率限制 限制同一IP的请求频率 随机延迟、控制速率
IP封禁 高频访问导致IP被封 使用代理IP
字体反爬 评分/票房数据用自定义字体渲染 本案例不涉及

5.2 为什么需要代理?

即使你完美配置了User-Agent和请求延迟,当需要反复运行爬虫或采集规模扩大时,IP封禁依然不可避免。猫眼平台会检测同一IP在短时间内的访问频率,一旦触发阈值,该IP就会被暂时或永久封禁。

传统方案是自建代理IP池------从网上爬取公开的代理IP,检测可用性后保存起来,每次请求轮换使用。但这种方法存在两个核心痛点:

  1. IP质量参差不齐:免费代理资源已被大量滥用,可用性低

  2. 手动维护繁琐:需要持续检测IP有效性,被封后手动更换

5.3 隧道代理:更优雅的解决方案

隧道代理是传统代理的升级方案。你不需要手动维护IP池,只需配置好接入信息。每次发送请求时,隧道代理会自动把流量引导至不同的出口IP------相当于给爬虫配备了一条专属的"IP安全隧道"。

站大爷隧道代理在爬虫场景中有几个突出的优势:

  • 自动切换无感知:每次请求自动更换出口IP,无需手动干预

  • 覆盖范围广泛:覆盖全国99%地域

  • 主备双隧道:持续更新IP池,稳定性有保障

  • 灵活配置:支持精细化的IP轮换周期自定义

  • 多种鉴权模式:支持白名单、用户名密码等多种鉴权方式

5.4 在爬虫中集成站大爷隧道代理

requests中集成隧道代理非常简单:

复制代码
import requests

# 站大爷隧道代理配置
PROXIES = {
    "http": "http://隧道代理地址:端口",
    "https": "https://隧道代理地址:端口"
}

def get_one_page_with_proxy(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    try:
        response = requests.get(
            url, 
            headers=headers, 
            proxies=PROXIES,  # 隧道代理自动切换IP
            timeout=15
        )
        if response.status_code == 200:
            return response.text
        # 遇到403/429,隧道代理自动切换出口IP重试
        if response.status_code in [403, 429]:
            print('请求被拒绝,隧道代理自动切换IP重试...')
            time.sleep(2)
    except Exception as e:
        print(f'请求异常: {e}')
    return None

对于需要精细控制IP轮换的场景,站大爷隧道代理支持通过参数自定义IP更换频率:

复制代码
# 每300秒更换一次IP
PROXIES = {
    "http": "http://隧道代理地址:端口?period=300",
    "https": "https://隧道代理地址:端口?period=300"
}

实际应用中,隧道代理可以将IP封禁率从80%以上降至5%以下,大幅提升采集的稳定性。

六、常见问题与避坑指南

6.1 请求返回空白或验证页面怎么办?

  • 检查是否设置了正确的User-Agent

  • 补充RefererAccept等完整的请求头

  • 确认URL是否正确(注意是maoyan.com而非www.maoyan.com

6.2 正则表达式匹配不到数据怎么办?

  • 不要在Elements中看源码,要去Network中查看原始响应

  • 检查页面结构是否发生了变化------猫眼可能会更新HTML

  • 使用print(html[:500])打印部分源码,肉眼确认结构

6.3 爬取过程中被封IP怎么办?

  • 增加请求间隔,使用随机延迟而非固定间隔

  • 使用站大爷隧道代理自动切换IP

  • 降低并发度,单线程顺序请求

6.4 评分数据出现乱码或特殊字符怎么办?

猫眼可能使用字体反爬 技术保护评分数据------评分数字用自定义字体渲染,直接提取HTML只能看到乱码。本案例中评分以integerfraction两个<i>标签呈现,提取后拼接即可。如果遇到更复杂的字体反爬,需要额外处理字体映射。

6.5 法律与合规提醒

  • 爬取前查看猫眼的robots.txt

  • 控制请求频率,避免对目标服务器造成过大压力

  • 仅将数据用于学习和研究目的,勿用于商业用途

七、总结

本文从猫眼电影TOP100的URL分析入手,完整走了一遍"Requests+正则表达式"爬虫的实战流程:

环节 核心知识点 关键代码
URL分析 分页规律、offset参数 range(0, 100, 10)
请求发送 User-Agent伪装、请求头构造 requests.get(url, headers=headers)
HTML解析 正则表达式、非贪婪匹配 re.compile(r'<dd>.*?...', re.S)
数据提取 排名、片名、主演、时间、评分 re.findall(pattern, html)
反爬应对 随机延迟、代理IP time.sleep(random.uniform(1, 3))
IP封禁 站大爷隧道代理 proxies={'http': '隧道地址:端口'}

技术选型速览 :猫眼TOP100是服务端渲染的静态页面,推荐"requests + 正则表达式 + 站大爷隧道代理"的组合方案------请求高效、解析轻量、IP安全。

猫眼电影TOP100是爬虫入门的经典案例------页面规整、反爬温和但不失代表性、数据字段丰富。通过这个项目,你可以完整地体验到爬虫的请求→解析→存储 全流程,同时理解User-Agent伪装、请求频率控制、代理IP等核心反爬应对策略。

正则表达式虽然写起来略显繁琐,但它是每个开发者都应该掌握的基本功。当你面对一个结构规整的页面时,正则表达式依然是最轻量、最高效的解析工具之一。

希望本文能帮助你在爬虫学习的道路上迈出坚实的一步。当你成功跑通代码、看到100部电影数据整整齐齐地出现在文件里时,那种成就感------就是编程最纯粹的魅力所在。

相关推荐
小小龙学IT4 小时前
C++ 正则表达式完全指南:从 std::regex 实战到 RE2 引擎原理(NFA/DFA/回溯陷阱)
c++·正则表达式
MgArcher6 小时前
抖音a_bogus参数逆向实战:JSVMP环境模拟与Hook技术(2025最新)
javascript·爬虫·python
MgArcher6 小时前
极验GT4滑块验证码全链路逆向:w参数生成与验证流程(2025最新)
爬虫
circuitsosk7 小时前
AI输出的“质检员”:构建智能体质量评估、异常检测与人工兜底的三层防线
人工智能·python·microsoft·正则表达式·langchain
Livia要学习13 小时前
Python正则表达式
正则表达式
亿牛云爬虫专家14 小时前
聊聊数据的“冷热分离“:如何对历史爬虫数据进行合理的归档与压缩存储?
爬虫·爬虫代理·架构设计·隧道代理·大数据处理·压缩存储·数据分层存放
袁袁袁袁满15 小时前
Python爬虫实战:利用巨量IP获取跨境电商数据
爬虫·python·网络爬虫·爬虫实战·跨境电商·电商爬虫
拿本唠嗑AI研究16 小时前
现代前端架构爬虫指南:从静态页面到 React/Vue 单页应用
前端·爬虫·架构
一个处女座的程序猿1 天前
Crawler之Tool:MediaCrawler的简介、安装和使用方法、案例应用之详细攻略
爬虫·mediacrawler