基于ModelArts的电影实时票房数据分析与预测
本项目大数据采集和线性回归的基础原理,使用ModelArts进行开发工作,编写Python代码从票房网站爬取数据到本地CSV文件,然后针对CSV文件的数据进行转换,分析,建模,最后预测未来的票房数据。以下通过猫眼《捕风追影》电影示例
一、创建Notebook
二、点击“File\New Launcher”进入Launcher页面后,点击Terminal图标进入Terminal页面

在Terminal终端,输入下面的命令安装schedule服务包,如下图,安装成功后会显示成功的信息:
pip install schedule

三、爬取票房数据
点击菜单File\New Launcher,选择PyTorch-XX, 打开一个代码编辑窗口。
下面的代码将爬取票房网站的数据,经过解码和转换后,到本地CSV文件,程序开启了一个定时任务,它每分钟执行一次,意味着每一份钟爬取票房数据,然后往CSV文件写入一行票房数据。
import requests
import schedule
import time
import csv
import re
import os
from datetime import datetime
import html
from fontTools.ttLib import TTFont
import json
import base64
# 发送HTTP请求获取实时票房数据
def fetch_box_office_data():
# 网址为存放动态数据的网址:猫眼电影API的URL
MOVIE_WEBSITE_URL = "https://piaofang.maoyan.com/dashboard-ajax/movie"
HEADERS = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
'Referer': 'https://piaofang.maoyan.com/dashboard/movie',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0'
}
# 使用当前时间戳
current_timestamp = int(time.time() * 1000)
# 基本参数
params = {
"orderType": "0",
"uuid": f"auto-gen-{current_timestamp}",
"timeStamp": str(current_timestamp),
"User-Agent": "TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSHRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEzNC4wLjAuMCBTYWZhcmkvNTM3LjM2IEVkZy8xMzQuMC4wLjA%3D",
"index": "84",
"channelId": "40009",
"sVersion": "2",
"signKey": f"sign-{current_timestamp}",
"WukongReady": "h5"
}
try:
x = requests.get(MOVIE_WEBSITE_URL, headers=HEADERS, params=params, timeout=10)
if x.status_code == 200:
json_data = x.json()
return json_data
else:
print(f"Failed to fetch data, status code: {x.status_code}")
print(f"Response: {x.text[:500]}...")
return None
except Exception as e:
print(f"Error fetching data: {e}")
return None
def writedata(jdata, writer):
'''提取字体链接'''
if not jdata or 'fontStyle' not in jdata:
print("No fontStyle found in response")
return
fontStyle = jdata['fontStyle']
# 直接从fontStyle中提取字体URL
pattern = r'url\("([^"]+\.woff)"\)'
match = re.search(pattern, fontStyle)
if not match:
print("Failed to extract font URL with pattern")
return
font_url = match.group(1)
if font_url.startswith('//'):
font_url = 'https:' + font_url
elif not font_url.startswith('http'):
font_url = 'https://' + font_url
print("Extracted font_url:", font_url)
# 获取当前时间
current_time = datetime.now()
formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
if "movieList" not in jdata or "list" not in jdata["movieList"]:
print("No movie list found in response")
return
print(f"Found {len(jdata['movieList']['list'])} movies in response")
found_target_movie = False
for movie in jdata["movieList"]["list"]:
if "movieInfo" not in movie:
continue
movie_info = movie["movieInfo"]
if "movieId" not in movie_info:
continue
movie_id = movie_info["movieId"]
movie_name = movie_info.get("movieName", "Unknown")
print(f"Processing movie: ID={movie_id}, Name={movie_name}")
# 只处理《捕风追影》 (根据电影名称匹配)
if "捕风追影" in movie_name:
found_target_movie = True
movie_data = {
"movieId": movie_id,
"movieName": movie_name,
"dataTimestamp": formatted_time
}
# 处理综合票房
if "boxSplitUnit" in movie and "num" in movie["boxSplitUnit"]:
encoded_box_office = movie["boxSplitUnit"]["num"]
movie_data["boxSplitUnitNum"] = decode_data(font_url, encoded_box_office)
print(f"Box office: {encoded_box_office} -> {movie_data['boxSplitUnitNum']}")
else:
movie_data["boxSplitUnitNum"] = "N/A"
print("No boxSplitUnit found")
# 处理分账票房
if "splitBoxSplitUnit" in movie and "num" in movie["splitBoxSplitUnit"]:
encoded_split_box_office = movie["splitBoxSplitUnit"]["num"]
movie_data["splitBoxSplitNum"] = decode_data(font_url, encoded_split_box_office)
print(f"Split box office: {encoded_split_box_office} -> {movie_data['splitBoxSplitNum']}")
else:
movie_data["splitBoxSplitNum"] = "N/A"
print("No splitBoxSplitUnit found")
# 写入其他字段
fields = ["avgSeatView", "avgShowView", "boxRate", "showCount",
"showCountRate", "splitBoxRate", "sumBoxDesc", "sumSplitBoxDesc"]
for field in fields:
movie_data[field] = movie.get(field, "N/A")
# 写入数据行
writer.writerow(movie_data.values())
print(f"Data for {movie_name} saved at {formatted_time}")
break # 找到目标电影后跳出循环
if not found_target_movie:
print("Target movie (捕风追影) not found in response")
# 将数据保存到CSV文件
def save_data_to_csv(json_data, filename="box_office.csv"):
if not json_data:
print("No data to save")
return
header = ["avgSeatView", "avgShowView", "boxRate", "showCount", "showCountRate",
"splitBoxRate", "sumBoxDesc", "sumSplitBoxDesc", "movieId",
"movieName", "boxSplitUnitNum", "splitBoxSplitNum", "dataTimestamp"]
# 检查文件是否存在
file_exists = os.path.exists(filename)
with open(filename, 'a' if file_exists else 'w', encoding='utf-8', newline='') as csvfile:
writer = csv.writer(csvfile)
# 如果文件不存在,写入列名
if not file_exists:
writer.writerow(header)
print(f"文件 '{filename}' 不存在,创建文件并写入列名...")
else:
print(f"文件 '{filename}' 存在, 写入新数据。")
# 写入数据
writedata(json_data, writer)
print(f"Data saved to {filename}")
def decode_data(font_url, encoded_text):
"""
破解加密字体函数
参数:
font_url (str): 字体文件的URL
encoded_text (str): 加密的文本
返回:
str: 解密后的文本
"""
if not encoded_text or encoded_text == "N/A":
return "N/A"
# 猫眼字体映射表(可能需要根据实际情况调整)
font_mapping = {
'': '0', '': '0', '': '0', '': '0',
'': '1', '': '1', '': '1', '': '1',
'': '2', '': '2', '': '2', '': '2',
'': '3', '': '3', '': '3', '': '3',
'': '4', '': '4', '': '4', '': '4',
'': '5', '': '5', '': '5', '': '5',
'': '6', '': '6', '': '6', '': '6',
'': '7', '': '7', '': '7', '': '7',
'': '8', '': '8', '': '8', '': '8',
'': '9', '': '9', '': '9', '': '9',
}
# 直接使用映射表替换HTML实体
decoded_text = encoded_text
for entity, digit in font_mapping.items():
decoded_text = decoded_text.replace(entity, digit)
# 移除可能残留的HTML实体
decoded_text = re.sub(r'&#x[0-9a-f]+;', '', decoded_text)
return decoded_text
# 主函数
def main():
data = fetch_box_office_data()
if data:
save_data_to_csv(data)
else:
print("No data to save")
def job():
print("Fetching and saving data...")
main()
# 手动调用一次任务函数,确保任务立即启动
job()
# 间隔运行。
schedule.every(1).minutes.do(job)
# 启动定时任务
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
print("程序已停止")
数据将保存到文件(box_office.csv)

最前面爬取的数据字体文件解析存在问题
最终按上面修改后的代码爬取的数据正常,从22行开始。
四、读取数据并分析数据
点击菜单File\New Launcher,选择PyTorch-XX, 打开一个代码编辑窗口,在单元格中输入以下代码,结果如下,因为我的数据从22行开始才正确,所以读取数据从22行开始。
#-*-coding:utf-8-*
import sklearn.model selection as msimport sklearn.linear model as lm
import sklearn.metrics as meansquarederror
import matplotlib.pyplot as plt
from datetime import datetime
import pandas as pd
#跳过前21行错误数据,从第22行开始读取
datas = pd.read csv("./box office.csv",skiprows=22, header=None)
datas .info()

新增一代码行,在单元格中输入以下代码,获得前5行数据信息,如下图所示
datas .head()

对字段进行相应的数据处理,在单元格中输入以下代码,如下图所示:
# 首先为数据框指定列名
column_names = [
'filmId', 'filmName', 'dataTimestamp', 'avgSeatView', 'avgShowView',
'boxRate', 'showCount', 'showCountRate', 'boxSplitUnitNum',
'splitBoxRate', 'splitBoxSplitNum', 'boxOffice', 'splitBoxOffice'
]
# 为数据框指定列名
datas.columns = column_names
# 处理缺失值 - 这里改为 inplace=True 以实际删除缺失值
datas.dropna(inplace=True)
# 先处理无效值,将 '.' 替换为 NaN
datas['avgSeatView'] = datas['avgSeatView'].replace('.', pd.NA)
datas['boxRate'] = datas['boxRate'].replace('.', pd.NA)
datas['showCountRate'] = datas['showCountRate'].replace('.', pd.NA)
datas['splitBoxRate'] = datas['splitBoxRate'].replace('.', pd.NA)
# 再次删除包含无效值的行
datas.dropna(inplace=True)
# 处理%值 - 添加错误处理
def safe_convert_percent(x):
try:
# 确保x是字符串并去除%号
x_str = str(x).strip()
if x_str.endswith('%'):
return float(x_str.strip('%')) / 100
else:
return float(x_str)
except:
return pd.NA
# 应用安全的转换函数
datas['avgSeatView'] = datas['avgSeatView'].apply(safe_convert_percent)
datas['boxRate'] = datas['boxRate'].apply(safe_convert_percent)
datas['showCountRate'] = datas['showCountRate'].apply(safe_convert_percent)
datas['splitBoxRate'] = datas['splitBoxRate'].apply(safe_convert_percent)
# 处理时间值
def safe_convert_timestamp(x):
try:
return datetime.strptime(x, "%Y-%m-%d %H:%M:%S").timestamp()
except:
return pd.NA
datas['dataTimestamp'] = datas['dataTimestamp'].apply(safe_convert_timestamp)
# 删除任何转换失败的行
datas.dropna(inplace=True)
# 检查需要编码的列的唯一值
columns_to_encode = ["avgSeatView", "avgShowView", "boxRate", "showCount",
"showCountRate", "splitBoxRate", "boxSplitUnitNum", "splitBoxSplitNum"]
for column in columns_to_encode:
print(f"\n{column}:")
print(pd.unique(datas[column]))
结果如下:

五、数据训练和预测
1、拆分数据集。选取特征数据列和目标数据列,将特征数据(排片场次、排片占比、时间)拆分为训练集和测试集,然后对现有数据进行训练和预测,并打印均方误差,实际值与预测值的对比图,在单元格中输入以下代码:
features = ['showCount','showCountRate','dataTimestamp']
target = 'boxSplitUnitNum'
X = datas[features]
y = datas[target]
##划分训练集和测试集
Xtrain, Xtest, ytrain, ytest = ms.train_test_split(X, y, test_size=0.2, random_state=42)
##训练模型
model = lm.LinearRegression()
model.fit(Xtrain, ytrain)
#预测
ypred = model.predict(Xtest)
##评估模型
mse = meansquarederror.mean_squared_error(ytest, ypred)
print(f'均方误差:{mse}')
##绘制实际值与预测值对比图
plt.figure(figsize=(10, 6))
plt.plot(ytest.values, label='Actual')
plt.plot(ypred, label='Predicted')
plt.xlabel('Time')
plt.ylabel('Box Office')
plt.title('Box Office Prediction vs Actual')
plt.legend()
plt.show()

2、定义模型并预测输出。基于模型进行预测,修改正确的showCount,showCountRate,future_time, showCount,showCountRate的值可以直接从生成的文件(nezha2_box_office.csv)中拷贝, future_time可以是现在时间加上几分钟到几小时不等, 在单元格中输入以下代码:
##avgSeatView:上座率, avgShowView:场均人次, boxRate:票房占比, showCount:排片场次,showCountRate:排片占比,
##预测未来票房
future_time = '2025-09-18 6:00:00'
futuredata = pd.DataFrame({
'showCount': ['117665'],
'showCountRate': [0.329],
'dataTimestamp': [datetime.strptime(future_time, "%Y-%m-%d %H:%M:%S").timestamp()]
})
futurepredictions = model.predict(futuredata)
print(f'未来票房预测, 预测时间点:{future_time}, 预测的综合票房: {futurepredictions}')

更多推荐
所有评论(0)