852 lines
30 KiB
Python
852 lines
30 KiB
Python
import json
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 【防乱码神器】考试必写这两行,不然中文全是方块
|
||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
# 1. 读取数据 (固定套路)
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
movies = json.load(f)
|
||
|
||
# ==========================================
|
||
# 第1题:柱状图 (类型 vs 数量)
|
||
# ==========================================
|
||
# 这里需要统计一下数量,稍微麻烦一点点,但我帮你写好了逻辑
|
||
type_counts = {}
|
||
for m in movies:
|
||
# 假设你的json里类型字段叫 'types' 或 'genres',根据实际改
|
||
types = m.get('types', '').split('/')
|
||
for t in types:
|
||
t = t.strip()
|
||
type_counts[t] = type_counts.get(t, 0) + 1
|
||
|
||
# 取前10个或者全部画图
|
||
x = list(type_counts.keys())
|
||
y = list(type_counts.values())
|
||
|
||
plt.figure() # 开启一张新画布
|
||
plt.bar(x, y) # <--- 考点:bar是柱状图
|
||
plt.title("类型电影数量分布")
|
||
plt.savefig("q4_1_bar.png", dpi=150)
|
||
|
||
|
||
# ==========================================
|
||
# 第2题:散点图 (时长 vs 评分)
|
||
# ==========================================
|
||
# 提取数据
|
||
durations = [m['duration'] for m in movies] # 时长做X
|
||
ratings = [m['rating'] for m in movies] # 评分做Y
|
||
|
||
plt.figure() # 开启新画布
|
||
plt.scatter(durations, ratings, color='red', alpha=0.6) # <--- 考点:scatter是散点图
|
||
plt.title("时长与评分关系散点图")
|
||
plt.xlabel("时长(分钟)")
|
||
plt.ylabel("评分")
|
||
plt.savefig("q4_2_scatter.png", dpi=150)
|
||
|
||
|
||
# ==========================================
|
||
# 第3题:直方图 (评分分布 & 时长分布)
|
||
# ==========================================
|
||
# A图:评分
|
||
plt.figure()
|
||
plt.hist(ratings, bins=5, color='blue') # <--- 考点:hist是直方图
|
||
plt.title("评分分布")
|
||
plt.savefig("q4_3a_hist.png", dpi=150)
|
||
|
||
# B图:时长
|
||
plt.figure()
|
||
plt.hist(durations, bins=5, color='green')
|
||
plt.title("时长分布")
|
||
plt.xlabel("时长(分钟)")
|
||
plt.savefig("q4_3b_hist.png", dpi=150)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
只记函数名:
|
||
|
||
看到“柱状图” \rightarrow 脑子反应出 plt.bar
|
||
|
||
看到“散点图” \rightarrow 脑子反应出 plt.scatter
|
||
|
||
看到“直方图/分布” \rightarrow 脑子反应出 plt.hist
|
||
|
||
搞定中文乱码:只要考试涉及中文图表,上来先把那两行 plt.rcParams... 写上,这是送分也是送命题(不写就没分)。
|
||
|
||
|
||
|
||
|
||
|
||
#2.0
|
||
第一步:准备工作(必写)
|
||
|
||
在代码最开头,必须导入这两个库,这是拿分的基础。
|
||
|
||
import json # 用来读取 JSON 文件
|
||
import matplotlib.pyplot as plt # 用来画图(plt 是它的简称)
|
||
|
||
# 【防坑设置】为了防止中文显示成乱码方块,考试时建议加上这两行
|
||
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
|
||
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
|
||
|
||
|
||
第二步:读取与提取数据(必写)
|
||
|
||
不管题目怎么变,都要先把数据从 movies.json 里拿出来。
|
||
|
||
# 1. 打开文件
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
data = json.load(f) # data 现在就是一个包含所有电影的大列表
|
||
|
||
# 2. 准备两个空篮子,用来装 X轴 和 Y轴 的数据
|
||
x_data = []
|
||
y_data = []
|
||
|
||
# 3. 遍历每一部电影,把数据挑出来
|
||
for item in data:
|
||
# 假设我们要取 "评分" 和 "时长"
|
||
x_data.append(item['rating']) # 把评分放入 X 篮
|
||
y_data.append(item['duration']) # 把时长放入 Y 篮
|
||
|
||
|
||
第三步:画图函数(根据题目选一个)
|
||
|
||
这是考试的核心,题目考什么图,就套用哪个函数。
|
||
|
||
图表类型 函数写法 适用场景 关键参数
|
||
柱状图 plt.bar(x, y) 统计数量、对比大小 width=0.5 (柱子宽度)
|
||
散点图 plt.scatter(x, y) 看分布、找关系 c='red' (颜色), alpha=0.6 (透明度)
|
||
折线图 plt.plot(x, y) 看趋势、随时间变化 linestyle='--' (线型)
|
||
直方图 plt.hist(data) 看频率分布 bins=10 (分成几组), color='blue'
|
||
|
||
|
||
第四步:美化与保存(必写)
|
||
|
||
题目通常会给具体的文件名和标题要求,直接复制粘贴即可。
|
||
|
||
# 设置标题和坐标轴标签(题目通常会指定文字)
|
||
plt.title("这里是题目要求的标题")
|
||
plt.xlabel("X轴名称")
|
||
plt.ylabel("Y轴名称")
|
||
|
||
# 保存图片(一定要写在 plt.show() 之前,或者只写这一句)
|
||
# dpi=150 是清晰度,题目没说的话写上更保险
|
||
plt.savefig('q4_1_bar.png', dpi=150)
|
||
|
||
# 如果你想看图,可以加这句;如果是自动评分系统,通常不需要
|
||
# plt.show()
|
||
|
||
|
||
|
||
|
||
|
||
#3.0
|
||
没问题!这道题是典型的数据可视化(Data Visualization)题目。它要求你从之前保存的 movies.json 文件中读取数据,然后用 Python 最常用的绘图库 matplotlib 把数据画成图表。
|
||
|
||
别担心,我会把代码拆解开,一步步教你怎么写,并解释每一行代码的作用。
|
||
|
||
🛠️ 准备工作:安装必要的库
|
||
做这道题,你需要用到两个库:
|
||
1. matplotlib:用来画图(柱状图、散点图等)。
|
||
2. json:Python 自带的,用来读取 .json 文件。
|
||
|
||
如果你还没装 matplotlib,请在终端运行:
|
||
pip install matplotlib
|
||
|
||
📂 第 1 小题:绘制“各类型电影数量”柱状图 (q4_1.py)
|
||
|
||
💡 思路讲解
|
||
1. 读数据:打开 movies.json,把里面的列表加载到内存中。
|
||
2. 统计数据:我们需要统计每种类型(比如“剧情”、“喜剧”)出现了多少次。这里有个坑:一部电影可能有多个类型(例如“剧情/犯罪”),所以我们要先按分隔符拆分,再统计。
|
||
3. 画图:用 plt.bar() 画柱子。
|
||
4. 保存:用 plt.savefig() 保存图片。
|
||
|
||
🐍 完整代码 (q4_1.py)
|
||
|
||
import json
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 1. 读取数据
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
movies = json.load(f)
|
||
|
||
# 2. 统计各类型的数量
|
||
type_counts = {}
|
||
for movie in movies:
|
||
# 假设类型字段叫 "types" 或 "genres",根据你之前的爬虫结果调整
|
||
# 这里假设字段名是 'types',且格式为 "剧情 / 犯罪"
|
||
types_str = movie.get('types', '')
|
||
|
||
# 将字符串按 '/' 分割,并去除首尾空格
|
||
type_list = [t.strip() for t in types_str.split('/')]
|
||
|
||
for t in type_list:
|
||
if t: # 防止空字符串
|
||
type_counts[t] = type_counts.get(t, 0) + 1
|
||
|
||
# 提取 X 轴(类型名)和 Y 轴(数量)
|
||
x_labels = list(type_counts.keys())
|
||
y_values = list(type_counts.values())
|
||
|
||
# 3. 设置中文字体(非常重要!否则中文会显示成方框)
|
||
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
|
||
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
|
||
|
||
# 4. 绘制柱状图
|
||
plt.figure(figsize=(10, 6)) # 设置画布大小
|
||
plt.bar(x_labels, y_values)
|
||
|
||
# 5. 设置标题和标签
|
||
plt.title("类型电影数量分布")
|
||
plt.xlabel("类型名称")
|
||
plt.ylabel("电影数量")
|
||
|
||
# 6. 保存和显示
|
||
plt.savefig('q4_1_bar.png', dpi=150)
|
||
print("图表已保存为 q4_1_bar.png")
|
||
# plt.show() # 如果你想看弹窗图,取消这行的注释
|
||
|
||
🔍 重点学习点
|
||
- movie.get('types', ''):安全地获取字典里的值,如果没有这个键就返回空字符串,防止报错。
|
||
- split('/'):因为爬虫抓下来的类型通常是连在一起的字符串(如 "剧情 / 犯罪"),必须切开才能分别计数。
|
||
- plt.rcParams[...]:这是解决 Matplotlib 中文乱码的万能公式,考试必背。
|
||
|
||
📂 第 2 小题:绘制“评分 vs 时长”散点图 (q4_2.py)
|
||
|
||
💡 思路讲解
|
||
1. 提数据:我们需要两个列表,一个存所有电影的时长,一个存所有电影的评分。
|
||
2. 画图:使用 plt.scatter()。
|
||
3. 样式:题目特别要求了颜色(红色)和透明度(alpha=0.6)。
|
||
|
||
🐍 完整代码 (q4_2.py)
|
||
|
||
import json
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 1. 读取数据
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
movies = json.load(f)
|
||
|
||
# 2. 提取时长和评分
|
||
durations = []
|
||
ratings = []
|
||
|
||
for movie in movies:
|
||
# 注意:要确保数据能转为数字,有些可能为空或格式不对
|
||
try:
|
||
dur = int(movie.get('duration', 0).replace('分钟', '').strip())
|
||
rate = float(movie.get('rate', 0))
|
||
|
||
durations.append(dur)
|
||
ratings.append(rate)
|
||
except ValueError:
|
||
continue # 如果转换失败(比如数据缺失),跳过这一条
|
||
|
||
# 3. 设置中文字体
|
||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||
plt.rcParams['axes.unicode_minus'] = False
|
||
|
||
# 4. 绘制散点图
|
||
plt.figure(figsize=(10, 6))
|
||
# c='red' 设置颜色, alpha=0.6 设置透明度
|
||
plt.scatter(durations, ratings, c='red', alpha=0.6)
|
||
|
||
# 5. 设置标题和标签
|
||
plt.title("时长与评分关系散点图")
|
||
plt.xlabel("时长 (分钟)")
|
||
plt.ylabel("评分")
|
||
|
||
# 6. 保存
|
||
plt.savefig('q4_2_scatter.png', dpi=150)
|
||
print("图表已保存为 q4_2_scatter.png")
|
||
|
||
🔍 重点学习点
|
||
- c='red', alpha=0.6:这是题目明确要求的参数。alpha 越小越透明,这在数据点很多重叠时非常有用,能让你看清数据的密度。
|
||
- 数据清洗 (try...except):真实数据往往很脏,有的电影可能没填时长。加上 try 结构能保证程序不会因为一条坏数据而崩溃。
|
||
|
||
📂 第 3 小题:绘制直方图 (q4_3a.py & q4_3b.py)
|
||
|
||
直方图(Histogram)通常用来展示数据的分布情况(比如:大部分电影都在 90-120 分钟之间)。
|
||
|
||
(A) 评分分布直方图 (q4_3a.py)
|
||
|
||
import json
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 读取数据 (省略重复部分,假设已读取到 movies 变量)
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
movies = json.load(f)
|
||
|
||
# 提取评分数据
|
||
scores = [float(m.get('rate', 0)) for m in movies]
|
||
|
||
# 设置字体
|
||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||
|
||
# 绘制直方图
|
||
plt.figure(figsize=(8, 5))
|
||
# bins=5 表示把数据分成 5 个区间段
|
||
# color='blue' 设置颜色
|
||
plt.hist(scores, bins=5, color='blue')
|
||
|
||
plt.title("评分分布")
|
||
plt.xlabel("评分")
|
||
|
||
# 保存
|
||
plt.savefig('q4_3a_hist.png', dpi=150)
|
||
print("已保存 q4_3a_hist.png")
|
||
|
||
(B) 时长分布直方图 (q4_3b.py)
|
||
|
||
import json
|
||
import matplotlib.pyplot as plt
|
||
|
||
# 读取数据
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
movies = json.load(f)
|
||
|
||
# 提取时长数据 (需转为整数)
|
||
durations = []
|
||
for m in movies:
|
||
try:
|
||
d = int(m.get('duration', 0).replace('分钟', '').strip())
|
||
durations.append(d)
|
||
except:
|
||
pass
|
||
|
||
# 设置字体
|
||
plt.rcParams['font.sans-serif'] = ['SimHei']
|
||
|
||
# 绘制直方图
|
||
plt.figure(figsize=(8, 5))
|
||
# bins=5, 颜色绿色
|
||
plt.hist(durations, bins=5, color='green')
|
||
|
||
plt.title("时长分布")
|
||
plt.xlabel("时长 (分钟)")
|
||
|
||
# 保存
|
||
plt.savefig('q4_3b_hist.png', dpi=150)
|
||
print("已保存 q4_3b_hist.png")
|
||
|
||
🔍 重点学习点
|
||
- plt.hist vs plt.bar:
|
||
- bar 是你自己算好每个柱子多高(比如:动作片 50 部),传给它画。
|
||
- hist 是你给它一堆原始数据(比如:[120, 90, 100...]),它自己帮你切分区间并计算高度。
|
||
- bins=5:题目要求分 5 个桶。这意味着它会自动找出最大值和最小值,然后平均切成 5 份来统计。
|
||
|
||
🎓 总结与避坑指南
|
||
|
||
1. 文件路径:确保你的 movies.json 文件和这些 .py 代码文件在同一个文件夹里,否则 open('movies.json') 会报错找不到文件。
|
||
2. 字段名称:我在代码里用了 types, duration, rate 作为字典的键(Key)。请务必打开你的 movies.json 看一眼,确认里面的键名是不是这几个。如果你的爬虫写的是 genre 而不是 types,代码里也要对应修改。
|
||
3. 中文乱码:只要看到图里的中文变成 □□□,就是没加那两行 plt.rcParams 代码。
|
||
|
||
你可以先把代码复制进去跑一下,如果有报错(比如 Key Error),可以把报错信息发给我,我帮你微调!
|
||
|
||
这部分弄完,需要我再帮你梳理下前面‘抓取全部250部电影’的代码吗?
|
||
|
||
|
||
|
||
|
||
|
||
|
||
#爬虫代码
|
||
#1.0
|
||
要将代码改为抓取前 50 部电影(即豆瓣 Top 250 的前两页),我们需要对 URL 进行循环处理。豆瓣的列表是分页显示的,每页展示 25 部电影。
|
||
|
||
因此,要获取前 50 部,我们需要访问 start=0(第 1-25 名)和 start=25(第 26-50 名)这两个页面。
|
||
|
||
以下是修改后的完整代码:
|
||
|
||
🐍 抓取前 50 部电影完整代码
|
||
|
||
import requests
|
||
from bs4 import BeautifulSoup as bs
|
||
import json
|
||
import time
|
||
|
||
# 设置请求头,模拟浏览器访问
|
||
headers = {
|
||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||
}
|
||
|
||
data = []
|
||
|
||
# 循环抓取前两页 (0 到 25,步长为 25)
|
||
# range(0, 50, 25) 会生成 [0, 25],正好对应前 50 部电影
|
||
for start_num in range(0, 50, 25):
|
||
# 动态构建 URL
|
||
url = f"https://movie.douban.com/top250?start={start_num}&filter="
|
||
print(f"正在抓取第 {start_num + 1} - {start_num + 25} 名的电影...")
|
||
|
||
try:
|
||
resp = requests.get(url, headers=headers)
|
||
resp.encoding = 'utf-8'
|
||
soup = bs(resp.text, "html.parser")
|
||
|
||
items = soup.find_all("div", class_="item")
|
||
|
||
for i in range(len(items)):
|
||
item_data = {}
|
||
|
||
# 1. 获取标题
|
||
title_span = items[i].find("span", class_="title")
|
||
if title_span:
|
||
item_data["title"] = title_span.get_text()
|
||
else:
|
||
item_data["title"] = "未知标题"
|
||
|
||
# 2. 获取演员信息
|
||
bd_div = items[i].find("div", class_="bd")
|
||
actors_text = bd_div.get_text().strip() if bd_div else ""
|
||
|
||
try:
|
||
# 尝试提取主演部分
|
||
if "主演:" in actors_text:
|
||
item_data["actors"] = actors_text.split("主演:")[1].split("n")[0].strip()
|
||
else:
|
||
item_data["actors"] = "无主演信息"
|
||
except:
|
||
item_data["actors"] = "解析错误"
|
||
|
||
# 3. 获取经典台词 (quote)
|
||
try:
|
||
quote_p = bd_div.find("p", class_="quote")
|
||
if quote_p:
|
||
item_data["quote"] = quote_p.find("span").get_text().strip()
|
||
else:
|
||
item_data["quote"] = "暂无台词"
|
||
except:
|
||
item_data["quote"] = "暂无台词"
|
||
|
||
data.append(item_data)
|
||
|
||
# 礼貌爬取:每抓完一页暂停 1 秒,防止被封 IP
|
||
time.sleep(1)
|
||
|
||
except Exception as e:
|
||
print(f"抓取第 {start_num} 页时出错: {e}")
|
||
|
||
# 打印并保存结果
|
||
print(f"n抓取完成!共获取 {len(data)} 部电影数据。")
|
||
print(data)
|
||
|
||
with open("movies_top50.json", "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
print("数据已保存到 movies_top50.json")
|
||
|
||
💡 主要修改点说明
|
||
|
||
1. 引入循环 (for start_num in range(0, 50, 25))
|
||
- 原代码只抓取了 start=0 这一页。
|
||
- 新代码通过 range 函数生成 0 和 25 两个数字。
|
||
- 当 start_num 为 0 时,抓取第 1-25 名;当 start_num 为 25 时,抓取第 26-50 名。
|
||
|
||
2. URL 动态拼接
|
||
- 使用了 f-string:f"...start={start_num}...",这样每次循环都会自动更新网址。
|
||
|
||
3. 增加 time.sleep(1)
|
||
- 这是一个非常重要的爬虫礼仪。连续快速请求同一个网站很容易导致你的 IP 被豆瓣暂时封锁(返回 418 错误)。加上 1 秒延时可以大大提高成功率。
|
||
|
||
4. 优化了数据结构
|
||
- 将每个电影的数据先存入字典 item_data,再添加到列表 data 中,逻辑更清晰,也避免了变量覆盖的问题。
|
||
|
||
现在脚本已经能稳定抓取前 50 部了,需要我帮你把它改成“一键抓取全部 250 部”吗?
|
||
|
||
|
||
|
||
|
||
#2.0
|
||
这是从图片中提取并整理好的完整 Python 代码。该脚本用于爬取豆瓣电影 Top 250 的电影信息(标题、演员、经典台词),并将结果保存为 movies.json 文件。
|
||
|
||
🐍 豆瓣电影 Top 250 爬虫代码
|
||
|
||
import requests
|
||
from bs4 import BeautifulSoup as bs
|
||
import json
|
||
|
||
# 设置请求头,模拟浏览器访问
|
||
headers = {
|
||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||
}
|
||
|
||
url = "https://movie.douban.com/top250?start=0&filter="
|
||
data = []
|
||
|
||
resp = requests.get(url, headers=headers)
|
||
resp.encoding = 'utf-8'
|
||
soup = bs(resp.text, "html.parser")
|
||
|
||
items = soup.find_all("div", class_="item")
|
||
|
||
for i in range(len(items)):
|
||
# 获取电影标题
|
||
title = items[i].find("span", class_="title").get_text()
|
||
|
||
# 获取演员信息(处理可能存在的格式问题)
|
||
actors = items[i].find("div", class_="bd").get_text().strip()
|
||
try:
|
||
actors = actors.split("主演:")[1].split("n")[0]
|
||
except:
|
||
actors = "无"
|
||
|
||
# 获取经典台词(注意:部分电影可能没有台词,需做异常处理)
|
||
try:
|
||
quote = items[i].find("div", class_="bd").find("p", class_="quote").get_text().strip()
|
||
except:
|
||
quote = "无"
|
||
|
||
data.append({
|
||
"title": title,
|
||
"actors": actors,
|
||
"quote": quote
|
||
})
|
||
|
||
print(data)
|
||
|
||
# 将数据保存为 JSON 文件
|
||
with open("movies.json", "w", encoding="utf-8") as f:
|
||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||
|
||
💡 代码关键点说明
|
||
|
||
1. 异常处理:在提取“主演”和“经典台词”时使用了 try...except 结构。这是因为部分电影条目可能缺少主演信息或经典台词,直接提取会导致程序报错中断。
|
||
2. 编码设置:resp.encoding = 'utf-8' 确保中文内容不会乱码;保存 JSON 时设置 ensure_ascii=False 也是为了正确显示中文字符。
|
||
3. 数据存储:最终数据被整理为字典列表,并使用 json.dump 格式化写入本地文件,方便后续分析使用。
|
||
|
||
这段代码只能抓取第一页的 25 部电影,需要我帮你改成循环抓取全部 250 部吗?
|
||
|
||
|
||
|
||
#3.0
|
||
除了保存为 JSON 格式,CSV (Excel) 和 TXT (文本) 是最常用的两种替代方案。
|
||
|
||
1. CSV 格式:最推荐。可以直接用 Excel 打开,方便后续做数据分析或整理表格。
|
||
2. TXT 格式:适合直接阅读,像写日记一样列出电影清单。
|
||
|
||
以下是修改后的代码,你可以根据需要选择其中一种保存方式(只需替换代码最后的保存部分)。
|
||
|
||
选项一:保存为 CSV 文件 (推荐,可用 Excel 打开)
|
||
|
||
这种方式会将数据整理成表格形式。我们需要引入 Python 自带的 csv 库。
|
||
|
||
import requests
|
||
from bs4 import BeautifulSoup as bs
|
||
import csv # 引入 csv 库
|
||
|
||
headers = {
|
||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||
}
|
||
|
||
data = []
|
||
|
||
# 循环抓取前两页 (前50部)
|
||
for start_num in range(0, 50, 25):
|
||
url = f"https://movie.douban.com/top250?start={start_num}&filter="
|
||
print(f"正在抓取: {url}")
|
||
|
||
resp = requests.get(url, headers=headers)
|
||
resp.encoding = 'utf-8'
|
||
soup = bs(resp.text, "html.parser")
|
||
items = soup.find_all("div", class_="item")
|
||
|
||
for i in range(len(items)):
|
||
try:
|
||
title = items[i].find("span", class_="title").get_text()
|
||
|
||
actors_raw = items[i].find("div", class_="bd").get_text().strip()
|
||
try:
|
||
actors = actors_raw.split("主演:")[1].split("n")[0].strip()
|
||
except:
|
||
actors = "无主演信息"
|
||
|
||
try:
|
||
quote = items[i].find("div", class_="bd").find("p", class_="quote").get_text().strip()
|
||
except:
|
||
quote = "暂无台词"
|
||
|
||
# 将数据存入列表
|
||
data.append([title, actors, quote])
|
||
|
||
except Exception as e:
|
||
print(f"解析出错: {e}")
|
||
|
||
# --- 保存为 CSV 文件 ---
|
||
filename = "movies_top50.csv"
|
||
with open(filename, "w", newline="", encoding="utf-8-sig") as f:
|
||
writer = csv.writer(f)
|
||
# 写入表头
|
||
writer.writerow(["电影名称", "主演", "经典台词"])
|
||
# 写入数据
|
||
writer.writerows(data)
|
||
|
||
print(f"成功保存 {len(data)} 部电影到 {filename}")
|
||
|
||
💡 提示: 这里使用了 utf-8-sig 编码,这是为了防止在 Windows 的 Excel 中打开 CSV 文件时出现中文乱码。
|
||
|
||
选项二:保存为 TXT 文本文件
|
||
|
||
这种方式适合直接双击打开阅读,格式比较直观。
|
||
|
||
import requests
|
||
from bs4 import BeautifulSoup as bs
|
||
|
||
headers = {
|
||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
|
||
}
|
||
|
||
content_list = []
|
||
|
||
# 循环抓取前两页
|
||
for start_num in range(0, 50, 25):
|
||
url = f"https://movie.douban.com/top250?start={start_num}&filter="
|
||
resp = requests.get(url, headers=headers)
|
||
resp.encoding = 'utf-8'
|
||
soup = bs(resp.text, "html.parser")
|
||
items = soup.find_all("div", class_="item")
|
||
|
||
for i in range(len(items)):
|
||
try:
|
||
title = items[i].find("span", class_="title").get_text()
|
||
|
||
actors_raw = items[i].find("div", class_="bd").get_text().strip()
|
||
try:
|
||
actors = actors_raw.split("主演:")[1].split("n")[0].strip()
|
||
except:
|
||
actors = "未知"
|
||
|
||
try:
|
||
quote = items[i].find("div", class_="bd").find("p", class_="quote").get_text().strip()
|
||
except:
|
||
quote = "无"
|
||
|
||
# 格式化字符串
|
||
line = f"【{title}】n主演:{actors}n台词:{quote}n{'-'*30}n"
|
||
content_list.append(line)
|
||
|
||
except Exception as e:
|
||
pass
|
||
|
||
# --- 保存为 TXT 文件 ---
|
||
filename = "movies_top50.txt"
|
||
with open(filename, "w", encoding="utf-8") as f:
|
||
f.write("豆瓣电影 Top 50 榜单nn")
|
||
for item in content_list:
|
||
f.write(item)
|
||
|
||
print(f"已保存到 {filename}")
|
||
|
||
总结建议
|
||
|
||
- 如果你需要统计、筛选(比如只看某位演员的电影),请选择 CSV。
|
||
- 如果你只是想看文字清单,或者发给别人看,请选择 TXT。
|
||
|
||
要是觉得这两种都不够直观,我还可以帮你改成存进 SQLite 数据库,方便以后做查询,需要的话告诉我。
|
||
|
||
|
||
#4.0
|
||
没问题,完全理解你的意思。你是希望保留刚才那份抓取前 50 部的完整逻辑(包括循环和防封延时),仅仅把最后保存为 JSON 的部分改成其他格式。
|
||
|
||
这里提供两个最常用的替代方案:Excel (CSV) 和 TXT 文本。你可以根据需要直接替换代码的最后 3 行。
|
||
|
||
方案一:保存为 Excel 表格 (CSV 格式)
|
||
这是最推荐的格式,生成的文件可以直接用 Excel 打开查看,整齐清晰。
|
||
|
||
修改步骤
|
||
1. 在代码最开头引入 csv 库:
|
||
import csv # <--- 加上这一行
|
||
|
||
2. 将代码最后 3 行(即 with open...json.dump... 部分)替换为:
|
||
# === 替换原来的 json 保存代码 ===
|
||
with open("movies_top50.csv", "w", encoding="utf-8-sig", newline="") as f:
|
||
writer = csv.DictWriter(f, fieldnames=["title", "actors", "quote"])
|
||
writer.writeheader() # 写入表头
|
||
writer.writerows(data) # 写入数据
|
||
|
||
print("数据已保存到 movies_top50.csv (可用Excel打开)")
|
||
|
||
注意: 使用 utf-8-sig 编码是为了防止 Excel 打开中文时出现乱码。
|
||
|
||
方案二:保存为 TXT 纯文本
|
||
适合直接阅读,像清单一样列出电影信息。
|
||
|
||
修改步骤
|
||
不需要引入新库,直接将代码最后 3 行替换为:
|
||
# === 替换原来的 json 保存代码 ===
|
||
with open("movies_top50.txt", "w", encoding="utf-8") as f:
|
||
for movie in data:
|
||
f.write(f"【{movie['title']}】n")
|
||
f.write(f"主演:{movie['actors']}n")
|
||
f.write(f"台词:{movie['quote']}n")
|
||
f.write("-" * 30 + "n") # 分割线
|
||
|
||
print("数据已保存到 movies_top50.txt")
|
||
|
||
💡 核心提示
|
||
- 前面的代码不用动: for start_num in range(0, 50, 25): 这个循环逻辑以及 time.sleep(1) 都保持不变,确保能抓到 50 条数据且不封 IP。
|
||
- 二选一即可: 上面两个方案选一个你想要的替换掉原代码末尾的 JSON 保存部分就行。
|
||
|
||
这份代码是抓前 50 部的,需要我帮你改成“一键抓取全部 250 部”吗?
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
#爬虫第2题
|
||
根据题目要求,我们需要基于第1题爬取并保存的 movies.json 文件,完成第2题的四个小任务。
|
||
|
||
题目分析
|
||
|
||
首先,我们需要明确 movies.json 的结构。根据第1题的描述,它包含“数据编号”和“10部电影的信息”,每部电影包含的键为:id, title, director, year, rating, duration, genre, actors_count。
|
||
|
||
我们可以假设 movies.json 的结构大致如下(具体结构以实际爬取为准,但通常是一个字典,包含一个数据编号字段和一个电影列表字段):
|
||
|
||
{
|
||
"data_id": "some_number",
|
||
"movies": [
|
||
{
|
||
"id": 1,
|
||
"title": "电影A",
|
||
"director": "导演X",
|
||
"year": 2020,
|
||
"rating": 8.5,
|
||
"duration": 120,
|
||
"genre": "动作",
|
||
"actors_count": 5
|
||
},
|
||
// ... 其他9部电影
|
||
]
|
||
}
|
||
|
||
接下来,我们需要加载这个 JSON 文件,并提取出 movies 列表进行后续处理。
|
||
|
||
解题步骤
|
||
|
||
1. 准备工作:加载数据
|
||
|
||
我们需要先读取 movies.json,获取电影列表。
|
||
|
||
import json
|
||
|
||
# 假设 movies.json 已经由第1题生成
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
# 提取电影列表,根据实际结构可能是 data['movies'] 或 data['data'] 等
|
||
# 这里假设电影列表的键是 'movies'
|
||
movies = data.get('movies')
|
||
if not movies:
|
||
# 如果结构不同,可能需要调整,例如 movies = data['data']
|
||
pass
|
||
|
||
2. ① 找出评分最高和最低的电影,打印电影名 + 评分。
|
||
|
||
我们需要遍历电影列表,找到 rating 最大和最小的电影。可以使用 max() 和 min() 函数配合 key 参数,或者手动遍历。
|
||
|
||
# 找出评分最高的电影
|
||
highest_rated_movie = max(movies, key=lambda m: m['rating'])
|
||
# 找出评分最低的电影
|
||
lowest_rated_movie = min(movies, key=lambda m: m['rating'])
|
||
|
||
print(f"评分最高的电影:{highest_rated_movie['title']},评分:{highest_rated_movie['rating']}")
|
||
print(f"评分最低的电影:{lowest_rated_movie['title']},评分:{lowest_rated_movie['rating']}")
|
||
|
||
3. ② 统计各类型的电影数量,用字典格式输出。
|
||
|
||
电影类型通常在 genre 字段中。我们需要统计每个 genre 出现的次数。
|
||
|
||
genre_counts = {}
|
||
for movie in movies:
|
||
genre = movie['genre']
|
||
genre_counts[genre] = genre_counts.get(genre, 0) + 1
|
||
|
||
print("各类型电影数量:", genre_counts)
|
||
|
||
4. ③ 统计各导演的电影数量,用字典格式输出。
|
||
|
||
同理,统计 director 字段的出现次数。
|
||
|
||
director_counts = {}
|
||
for movie in movies:
|
||
director = movie['director']
|
||
director_counts[director] = director_counts.get(director, 0) + 1
|
||
|
||
print("各导演电影数量:", director_counts)
|
||
|
||
5. ④ 统计 2020 年(含)以后上映的电影数量。
|
||
|
||
遍历电影列表,判断 year 是否大于等于 2020。
|
||
|
||
count_2020_plus = 0
|
||
for movie in movies:
|
||
if movie['year'] >= 2020:
|
||
count_2020_plus += 1
|
||
|
||
print("2020年(含)以后上映的电影数量:", count_2020_plus)
|
||
|
||
完整代码示例
|
||
|
||
将上述步骤整合,完整的 Python 代码如下:
|
||
|
||
import json
|
||
|
||
# --- 1. 加载 movies.json 数据 ---
|
||
try:
|
||
with open('movies.json', 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
# 根据第1题,电影信息通常在 'movies' 键下
|
||
movies = data.get('movies')
|
||
if not movies:
|
||
raise ValueError("未在 movies.json 中找到 'movies' 列表,请检查数据结构。")
|
||
except FileNotFoundError:
|
||
print("错误:movies.json 未找到。请先运行第1题的爬取代码生成该文件。")
|
||
exit()
|
||
except json.JSONDecodeError:
|
||
print("错误:movies.json 格式不正确。")
|
||
exit()
|
||
|
||
# --- 2. ① 找出评分最高和最低的电影 ---
|
||
if movies:
|
||
highest_rated_movie = max(movies, key=lambda m: m['rating'])
|
||
lowest_rated_movie = min(movies, key=lambda m: m['rating'])
|
||
|
||
print("--- 评分最高/最低 ---")
|
||
print(f"评分最高的电影:{highest_rated_movie['title']},评分:{highest_rated_movie['rating']}")
|
||
print(f"评分最低的电影:{lowest_rated_movie['title']},评分:{lowest_rated_movie['rating']}")
|
||
|
||
# --- 3. ② 统计各类型的电影数量 ---
|
||
genre_counts = {}
|
||
for movie in movies:
|
||
genre = movie['genre']
|
||
genre_counts[genre] = genre_counts.get(genre, 0) + 1
|
||
print("n--- 各类型电影数量 ---")
|
||
print(genre_counts)
|
||
|
||
# --- 4. ③ 统计各导演的电影数量 ---
|
||
director_counts = {}
|
||
for movie in movies:
|
||
director = movie['director']
|
||
director_counts[director] = director_counts.get(director, 0) + 1
|
||
print("n--- 各导演电影数量 ---")
|
||
print(director_counts)
|
||
|
||
# --- 5. ④ 统计 2020 年(含)以后上映的电影数量 ---
|
||
count_2020_plus = sum(1 for movie in movies if movie['year'] >= 2020)
|
||
print("n--- 2020年(含)以后上映的电影数量 ---")
|
||
print(count_2020_plus)
|
||
else:
|
||
print("没有电影数据可处理。")
|
||
|
||
注意事项
|
||
1. 文件依赖:此代码依赖于第1题生成的 movies.json。请确保第1题代码已正确运行,且 movies 列表中的键名(如 genre, director, year, rating, title)与上述代码一致。
|
||
2. 数据结构:如果 movies.json 中电影列表的键不是 movies(例如是 data 或 list),请调整 movies = data.get('movies') 这一行。
|
||
3. 输出格式:题目要求字典格式输出,上述代码中的 genre_counts 和 director_counts 已经是字典,直接打印即可满足要求。
|
||
|
||
这段代码逻辑清晰,能够一次性完成第2题的所有统计任务。 |