一、案例背景与目标
豆瓣电影 Top250 是国内权威的电影评分榜单之一,包含电影名称、评分、评价人数、导演、主演、上映年份、国家 / 地区、类型等关键信息。本案例将使用 Python 编写爬虫,实现以下目标:
- 自动请求豆瓣电影 Top250 的 10 个分页(每页 25 部电影);
- 解析页面 HTML 结构,提取每部电影的 8 项核心信息;
- 数据清洗(处理异常字符、统一格式);
- 将最终数据保存到 Excel 文件,方便后续分析。
二、技术栈选择
本案例使用轻量级且易上手的技术组合,适合爬虫初学者:
工具 / 库 | 作用 |
---|---|
requests | 发送 HTTP 请求,获取网页源代码 |
BeautifulSoup4 | 解析 HTML 文档,提取目标数据(非结构化→结构化) |
pandas | 数据清洗、整理,并将数据写入 Excel |
time | 控制请求间隔,避免触发网站反爬机制 |
user-agent | 伪装浏览器请求头,绕过基础反爬 |
三、完整代码实现
# 1. 导入所需库
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time# 2. 定义核心配置
# 豆瓣电影 Top250 分页 URL 规律:start 参数从 0 开始,每次加 25(0、25、50...225)
BASE_URL = "https://movie.douban.com/top250?start={}&filter="
# 伪装浏览器请求头(避免被识别为爬虫)
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
}
# 存储所有电影数据的列表
movie_list = []# 3. 定义页面解析函数(提取单页电影数据)
def parse_movie_page(html):# 初始化 BeautifulSoup 解析器(指定 lxml 解析器,效率更高)soup = BeautifulSoup(html, "lxml")# 定位所有电影项的父容器(每个 li 对应一部电影)movie_items = soup.find_all("li", class_="item")for item in movie_items:# 3.1 提取电影名称(默认取第一个中文名称)title_tag = item.find("span", class_="title")movie_name = title_tag.get_text(strip=True) if title_tag else "未知名称"# 3.2 提取评分rating_tag = item.find("span", class_="rating_num")rating = rating_tag.get_text(strip=True) if rating_tag else "0.0"# 3.3 提取评价人数(处理格式,如 "123.4万人评价" → "1234000")comment_tag = item.find("span", text=lambda x: x and "人评价" in x)comment_count = comment_tag.get_text(strip=True).replace("人评价", "").replace("万", "0000") if comment_tag else "0"# 3.4 提取导演和主演(格式:"导演: 张艺谋 主演: 沈腾, 马丽")info_tag = item.find("div", class_="bd").find("p", class_="")info_text = info_tag.get_text(strip=True).split("\n") if info_tag else ["", ""]director_actor = info_text[0].strip() if len(info_text) > 0 else "未知信息"# 3.5 提取上映年份、国家/地区、类型(格式:"2023 / 中国大陆 / 喜剧, 剧情")detail_text = info_text[1].strip() if len(info_text) > 1 else "未知 / 未知 / 未知"detail_list = detail_text.split(" / ")release_year = detail_list[0].strip() if len(detail_list) > 0 else "未知年份"country = detail_list[1].strip() if len(detail_list) > 1 else "未知国家"genre = detail_list[2].strip() if len(detail_list) > 2 else "未知类型"# 3.6 提取电影简介(处理可能的空值)quote_tag = item.find("span", class_="inq")intro = quote_tag.get_text(strip=True) if quote_tag else "无简介"# 3.7 将单部电影数据存入字典movie_dict = {"电影名称": movie_name,"评分": rating,"评价人数": comment_count,"导演&主演": director_actor,"上映年份": release_year,"国家/地区": country,"类型": genre,"简介": intro}movie_list.append(movie_dict)# 4. 定义主爬虫函数(循环请求所有分页)
def crawl_douban_top250():# 循环 10 个分页(start=0,25,...,225)for page in range(10):start = page * 25url = BASE_URL.format(start)print(f"正在爬取第 {page+1} 页,URL:{url}")try:# 4.1 发送 GET 请求(添加超时控制,避免无限等待)response = requests.get(url, headers=HEADERS, timeout=10)# 4.2 检查请求是否成功(状态码 200 表示正常)response.raise_for_status() # 若状态码非 200,抛出 HTTPError 异常# 4.3 解析当前页面数据parse_movie_page(response.text)# 4.4 控制请求间隔(1-2 秒),避免给服务器造成压力,降低反爬风险time.sleep(1.5)except requests.exceptions.RequestException as e:print(f"爬取第 {page+1} 页失败,错误原因:{str(e)}")continue # 跳过失败页面,继续爬取下一页# 5. 定义数据保存函数(保存到 Excel)
def save_to_excel():if not movie_list:print("无数据可保存!")return# 5.1 将列表转换为 DataFrame(pandas 数据结构,便于处理)df = pd.DataFrame(movie_list)# 5.2 数据清洗:处理评价人数的数值格式(如 "123.4000" → 1234000)df["评价人数"] = pd.to_numeric(df["评价人数"].str.replace(".", ""), errors="coerce").fillna(0).astype(int)# 5.3 保存到 Excel(index=False 表示不保存行号)excel_path = "豆瓣电影Top250数据.xlsx"df.to_excel(excel_path, index=False, engine="openpyxl")print(f"数据已成功保存到:{excel_path}")print(f"共爬取到 {len(movie_list)} 部电影数据")# 6. 程序入口(执行爬虫流程)
if __name__ == "__main__":print("开始爬取豆瓣电影 Top250 数据...")crawl_douban_top250() # 1. 爬取数据save_to_excel() # 2. 保存数据print("爬取任务完成!")
四、代码解析(关键步骤拆解)
1. 环境准备(安装依赖库)
在运行代码前,需要确保所有必要的 Python 库都已正确安装。这些库分别承担不同的功能:
requests
:用于发送 HTTP 请求,获取网页内容beautifulsoup4
:用于解析 HTML 文档,提取所需数据pandas
:用于数据处理和分析openpyxl
:作为 pandas 的依赖,用于写入 Excel 文件lxml
:作为 BeautifulSoup 的解析器,提供高效的 HTML 解析能力
安装命令:
pip install requests beautifulsoup4 pandas openpyxl lxml
提示:如果是在国内网络环境,建议使用国内镜像源加速安装,例如:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 pandas openpyxl lxml
2. 反爬机制规避(核心细节)
网络爬虫需要尊重目标网站的规则,同时也要采取适当措施避免被网站识别并封锁。本代码中采用了多种反爬策略:
2.1 伪装请求头
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
}
这是最基础也最重要的反爬措施。网站服务器通过User-Agent
字段识别访问者身份,默认情况下,requests
库的请求头会显示为 "python-requests/xx.x.x",很容易被识别为爬虫。通过设置一个真实的浏览器User-Agent
,可以模拟正常用户的浏览器访问。
你可以通过访问https://httpbin.org/get查看自己浏览器的User-Agent
并替换。
2.2 控制请求频率
time.sleep(1.5)
这行代码的作用是在爬取完一页后暂停 1.5 秒再继续。短时间内发送大量请求是爬虫最明显的特征之一,通过添加合理的延迟,可以模拟人类浏览网页的行为,降低被网站识别的概率。
延迟时间可以根据实际情况调整,一般建议在 1-3 秒之间。对于反爬严格的网站,可能需要更长的延迟。
2.3 异常处理机制
try:# 发送请求的代码
except requests.exceptions.RequestException as e:print(f"爬取第 {page+1} 页失败,错误原因:{str(e)}")continue
这段异常处理代码可以捕获所有与请求相关的异常,包括网络连接错误、超时、HTTP 错误状态码等。当某一页爬取失败时,程序不会崩溃,而是会打印错误信息并继续爬取下一页,保证了程序的健壮性。
response.raise_for_status()
方法会在 HTTP 请求返回错误状态码(4xx 或 5xx)时抛出异常,让我们能够及时发现并处理请求错误。
3. HTML 解析逻辑(如何定位数据)
解析 HTML 是爬虫的核心步骤,需要仔细分析网页结构,找到目标数据所在的位置。我们可以通过浏览器的开发者工具(F12)来查看网页的 HTML 结构。
3.1 分析网页结构
豆瓣电影 Top250 的页面结构具有一定的规律性:
- 所有电影条目都包含在
<ul class="grid_view">
标签中 - 每个电影条目对应一个
<li class="item">
标签 - 每个条目中包含了电影的各种信息:名称、评分、评价人数等
3.2 提取电影列表
movie_items = soup.find_all("li", class_="item")
这行代码使用find_all
方法查找所有class
为 "item" 的li
标签,每个标签对应一部电影的信息。返回的movie_items
是一个列表,包含了当前页面所有电影的信息。
3.3 提取单个电影信息
3.3.1 提取电影名称
title_tag = item.find("span", class_="title")
movie_name = title_tag.get_text(strip=True) if title_tag else "未知名称"
电影名称位于class
为 "title" 的span
标签中。get_text(strip=True)
方法用于获取标签内的文本内容,并去除前后的空白字符。通过if title_tag else "未知名称"
的判断,可以处理标签不存在的情况,避免程序出错。
3.3.2 提取评分
rating_tag = item.find("span", class_="rating_num")
rating = rating_tag.get_text(strip=True) if rating_tag else "0.0"
评分信息位于class
为 "rating_num" 的span
标签中。同样使用了条件判断来处理可能的缺失情况。
3.3.3 提取评价人数
comment_tag = item.find("span", text=lambda x: x and "人评价" in x)
comment_count = comment_tag.get_text(strip=True).replace("人评价", "").replace("万", "0000") if comment_tag else "0"
评价人数的提取相对复杂一些,因为它没有特定的class
名称。这里使用了一个 lambda 函数作为筛选条件,查找文本中包含 "人评价" 的span
标签。
提取到文本后,还需要进行处理:
- 去除 "人评价" 字符串
- 将 "万" 转换为 "0000",以便后续转换为数字
3.3.4 提取导演和主演
info_tag = item.find("div", class_="bd").find("p", class_="")
info_text = info_tag.get_text(strip=True).split("\n") if info_tag else ["", ""]
director_actor = info_text[0].strip() if len(info_text) > 0 else "未知信息"
导演和主演信息位于class
为 "bd" 的div
标签下的第一个p
标签中。这里的find("p", class_="")
表示查找没有class
属性的p
标签。
获取文本后,使用split("\n")
按换行符分割,取第一部分作为导演和主演信息。
3.3.5 提取上映年份、国家 / 地区、类型
detail_text = info_text[1].strip() if len(info_text) > 1 else "未知 / 未知 / 未知"
detail_list = detail_text.split(" / ")
release_year = detail_list[0].strip() if len(detail_list) > 0 else "未知年份"
country = detail_list[1].strip() if len(detail_list) > 1 else "未知国家"
genre = detail_list[2].strip() if len(detail_list) > 2 else "未知类型"
这部分信息位于上一步中分割得到的info_text
的第二部分,格式为 "年份 / 国家 / 地区 / 类型"。我们使用split(" / ")
按 "/" 分割,得到一个包含三个元素的列表,分别对应年份、国家 / 地区和类型。
每个字段都添加了条件判断,以处理可能的缺失情况,保证程序的稳定性。
3.3.6 提取电影简介
quote_tag = item.find("span", class_="inq")
intro = quote_tag.get_text(strip=True) if quote_tag else "无简介"
电影简介位于class
为 "inq" 的span
标签中。同样添加了条件判断,处理没有简介的情况。
3.3.7 存储电影数据
movie_dict = {"电影名称": movie_name,"评分": rating,"评价人数": comment_count,"导演&主演": director_actor,"上映年份": release_year,"国家/地区": country,"类型": genre,"简介": intro
}
movie_list.append(movie_dict)
将提取到的各项信息存入一个字典,然后将字典添加到movie_list
列表中。这样处理后,movie_list
将包含当前页面所有电影的信息。
4. 数据清洗与保存
4.1 数据转换
df = pd.DataFrame(movie_list)
使用 pandas 库将列表转换为 DataFrame,这是一种二维表格数据结构,便于进行数据处理和分析。
4.2 数据清洗
df["评价人数"] = pd.to_numeric(df["评价人数"].str.replace(".", ""), errors="coerce").fillna(0).astype(int)
这行代码对 "评价人数" 进行清洗和转换:
str.replace(".", "")
:去除字符串中的小数点,处理 "123.4 万" 这种格式pd.to_numeric(..., errors="coerce")
:将字符串转换为数值类型,无法转换的将设为 NaNfillna(0)
:将 NaN 值替换为 0astype(int)
:转换为整数类型
经过这些处理,"评价人数" 字段将成为干净的整数,便于后续的统计分析。
4.3 保存到 Excel
excel_path = "豆瓣电影Top250数据.xlsx"
df.to_excel(excel_path, index=False, engine="openpyxl")
使用 pandas 的to_excel
方法将数据保存到 Excel 文件:
index=False
:表示不保存 DataFrame 的索引列engine="openpyxl"
:指定使用 openpyxl 库作为引擎,支持.xlsx 格式
保存完成后,会打印保存路径和爬取到的电影数量,方便用户确认结果。
5. 主程序流程
if __name__ == "__main__":print("开始爬取豆瓣电影 Top250 数据...")crawl_douban_top250() # 1. 爬取数据save_to_excel() # 2. 保存数据print("爬取任务完成!")
这是程序的入口点,使用if __name__ == "__main__":
确保只有在直接运行该脚本时才会执行以下代码,而在被导入为模块时不会执行。
五、运行结果与验证
- 运行代码:执行程序后,控制台会输出爬取进度(如 “正在爬取第 1 页...”);
- 生成文件:程序结束后,当前目录会生成
豆瓣电影Top250数据.xlsx
文件; - 验证数据:打开 Excel 文件,可看到 250 行数据(若部分页面爬取失败,行数可能少于 250),列包含 “电影名称”“评分” 等 8 项信息,数据格式统一、无乱码。
六、拓展与注意事项
1. 功能拓展
- 多线程爬取:使用
threading
或concurrent.futures
库实现多线程,提升爬取速度(注意控制线程数,避免给服务器造成过大压力); - 数据可视化:用
matplotlib
或seaborn
绘制评分分布直方图、类型占比饼图等; - 增量爬取:记录上次爬取的最后一部电影,下次只爬取更新的数据。
2. 注意事项
- 遵守网站 robots 协议:豆瓣电影的
robots.txt
(https://movie.douban.com/robots.txt)允许爬取 Top250 数据,但需控制频率; - 反爬升级应对:若出现 “验证码” 或 “403 禁止访问”,可尝试添加
Cookie
(模拟登录状态)、使用代理 IP 池; - 法律风险:不得将爬取的数据用于商业用途,遵守《网络安全法》《数据安全法》等法律法规。
通过本案例,可掌握爬虫的核心流程(请求→解析→清洗→保存),理解 HTML 结构分析、反爬规避、数据处理的关键技巧,为后续爬取更复杂网站(如动态加载、需要登录的网站)打下基础。