爬豆瓣电影Top250全流程:用requests+BeautifulSoup实战,避过反爬坑还能导出Excel
本文介绍了如何用Python爬取豆瓣电影Top250数据并导出Excel。文章重点解决三大难点:1)处理反爬机制(通过添加User-Agent请求头);2)精准解析HTML结构(使用BeautifulSoup定位7类关键信息);3)数据清洗与格式化(处理多语言标题、拆分导演/主演等)。作者提供了完整的代码实现,包括分页请求、异常处理和数据导出功能,并分享了实际项目中遇到的典型问题(如403错误、数
爬豆瓣电影Top250是很多Python爬虫新手的入门练手项目,但真动手做起来会发现不少“坑”:请求被403拒绝、数据提取混乱、导出Excel格式错乱…… 我最近刚做完这个练手项目,从0到1踩了不少坑,也总结了一套能稳定运行的流程。这篇文章就带你一步步实现:从分析网页结构、处理反爬,到精准提取数据、清洗格式,最后用pandas导出Excel,全程附可直接运行的代码和避坑细节。
一、先搞清楚:我们要爬什么?怎么爬?
爬取前先明确目标:豆瓣电影Top250页面(https://movie.douban.com/top250)有250部电影,分10页展示(每页25部)。我们需要提取每部电影的排名、标题、导演/主演、年份/国家/类型、评分、评价人数、简介这7项信息,最后汇总成Excel。
先看网页结构(F12打开开发者工具):
每部电影的信息都包裹在<li>
标签里,class是"item"
。点进一个item
能看到:排名在<em>
标签里,标题在class="title"
的<span>
里,导演主演在class="bd"
的<p>
里,评分在class="rating_num"
里…… 这些就是我们要定位的标签。
(实际写时可配自己抓的图)
爬取思路很简单:
- 循环请求10页数据(URL规律:第1页
?start=0
,第2页?start=25
,…,第10页?start=225
); - 用BeautifulSoup解析每页HTML,提取7项信息;
- 清洗数据(比如从标题里拆年份、处理多语言标题);
- 用pandas导出到Excel。
二、环境准备:3个库就够了
不需要复杂的环境,安装3个基础库:
requests
:发送HTTP请求获取网页内容;beautifulsoup4
:解析HTML提取数据;pandas
:处理数据并导出Excel(依赖openpyxl
库)。
直接用pip安装:
pip install requests beautifulsoup4 pandas openpyxl
建议用Python3.8+版本,避免一些库的兼容性问题。
三、爬取数据:重点处理反爬和标签定位
3.1 发送请求:先解决“被豆瓣拒绝”的问题
第一次写的时候,直接用requests.get(url)请求,结果返回403 Forbidden——豆瓣有反爬机制,会识别“非浏览器”的请求。解决办法是给请求加User-Agent头,模拟浏览器访问。
User-Agent可以从浏览器开发者工具的“Network”里找(随便刷新一个页面,看请求头里的User-Agent),比如Chrome的可以用:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
封装一个请求函数,自动处理10页URL:
import requests
from bs4 import BeautifulSoup
import time
# 模拟浏览器请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}
def get_page_html(page_num):
"""获取第page_num页的HTML内容(page_num从0到9)"""
start = page_num * 25 # 计算start参数:0,25,...,225
url = f"https://movie.douban.com/top250?start={start}&filter="
try:
# 发送GET请求,添加超时控制(避免卡壳)
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
# 豆瓣网页用utf-8编码,直接指定避免乱码
response.encoding = "utf-8"
return response.text
except Exception as e:
print(f"第{page_num+1}页请求失败:{e}")
return None
注意加timeout=10
和response.raise_for_status()
,避免请求超时或出错时程序崩溃。另外,爬多页时建议加个time.sleep(1)
,别太频繁请求(礼貌爬取,避免被封IP)。
3.2 解析数据:精准定位标签,提取7项信息
拿到HTML后,用BeautifulSoup解析。核心是从每个class="item"
的<li>
里提取数据,逐个分析标签:
- 排名:在
<em class="">
标签里,直接取文本即可; - 标题:有多个
<span class="title">
,第一个是中文标题,后面可能有外文标题(带/
分隔),我们只取中文标题; - 导演/主演:在
<div class="bd">
下的第一个<p>
里,文本格式是“导演: 张艺谋 主演: 葛优 / 巩俐 …”,需要拆分; - 年份/国家/类型:和导演信息在同一个
<p>
里,靠后的位置,格式是“1994 / 中国大陆 / 剧情”; - 评分:在
<span class="rating_num">
里,文本是“9.7”这样的数字; - 评价人数:在评分后面的
<span>
里,格式是“(123456人评价)”,需要提取数字; - 简介:在
<span class="inq">
里,部分电影没有简介,需要处理空值。
直接上解析代码,每个提取步骤都加了注释:
def parse_html(html):
"""解析单页HTML,返回电影数据列表"""
soup = BeautifulSoup(html, "html.parser") # 用html.parser解析
movie_list = []
# 找到所有电影item
items = soup.find_all("li", class_="item")
for item in items:
movie = {}
# 1. 排名
rank = item.find("em").text.strip()
movie["排名"] = int(rank) # 转成整数
# 2. 标题(只取中文标题)
title_tags = item.find_all("span", class_="title")
# 中文标题通常是第一个,且不带/
chinese_title = [t.text for t in title_tags if "/" not in t.text][0]
movie["标题"] = chinese_title
# 3. 导演和主演
bd_p = item.find("div", class_="bd").find("p").text.strip()
# 按换行分割,第一行是导演,第二行是年份等信息
bd_lines = [line.strip() for line in bd_p.split("\n") if line.strip()]
director_actor_line = bd_lines[0]
# 拆分导演和主演(格式:"导演: 张艺谋 主演: 葛优 / 巩俐")
if "主演:" in director_actor_line:
director_part, actor_part = director_actor_line.split("主演:")
movie["导演"] = director_part.replace("导演:", "").strip()
movie["主演"] = actor_part.strip()
else:
# 有些电影可能没有主演信息
movie["导演"] = director_part.replace("导演:", "").strip()
movie["主演"] = "无"
# 4. 年份、国家、类型(从第二行提取)
info_line = bd_lines[1]
# 格式:"1994 / 中国大陆 / 剧情",按/分割
info_parts = [p.strip() for p in info_line.split("/")]
movie["年份"] = info_parts[0] if len(info_parts)>=1 else "未知"
movie["国家"] = info_parts[1] if len(info_parts)>=2 else "未知"
movie["类型"] = info_parts[2] if len(info_parts)>=3 else "未知"
# 5. 评分
rating = item.find("span", class_="rating_num").text.strip()
movie["评分"] = float(rating) # 转成浮点数
# 6. 评价人数(提取数字)
comment_span = item.find("div", class_="star").find_all("span")[-1]
comment_text = comment_span.text.strip()
# 用切片去掉"(人评价)",只留数字
comment_num = comment_text[1:-4]
# 处理带逗号的数字(如"123,456")
movie["评价人数"] = int(comment_num.replace(",", ""))
# 7. 简介(可能为空)
quote = item.find("span", class_="inq")
movie["简介"] = quote.text.strip() if quote else "无简介"
movie_list.append(movie)
return movie_list
这里有几个细节需要注意:
- 标题可能有多个(中文+外文),用
if "/" not in t.text
过滤出中文标题; - 导演和主演可能在同一行,用“主演:”分割,避免提取混乱;
- 评价人数带逗号(如“123,456”),需要用
replace(",", "")
处理后再转整数; - 部分电影没有简介,用
if quote else "无简介"
避免报错。
四、数据清洗:让Excel里的数据更规整
爬下来的数据虽然能看,但还有些小问题需要处理,比如:
- 年份是字符串(如“1994”),可以转成整数;
- 国家可能有多个(如“中国大陆 / 中国香港”),保持原样即可;
- 类型可能有多个(如“剧情 / 爱情”),无需拆分;
- 极少数电影的“评价人数”可能为空(概率极低,加个判断兜底)。
简单写个清洗函数(其实在解析时已经处理了大部分,这里再补充):
def clean_data(movies):
"""清洗电影数据,处理异常值"""
for movie in movies:
# 年份转整数(防止有非数字的异常值)
try:
movie["年份"] = int(movie["年份"])
except:
movie["年份"] = "未知"
return movies
五、导出Excel:用pandas一行代码搞定
数据处理完后,用pandas导出Excel非常方便。直接创建DataFrame,然后调用to_excel
方法:
import pandas as pd
def save_to_excel(movies, filename="豆瓣电影Top250.xlsx"):
"""将电影数据保存到Excel"""
df = pd.DataFrame(movies)
# 调整列顺序(按我们想要的顺序排列)
columns = ["排名", "标题", "年份", "国家", "类型", "导演", "主演", "评分", "评价人数", "简介"]
df = df[columns]
# 导出Excel,index=False去掉行索引
df.to_excel(filename, index=False, engine="openpyxl")
print(f"数据已保存到{filename},共{len(movies)}条记录")
这里指定engine="openpyxl"
是因为pandas导出xlsx格式需要这个引擎(前面已经安装过)。导出后打开Excel,数据会按我们指定的列顺序排列,非常规整。
六、完整代码:组装起来,跑一遍试试
把上面的函数组装成完整程序,加上主逻辑:
import requests
from bs4 import BeautifulSoup
import time
import pandas as pd
# 模拟浏览器请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}
def get_page_html(page_num):
"""获取第page_num页的HTML内容(page_num从0到9)"""
start = page_num * 25
url = f"https://movie.douban.com/top250?start={start}&filter="
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
response.encoding = "utf-8"
return response.text
except Exception as e:
print(f"第{page_num+1}页请求失败:{e}")
return None
def parse_html(html):
"""解析单页HTML,返回电影数据列表"""
soup = BeautifulSoup(html, "html.parser")
movie_list = []
items = soup.find_all("li", class_="item")
for item in items:
movie = {}
# 排名
movie["排名"] = int(item.find("em").text.strip())
# 标题(中文)
title_tags = item.find_all("span", class_="title")
chinese_title = [t.text for t in title_tags if "/" not in t.text][0]
movie["标题"] = chinese_title
# 导演和主演
bd_p = item.find("div", class_="bd").find("p").text.strip()
bd_lines = [line.strip() for line in bd_p.split("\n") if line.strip()]
director_actor_line = bd_lines[0]
if "主演:" in director_actor_line:
director_part, actor_part = director_actor_line.split("主演:")
movie["导演"] = director_part.replace("导演:", "").strip()
movie["主演"] = actor_part.strip()
else:
movie["导演"] = director_actor_line.replace("导演:", "").strip()
movie["主演"] = "无"
# 年份、国家、类型
info_line = bd_lines[1]
info_parts = [p.strip() for p in info_line.split("/")]
movie["年份"] = info_parts[0] if len(info_parts)>=1 else "未知"
movie["国家"] = info_parts[1] if len(info_parts)>=2 else "未知"
movie["类型"] = info_parts[2] if len(info_parts)>=3 else "未知"
# 评分
movie["评分"] = float(item.find("span", class_="rating_num").text.strip())
# 评价人数
comment_span = item.find("div", class_="star").find_all("span")[-1]
comment_text = comment_span.text.strip()
movie["评价人数"] = int(comment_text[1:-4].replace(",", ""))
# 简介
quote = item.find("span", class_="inq")
movie["简介"] = quote.text.strip() if quote else "无简介"
movie_list.append(movie)
return movie_list
def clean_data(movies):
"""清洗数据"""
for movie in movies:
try:
movie["年份"] = int(movie["年份"])
except:
movie["年份"] = "未知"
return movies
def save_to_excel(movies, filename="豆瓣电影Top250.xlsx"):
"""保存到Excel"""
df = pd.DataFrame(movies)
columns = ["排名", "标题", "年份", "国家", "类型", "导演", "主演", "评分", "评价人数", "简介"]
df = df[columns]
df.to_excel(filename, index=False, engine="openpyxl")
print(f"数据已保存到{filename},共{len(movies)}条记录")
def main():
all_movies = []
# 爬取10页数据(0-9)
for page in range(10):
print(f"正在爬取第{page+1}页...")
html = get_page_html(page)
if not html:
continue # 失败则跳过该页
movies = parse_html(html)
all_movies.extend(movies)
time.sleep(1) # 间隔1秒,礼貌爬取
# 清洗并保存
all_movies = clean_data(all_movies)
save_to_excel(all_movies)
if __name__ == "__main__":
main()
运行程序,会看到控制台输出“正在爬取第X页”,最后提示“数据已保存到豆瓣电影Top250.xlsx”。打开Excel,250部电影的信息整齐排列,包括《肖申克的救赎》《霸王别姬》等经典影片的详细信息。
七、避坑总结:这些细节决定爬取成败
- User-Agent必须加:豆瓣会检测请求头,没加或用默认头会被403;
- 请求间隔别太短:连续快速请求可能被封IP,加
time.sleep(1)
足够; - 标签提取要精准:比如标题区分中文/外文、评价人数处理逗号,这些细节错了数据就乱了;
- 异常处理不能少:用
try-except
处理请求失败、数据格式异常,避免程序中途崩溃; - 编码指定为utf-8:豆瓣网页用utf-8编码,不指定可能出现中文乱码。
八、扩展思路:让这个爬虫更强大
如果想进一步练习,可以试试这些扩展:
- 加个IP代理池:如果爬多了被封IP,用代理池切换IP(推荐
requests-proxies
库); - 多线程爬取:用
threading
或concurrent.futures
提速(注意控制线程数,别给服务器太大压力); - 存储到数据库:用
pymysql
存到MySQL,或sqlite3
存本地数据库; - 可视化分析:用
matplotlib
画评分分布直方图,或用wordcloud
做电影类型词云。
爬豆瓣Top250看似简单,但能帮你掌握爬虫的核心流程:分析页面→发送请求→解析数据→清洗存储。掌握这些后,再爬其他网站(比如豆瓣图书、IMDb)会更得心应手。如果运行代码时遇到问题,欢迎在评论区留言,我会尽量回复~
更多推荐
所有评论(0)