import base64
import hashlib
import math
import random
import time
import requests
import json
import pandas as pd
from datetime import datetime, timedelta
import os
class MaoyanAPISpider:
def __init__(self):
self.session = requests.Session()
self.setup_headers()
def setup_headers(self):
"""设置请求头"""
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
'Referer': 'https://piaofang.maoyan.com/dashboard/movie',
'Origin': 'https://piaofang.maoyan.com',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Cookie': self.generate_cookies()
}
def generate_cookies(self):
"""生成必要的Cookie避免弹窗"""
cookies = [
'__mta=',
'_lxsdk_cuid=' + str(random.randint(1000000000, 9999999999)),
'_lxsdk=' + str(random.randint(1000000000, 9999999999)),
'_lx_utm=',
'_lxsdk_s=%s' % (''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10))),
'uuid=' + self.generate_uuid(),
'userTicket=',
'ci=1',
]
return '; '.join(cookies)
def generate_uuid(self):
"""生成UUID"""
return f'{random.randint(1000000000, 9999999999)}{int(time.time())}'
def generate_signature(self, timestamp, user_agent_encoded, index):
"""生成签名"""
content = f"method=GET&timeStamp={timestamp}&User-Agent={user_agent_encoded}&index={index}&channelId=40009&sVersion=2&key=A013F70DB97834C0A5492378BD76C53A"
md5 = hashlib.md5()
md5.update(content.encode('utf-8'))
return md5.hexdigest()
def get_daily_data(self, date_str):
"""获取单日票房数据"""
try:
# 生成加密参数
user_agent = self.headers['User-Agent']
user_agent_encoded = str(base64.b64encode(user_agent.encode('utf-8')), 'utf-8')
index = str(round(random.random() * 1000))
timestamp = str(math.ceil(time.time() * 1000))
# 生成签名
sign = self.generate_signature(timestamp, user_agent_encoded, index)
# 构造请求参数
params = {
'orderType': '0',
'uuid': self.generate_uuid(),
'timeStamp': timestamp,
'User-Agent': user_agent_encoded,
'index': index,
'channelId': '40009',
'sVersion': '2',
'signKey': sign,
'date': date_str
}
url = 'https://piaofang.maoyan.com/dashboard-ajax/movie'
# 发送请求
response = self.session.get(url, headers=self.headers, params=params, timeout=10)
response.raise_for_status()
# 检查响应内容
if response.text.strip() == '':
print(f"日期 {date_str}: 返回空数据")
return []
# 解析JSON数据
data = response.json()
return self.parse_movie_data(data, date_str)
except requests.exceptions.RequestException as e:
print(f"网络请求失败 {date_str}: {e}")
return []
except json.JSONDecodeError as e:
print(f"JSON解析失败 {date_str}: {e}")
print(f"响应内容: {response.text[:200]}...")
return []
except Exception as e:
print(f"获取 {date_str} 数据失败: {e}")
return []
def parse_movie_data(self, data, date_str):
"""解析电影数据 - 修复字段映射问题"""
movies_data = []
try:
if 'movieList' in data and 'list' in data['movieList']:
for movie in data['movieList']['list']:
# 调试输出原始数据
if len(movies_data) == 0: # 只打印第一部电影的调试信息
print(f"调试信息 - 原始数据结构:")
print(f"movie: {json.dumps(movie, ensure_ascii=False, indent=2)[:500]}...")
# 正确解析字段
movie_info = {
'日期': date_str,
'电影名称': movie.get('movieInfo', {}).get('movieName', ''),
'上映天数': movie.get('releaseInfo', {}).get('releaseDays', 0), # 修复:上映天数应该是数字
'累计票房': movie.get('sumBoxDesc', ''),
'单日票房': movie.get('boxDesc', ''),
'票房占比': movie.get('boxRate', ''),
'排片占比': movie.get('showCountRate', ''),
'上座率': movie.get('avgShowView', ''),
'平均票价': movie.get('avgViewBox', ''),
'场次': movie.get('showCount', ''),
'人次': movie.get('showView', '')
}
# 检查数据是否正确
if len(movies_data) == 0: # 只打印第一部电影的检查信息
print(f"调试信息 - 解析后数据:")
for key, value in movie_info.items():
print(f" {key}: {value}")
movies_data.append(movie_info)
print(f"日期 {date_str}: 解析到 {len(movies_data)} 部电影数据")
return movies_data
except Exception as e:
print(f"解析数据失败: {e}")
import traceback
traceback.print_exc()
return []
def crawl_range_data(self, start_date, end_date, target_count=10000):
"""爬取指定日期范围的数据"""
all_data = []
current_date = datetime.strptime(start_date, "%Y-%m-%d")
end_date = datetime.strptime(end_date, "%Y-%m-%d")
print(f"开始爬取 {start_date} 到 {end_date} 的票房数据")
print(f"目标数据量: {target_count} 条")
while current_date <= end_date and len(all_data) < target_count:
date_str = current_date.strftime("%Y-%m-%d")
print(f"正在处理: {date_str}")
daily_data = self.get_daily_data(date_str)
if daily_data:
all_data.extend(daily_data)
print(f"当前总计: {len(all_data)} 条数据")
# 避免请求过于频繁
time.sleep(random.uniform(1, 2))
current_date += timedelta(days=1)
# 每100条保存一次备份
if len(all_data) % 100 == 0 and len(all_data) > 0:
self.backup_data(all_data)
return all_data
def backup_data(self, data):
"""备份数据"""
if data:
df = pd.DataFrame(data)
backup_dir = "D:\\毕业论文\\box_office_data\\backup"
os.makedirs(backup_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = os.path.join(backup_dir, f"api_backup_{timestamp}.csv")
df.to_csv(backup_path, index=False, encoding='utf-8-sig')
print(f"数据已备份到: {backup_path}")
def save_final_data(self, data, output_dir="D:\\毕业论文\\box_office_data"):
"""保存最终数据"""
if not data:
print("没有数据可保存")
return None
os.makedirs(output_dir, exist_ok=True)
df = pd.DataFrame(data)
# 保存各种格式
csv_path = os.path.join(output_dir, "maoyan_api_box_office.csv")
df.to_csv(csv_path, index=False, encoding='utf-8-sig')
json_path = os.path.join(output_dir, "maoyan_api_box_office.json")
df.to_json(json_path, orient='records', force_ascii=False, indent=2)
excel_path = os.path.join(output_dir, "maoyan_api_box_office.xlsx")
df.to_excel(excel_path, index=False)
print(f"数据保存完成:")
print(f"CSV: {csv_path}")
print(f"JSON: {json_path}")
print(f"Excel: {excel_path}")
print(f"总数据量: {len(df)} 条")
# 显示数据预览
print("\n数据预览:")
print(df[['日期', '电影名称', '上映天数', '累计票房', '单日票房']].head(10))
# 显示列名信息
print("\n列名信息:")
for i, col in enumerate(df.columns, 1):
print(f"{i}. {col}")
return df
def test_single_date():
"""测试单日数据获取"""
print("=" * 60)
print("测试单日数据获取")
print("=" * 60)
spider = MaoyanAPISpider()
# 测试不同日期
test_dates = ["2024-11-21", "2024-11-22", "2025-11-20"]
for test_date in test_dates:
print(f"\n正在测试日期: {test_date}")
data = spider.get_daily_data(test_date)
if data:
df = pd.DataFrame(data)
print(f"获取到 {len(data)} 条数据")
print(f"日期列的值: {df['日期'].iloc[0]}")
print(f"上映天数样例: {df['上映天数'].iloc[0]}")
print(f"累计票房样例: {df['累计票房'].iloc[0]}")
print(f"单日票房样例: {df['单日票房'].iloc[0]}")
else:
print("没有获取到数据")
time.sleep(2)
def main():
"""主函数"""
print("=" * 60)
print("猫眼API票房数据爬虫")
print("=" * 60)
spider = MaoyanAPISpider()
# 设置爬取参数 - 使用正确的日期范围
start_date = "2024-11-21"
end_date = "2024-11-25" # 先测试小范围
target_count = 200
# 开始爬取
data = spider.crawl_range_data(start_date, end_date, target_count)
# 保存数据
if data:
df = spider.save_final_data(data)
print("爬虫执行完成!")
# 显示数据统计信息
if df is not None:
print(f"\n数据统计:")
print(f"日期范围: {df['日期'].min()} 到 {df['日期'].max()}")
print(f"电影数量: {df['电影名称'].nunique()}")
print(f"总记录数: {len(df)}")
# 检查数据质量
print(f"\n数据质量检查:")
print(f"上映天数为空: {df['上映天数'].isnull().sum()}")
print(f"累计票房为空: {df['累计票房'].isnull().sum()}")
print(f"单日票房为空: {df['单日票房'].isnull().sum()}")
else:
print("没有获取到数据")
if __name__ == "__main__":
# 先运行测试
test_single_date()
# 再运行主程序
print("\n" + "=" * 60)
print("开始主程序")
print("=" * 60)
main()显示D:\毕业论文\pythonProject3\.venv\Scripts\python.exe D:\毕业论文\pythonProject3\爬取.py
============================================================
测试单日数据获取
============================================================
正在测试日期: 2024-11-21
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2024-11-21
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2024-11-21: 解析到 84 部电影数据
获取到 84 条数据
日期列的值: 2024-11-21
上映天数样例: 0
累计票房样例: 4.61亿
单日票房样例:
正在测试日期: 2024-11-22
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2024-11-22
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2024-11-22: 解析到 84 部电影数据
获取到 84 条数据
日期列的值: 2024-11-22
上映天数样例: 0
累计票房样例: 4.61亿
单日票房样例:
正在测试日期: 2025-11-20
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2025-11-20
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2025-11-20: 解析到 84 部电影数据
获取到 84 条数据
日期列的值: 2025-11-20
上映天数样例: 0
累计票房样例: 4.61亿
单日票房样例:
============================================================
开始主程序
============================================================
============================================================
猫眼API票房数据爬虫
============================================================
开始爬取 2024-11-21 到 2024-11-25 00:00:00 的票房数据
目标数据量: 200 条
正在处理: 2024-11-21
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2024-11-21
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2024-11-21: 解析到 84 部电影数据
当前总计: 84 条数据
正在处理: 2024-11-22
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2024-11-22
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2024-11-22: 解析到 84 部电影数据
当前总计: 168 条数据
正在处理: 2024-11-23
调试信息 - 原始数据结构:
movie: {
"avgSeatView": "1.0%",
"avgShowView": "1.5",
"boxRate": "72.0%",
"boxSplitUnit": {
"num": ".",
"unit": "万"
},
"movieInfo": {
"movieId": 1528577,
"movieName": "鬼灭之刃:无限城篇 第一章 猗窝座再袭",
"releaseInfo": "上映8天"
},
"showCount": 129162,
"showCountRate": "34.8%",
"splitBoxRate": "71.2%",
"splitBoxSplitUnit": {
"num": ".",
"unit": "万"
},
"sumBoxDesc": "4.61亿",
"sumSplitBo...
调试信息 - 解析后数据:
日期: 2024-11-23
电影名称: 鬼灭之刃:无限城篇 第一章 猗窝座再袭
上映天数: 0
累计票房: 4.61亿
单日票房:
票房占比: 72.0%
排片占比: 34.8%
上座率: 1.5
平均票价:
场次: 129162
人次:
日期 2024-11-23: 解析到 84 部电影数据
当前总计: 252 条数据
数据保存完成:
CSV: D:\毕业论文\box_office_data\maoyan_api_box_office.csv
JSON: D:\毕业论文\box_office_data\maoyan_api_box_office.json
Excel: D:\毕业论文\box_office_data\maoyan_api_box_office.xlsx
总数据量: 252 条
数据预览:
日期 电影名称 上映天数 累计票房 单日票房
0 2024-11-21 鬼灭之刃:无限城篇 第一章 猗窝座再袭 0 4.61亿
1 2024-11-21 惊天魔盗团3 0 1.94亿
2 2024-11-21 菜肉馄饨 0 1626.2万
3 2024-11-21 狂野时代 0 22.5万
4 2024-11-21 铁血战士:杀戮之地 0 9633.6万
5 2024-11-21 志愿军:浴血和平 0 6.35亿
6 2024-11-21 三滴血 0 1851.2万
7 2024-11-21 不该停止的追问 0 15.8万
8 2024-11-21 浪浪人生 0 4.48亿
9 2024-11-21 开心岭 0 114.7万
列名信息:
1. 日期
2. 电影名称
3. 上映天数
4. 累计票房
5. 单日票房
6. 票房占比
7. 排片占比
8. 上座率
9. 平均票价
10. 场次
11. 人次
爬虫执行完成!
数据统计:
日期范围: 2024-11-21 到 2024-11-23
电影数量: 84
总记录数: 252
数据质量检查:
上映天数为空: 0
累计票房为空: 0
单日票房为空: 0
Process finished with exit code 0怎么解决,且2024-11-21 鬼灭之刃:无限城篇 第一章 猗窝座再袭 0 4.61亿
1 2024-11-21 惊天魔盗团3 0 1.94亿
2 2024-11-21 菜肉馄饨 0 1626.2万
3 2024-11-21 狂野时代 0 22.5万 都是2025.11.20的数据,却显示2024,请给出完整代码