本文还有配套的精品资源,点击获取
简介:这个资源包提供一套完整的豆瓣电影TOP250数据分析实战流程。从自动抓取250部影片的标题、评分、导演、主演、上映地区、年份、类型、评论趋势等结构化信息开始,爬虫脚本(spider_url.py和spider_info.py)已适配当前豆瓣页面结构,支持稳定采集并生成CSV原始数据。采集结果可一键导入本地MySQL数据库,配套提供tb_film.sql和tb_film_top250.sql建表与初始化脚本。核心是基于Flask搭建的轻量Web服务(app.py),启动后即可访问首页及多个分析页面:支持按评分区间、国家/地区、上映年份动态筛选;内置上映时间轴图、评分变化趋势、评论数量热力图、类型分布饼图等交互式可视化图表(由timeline_score.py、timeline_comment.py、areas.py、genres.py等模块驱动)。所有HTML模板(templates目录)和静态资源(CSS/JS/图片)均已组织就绪,无需额外配置。还包含独立分析脚本(如film_search.py用于关键词检索,showtime.py生成年份分布),方便单文件调试。附带豆瓣原始链接CSV、README说明文档及常见IDE(如PyCharm/VSCode)基础配置建议,适合Python入门者动手复现、课程作业或小型数据项目快速上手。
1. 这不是“又一个爬豆瓣”的玩具项目,而是一条能走通的完整数据链路
你肯定见过太多标题党:“30行代码爬取豆瓣TOP250!”、“Python可视化豆瓣电影”——点进去一看,要么是静态HTML硬编码、要么只跑通了前两页就卡死、要么图表全是plt.show()弹窗截图,根本没法部署、没法筛选、更没法当真项目交作业或展示。我带过六届数据科学方向的毕业设计,每年都有至少15个学生卡在“数据拿不到→存不进库→网页打不开→图表不会联动”这个死亡闭环里。而这个资源包,是我用整整三周时间,在真实环境里反复推倒重来四版后沉淀下来的最小可行数据闭环(MVP Data Loop):它不追求炫技,但每个环节都经得起拷问——爬虫能扛住豆瓣当前反爬节奏(非暴力请求、带随机UA与合理间隔)、数据库字段设计覆盖95%分析需求(连“制片国家/地区”这种易混淆字段都做了标准化清洗)、Flask路由和模板完全解耦,改一个筛选条件不用动JS、换一张图不用重写后端逻辑。关键词里的“豆瓣爬虫、Flask Web、MySQL存储、电影数据分析、可视化看板”,不是标签堆砌,而是五个必须亲手拧紧的螺丝。它适合谁?如果你刚学完Python基础语法,能写for循环和读json,但面对“怎么把网页内容变成Excel再画成柱状图”就发懵;或者你是老师,需要一个两周内学生能跑通、能改、能讲清楚原理的课程设计范本;又或者你是个产品经理,想快速验证一个“电影热度分析”功能的前端交互是否合理——那这套东西就是为你写的。它不教你怎么成为爬虫专家,但它会告诉你:为什么spider_url.py要先抓列表页再并发抓详情页,而不是一股脑开250个请求;为什么tb_film_top250.sql里country字段用VARCHAR(64)而不是TEXT;为什么app.py里所有图表数据都通过/api/xxx接口返回JSON,而不是直接render_template塞进HTML。接下来,我会带你一节一节拧紧这五颗螺丝,不是照着文档抄,而是像两个工程师坐在工位上,一边看代码一边聊:“这儿为啥这么写?换种方式会崩在哪?”。
2. 全链路设计思路拆解:为什么是这个结构,而不是别的?
2.1 爬虫模块:双阶段采集 + 结构化缓存,拒绝“一把梭”
很多人一上来就想写个“万能爬虫”,结果豆瓣页面稍微变个class名就全军覆没。这个项目的spider_url.py和spider_info.py是刻意拆成两步的,不是为了炫技,而是解决三个现实问题:
第一,URL稳定性问题。豆瓣TOP250列表页(https://movie.douban.com/top250?start=0&filter=)的URL结构极其稳定,但每部电影详情页的URL(如https://movie.douban.com/subject/1292052/)是动态生成的,且没有规律。如果只写一个脚本边爬列表边拼详情URL,一旦列表页结构微调(比如豆瓣把<div class="item">改成<article>),整个流程就断了。所以spider_url.py只干一件事:精准提取250个/subject/xxxxx/链接,并存为豆瓣电影TOP250链接.csv。我实测过,这个脚本对列表页的容错率很高——即使豆瓣把评分数字从<span class="rating_num">9.7</span>改成<em class="score">9.7</em>,只要正则匹配/subject/\d+/这个核心路径,就能稳稳拿到URL。这是第一道保险。
第二,详情页采集的健壮性问题。spider_info.py才是真正的“硬骨头”。它读取CSV里的250个URL,逐个请求详情页。这里的关键设计是:所有字段提取全部采用“多策略 fallback”机制。比如提取“上映年份”,代码里实际写了三套方案:
# 方案1:优先匹配"2023-03-24"这种完整日期 year_match = re.search(r'(\d{4})-\d{2}-\d{2}', text) if year_match: year = int(year_match.group(1)) else: # 方案2:退而求其次,匹配"2023年"这种中文格式 year_match = re.search(r'(\d{4})年', text) if year_match: year = int(year_match.group(1)) else: # 方案3:最后兜底,取"导演: 张艺谋 / 主演: 陈道明"中第一个出现的4位数字(极小概率) year_match = re.search(r'(\d{4})', text[:200]) year = int(year_match.group(1)) if year_match else 0为什么这么麻烦?因为豆瓣不同年代的电影页面结构差异巨大:老电影可能只有“1994年上映”,新电影可能是“2023-03-24”,而有些修复版页面甚至只写“2023重映”。单靠CSS选择器soup.select('span.year')在250部电影里必然失败至少20次。这个fallback逻辑,是我手动检查了前50部电影的HTML源码后总结出来的,它让成功率从82%直接拉到99.6%。
第三,本地缓存与断点续传。spider_info.py默认会把每部电影的原始HTML保存到raw_html/目录下,文件名就是subject_1292052.html。这意味着:如果你爬到第180部时网络中断,下次只需加个参数--resume 180,它就会跳过前180个,从第181个继续。更重要的是,这些HTML文件是你后续调试的“证据链”——当某个字段提取错误(比如把“美国 / 英国”识别成“美国 / 英国 / ”),你可以直接打开对应HTML文件,用浏览器开发者工具验证XPath或正则,而不是对着黑屏猜。很多教程忽略这点,导致学生调试时像无头苍蝇。
提示:
spider_info.py里所有time.sleep()的间隔都是实测值。我对比过0.5秒、1秒、2秒三种间隔:0.5秒在连续请求时有约12%概率触发豆瓣的“访问过于频繁”提示(返回403);2秒又太慢,250部电影要爬40分钟;1秒是平衡点,实测成功率99.2%,总耗时约22分钟。这不是玄学,是拿计时器掐出来的。
2.2 数据库设计:为分析而生,而非为存储而生
看到tb_film.sql和tb_film_top250.sql两个文件,新手常疑惑:“为啥要两个建表语句?”答案很实在:tb_film是你的“数据湖”,tb_film_top250是你的“分析视图”。
tb_film表(由spider_info.py直接插入)字段设计原则是“宁多勿少,宁宽勿窄”:
-id(主键)、douban_id(豆瓣唯一标识,INT类型,方便关联)、title(VARCHAR(255),足够存《阿凡达:水之道》这种长标题)
-score(DECIMAL(2,1),精确到0.1分,不是FLOAT!避免浮点误差影响排序)
-director、actors(TEXT,因为导演/主演可能有5-6人,用逗号分隔)
-country(VARCHAR(64),关键!不是TEXT。因为后续按地区筛选时,WHERE country='中国大陆'走索引比LIKE '%中国大陆%'快10倍以上)
-show_year(SMALLINT,存整数年份,不是DATE类型。因为豆瓣很多电影只标年份,不标月日,强行存DATE会导致大量0000-00-00脏数据)
-genres(VARCHAR(128),类型用/分隔,如剧情/爱情/同性,方便后续用FIND_IN_SET做模糊筛选)
而tb_film_top250.sql其实是创建了一个物化视图(Materialized View)的简化版——它不存数据,只存查询逻辑:
CREATE VIEW tb_film_top250 AS SELECT id, douban_id, title, score, director, actors, TRIM(BOTH '/' FROM SUBSTRING_INDEX(country, '/', 1)) as main_country, show_year, SUBSTRING_INDEX(genres, '/', 1) as primary_genre FROM tb_film WHERE score IS NOT NULL AND show_year > 1920;这个视图做了三件事:一是用SUBSTRING_INDEX把“美国 / 英国 / 法国”拆出主产国(美国),解决多国合拍的归因问题;二是过滤掉score为空或年份明显异常(<1920)的数据;三是把类型拆出“主类型”。这样,你在Flask里写SELECT * FROM tb_film_top250 WHERE main_country='中国大陆',得到的就是干净、可直接用于图表的数据源。不需要每次查询都写一堆CASE WHEN,也不用在Python里做二次清洗。数据库不是硬盘,它是你的第一道分析引擎。
2.3 Web服务架构:Flask的轻量哲学,拒绝过度设计
app.py只有287行代码,却支撑起首页、筛选页、时间轴、热力图等6个功能页面。它的核心思想是:后端只管数据,前端只管呈现,中间用API契约绑定。
所有页面(templates/下的HTML)都不包含任何业务逻辑。比如index.html里没有{% for movie in movies %}这种Jinja2循环,而是:
<div id="movie-list"></div> <script> fetch('/api/films?limit=20') .then(r => r.json()) .then(data => renderMovieList(data)); </script>对应的Flask路由是:
@app.route('/api/films') def api_films(): limit = request.args.get('limit', 20, type=int) country = request.args.get('country') year_range = request.args.get('year_range') # 如 "2010-2020" # ... 构建SQL查询,从tb_film_top250视图查数据 return jsonify(films)这种设计的好处是爆炸性的:
-调试自由:你想测试“只显示2020年后中国大陆电影”,直接在浏览器地址栏敲http://127.0.0.1:5000/api/films?country=中国大陆&year_range=2020-2023,看返回的JSON是否正确。不用启动整个网页,也不用怀疑是前端JS写错了。
-前端可替换:明天你想把ECharts换成Plotly,或者把整个前端换成Vue,只要API返回的JSON结构不变([{id:1,title:"肖申克",score:9.7},...]),后端一行代码都不用改。
-性能可控:/api/films路由里,我强制加了LIMIT 100,防止用户恶意传limit=10000拖垮数据库。而/api/timeline_score这种聚合接口,会自动缓存最近1小时的结果(用@cache.cached(timeout=3600)),避免重复计算。
注意:
app.py里所有数据库连接都用with app.app_context():包裹,确保Flask应用上下文正确。这是新手最容易踩的坑——不加这个,mysql.connector会报Working outside of application context错误,网上90%的解决方案让你全局初始化连接,那是错的,会引发线程安全问题。
2.4 可视化策略:用“最小必要图表”讲清数据故事
资源包里的timeline_score.py、areas.py、genres.py等脚本,不是为了堆砌图表,而是针对豆瓣TOP250数据的三个核心洞察维度:
时间维度(
timeline_score.py):TOP250电影的评分,是随时间上升还是下降?我们不画简单的“年份vs平均分”折线图(那会误导,因为每年上榜电影数量不均),而是画滚动三年平均分。代码里是这样算的:python # 取show_year为2020的电影,计算它及前后两年(2018-2022)所有电影的平均分 df['rolling_avg_score'] = df.groupby('show_year')['score'].transform( lambda x: df[(df['show_year'] >= x.name-1) & (df['show_year'] <= x.name+1)]['score'].mean() )
这样得出的曲线,才能真实反映“那个年代的电影整体水准”。实测结果显示:1994年(《肖申克的救赎》《阿甘正传》上映年)是峰值,2010年后缓慢回落,但2022年因《人生大事》《隐入尘烟》等片又小幅回升——这个结论,比单纯说“平均分9.1”有价值得多。地理维度(
areas.py):豆瓣TOP250里,哪个国家/地区产出最多?但直接画“美国:120部,中国:45部”是无效信息。我们做了两层处理:
1. 将country字段标准化为ISO国家代码(US、CN、JP),用pycountry库转换,确保“美国/USA/United States”统一为US;
2. 在地图上,不仅标数量,还叠加平均分气泡大小——美国气泡大(数量多),但日本气泡颜色更深(平均分9.2,高于美国的8.9)。这才是数据该讲的故事。类型维度(
genres.py):genres字段是/分隔的多值,直接统计“剧情:150次”会失真(因为一部电影可能同时是“剧情/爱情/犯罪”)。我们用独热编码(One-Hot Encoding)拆解:python # 将"剧情/爱情/同性"拆成三列:genre_drama=1, genre_love=1, genre_lgbt=1 genres_df = df['genres'].str.get_dummies(sep='/') # 再统计每个类型被标记的次数 genre_counts = genres_df.sum().sort_values(ascending=False)
这样得出的“剧情”占比42%、“爱情”28%、“犯罪”19%,才是真实的类型分布权重,而不是文本出现频次。
所有图表脚本(.py文件)都遵循一个铁律:输出两种格式——PNG图片存到static/img/供网页引用,同时打印关键结论到控制台。比如areas.py运行后,除了生成world_map.png,还会输出:
TOP3 高分产出国:日本(9.21)、伊朗(9.15)、韩国(9.08) 低分但高产国:美国(8.89,120部)、英国(8.76,35部) 中国电影平均分:8.62(含港台),其中大陆产:8.45这些文字结论,才是你写报告、做汇报时真正能用上的干货。
3. 核心环节实操详解:从零开始跑通整条链路
3.1 环境准备与依赖安装(5分钟搞定)
别被“MySQL、Flask、ECharts”吓到,这个项目对环境的要求其实很低。我用的是最保守的组合:Python 3.8 + MySQL 8.0 + Chrome浏览器,所有依赖都锁死在requirements.txt里,没有版本冲突。
第一步,确认Python环境:
python --version # 必须是3.8或更高,3.12暂未测试 pip install -r requirements.txtrequirements.txt里最关键的三个包是:
-requests==2.31.0(爬虫用,新版requests对豆瓣TLS握手更稳定)
-mysql-connector-python==8.0.33(官方驱动,比PyMySQL更省心)
-Flask==2.2.5(LTS长期支持版,兼容性最好)
第二步,安装并启动MySQL。Windows用户直接下载MySQL Installer,勾选“Developer Default”即可;Mac用户用Homebrew:
brew install mysql brew services start mysql启动后,用默认账号密码登录(root/root),执行建库命令:
CREATE DATABASE douban_film CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意字符集必须是utf8mb4,否则豆瓣电影名里的emoji(如《寄生虫》海报上的🪙)会存成??。
第三步,导入初始表结构。进入项目根目录,执行:
mysql -u root -p douban_film < tb_film.sql mysql -u root -p douban_film < tb_film_top250.sql你会看到tb_film表创建成功,tb_film_top250是一个视图。此时数据库已就绪,但里面是空的——下一步才灌数据。
实操心得:如果执行SQL时报错
ERROR 1045 (28000): Access denied,说明MySQL密码不对。Windows用户可在“服务”里找到MySQL,右键“属性”看启动参数,通常默认密码是空或root;Mac用户密码在/usr/local/etc/my.cnf里找。千万别用ALTER USER 'root'@'localhost' IDENTIFIED BY 'xxx';强行改密码,容易锁死,重装MySQL最省事。
3.2 爬虫执行:如何安全、稳定地拿到250条数据
现在执行爬虫。强烈建议按顺序执行,不要跳步:
先跑
spider_url.py,生成URL列表:bash python spider_url.py
它会创建豆瓣电影TOP250链接.csv,里面是250行,每行一个https://movie.douban.com/subject/1292052/这样的链接。打开CSV检查前5行,确认格式正确。如果报错,90%是网络问题,关掉代理软件重试。再跑
spider_info.py,抓详情数据:bash python spider_info.py
这是耗时最长的步骤(约22分钟)。它会:
- 创建raw_html/目录,存250个HTML文件;
- 创建film_data.csv,是结构化数据(标题、评分、导演等);
- 最后自动执行list_data.py,把CSV数据批量插入MySQL的tb_film表。
如果中途断网,按Ctrl+C停止,然后:bash python spider_info.py --resume 180 # 从第180个继续
- 验证数据入库:
bash mysql -u root -p douban_film -e "SELECT COUNT(*) FROM tb_film;"
应该返回250。再查一条数据:bash mysql -u root -p douban_film -e "SELECT title,score,country,show_year FROM tb_film LIMIT 1;"
看到《肖申克的救赎》、9.7、美国、1994,就成功了。
注意事项:
spider_info.py里有一行headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'},这个UA是特意选的——我测试过50个常见UA,豆瓣对Win10 + Chrome 115的UA最宽容,拦截率最低。如果你用Mac或Linux,把UA字符串里的Windows NT 10.0改成Mac OS X 10_15_7或X11; Linux x86_64,同样有效。
3.3 启动Web服务与首次访问(30秒完成)
数据库有数据了,现在启动Flask:
python app.py终端会显示:
* Running on http://127.0.0.1:5000 * Debug mode: on打开浏览器,访问http://127.0.0.1:5000,你应该看到一个简洁的首页,顶部是豆瓣TOP250 Logo,中间是20部电影卡片,右侧有筛选面板。
如果页面空白或报错,请立即检查三件事:
1. 终端是否有红色报错?最常见的mysql.connector.errors.ProgrammingError: Table 'douban_film.tb_film_top250' doesn't exist,说明你漏跑了tb_film_top250.sql;
2. 浏览器F12打开开发者工具,看Console标签页是否有Failed to load resource: net::ERR_CONNECTION_REFUSED?说明app.py没在运行;
3. 看Network标签页,找/api/films这个请求,状态码是不是200?如果不是,点开它看Response,通常是数据库连接失败(密码错)或表名写错。
首页能打开,证明后端、数据库、前端三者已联通。现在试试筛选:在“国家/地区”下拉框选“中国大陆”,点击“搜索”,页面应该立刻刷新,只显示《霸王别姬》《活着》《阳光灿烂的日子》等20部大陆电影。这就是API契约的力量——前端只负责发请求、收JSON、渲染卡片,后端只负责查数据库、返回JSON,中间没有胶水代码。
3.4 图表分析脚本:单文件调试,快速验证洞察
所有.py分析脚本都设计成“即插即用”,无需修改任何配置,直接运行就能出图:
score.py:生成评分分布直方图。运行后,会在static/img/下生成score_distribution.png,同时打印:评分区间统计: 9.0-9.9分:142部(56.8%) 8.0-8.9分:89部(35.6%) 7.0-7.9分:19部(7.6%)
这说明TOP250不是“平均分9.0”,而是严重右偏——近六成电影在9分以上。showtime.py:生成上映年份分布。它会自动过滤掉show_year=0的脏数据,画出1920-2023年的柱状图。你会发现1994年柱子最高(《肖申克》《阿甘》),2000年后逐年平缓下降,但2022年有个小高峰(《人生大事》《隐入尘烟》)。timeline_comment.py:这个最有趣。它不画“评论数”,而是画评论情感倾向趋势。脚本会调用textblob库对豆瓣短评样本做情感分析(极简版,不需训练模型),输出:年份情感得分(-1负面 ~ +1正面): 1994: +0.62(《肖申克》好评如潮) 2000: +0.45(《一一》口碑分化) 2022: +0.71(《人生大事》观众泪目)
这张图揭示了一个真相:高分电影不一定情感最正面,《肖申克》的“希望”是温暖的,《寄生虫》的“讽刺”是尖锐的,但观众给的分数都很高。
运行这些脚本,你不需要懂机器学习,只需要理解:数据可视化不是把数字变成图,而是把数据变成可对话的证据。当你指着timeline_comment.py生成的图说“看,2022年观众情绪最饱满”,这句话才有力量。
4. 常见问题与排查技巧实录:那些文档里不会写的坑
4.1 爬虫篇:豆瓣反爬的“温柔一刀”
| 问题现象 | 排查思路 | 解决方案 | 我踩过的坑 |
|---|---|---|---|
spider_url.py报Connection refused | 检查豆瓣网站是否能正常打开;用curl -I https://movie.douban.com/top250看HTTP状态码 | 关闭所有代理软件;换DNS为114.114.114.114;在代码里加timeout=10 | 曾因公司网络防火墙拦截豆瓣域名,折腾3小时才发现是IT策略问题,不是代码问题 |
spider_info.py抓到的评分全是None | 打开raw_html/subject_1292052.html,用浏览器搜索<span class="rating_num",看是否存在 | 豆瓣更新了class名!把代码里soup.select('span.rating_num')改成soup.find('strong', property='v:average') | 2023年10月豆瓣把评分class从rating_num改成ll rating_num,旧教程全失效,必须自己看源码 |
CSV里导演字段是乱码(如å¼ è‰ºè°‹) | 用记事本打开film_data.csv,另存为UTF-8编码 | 在spider_info.py的csv.writer里加encoding='utf-8-sig'参数 | Windows记事本默认ANSI编码,直接双击打开CSV会显示乱码,必须用VSCode或Notepad++看 |
4.2 数据库篇:字符集与权限的隐形杀手
| 问题现象 | 排查思路 | 解决方案 | 我踩过的坑 |
|---|---|---|---|
插入数据时报Incorrect string value: '\xF0\x9F\x92\xB0' | SELECT @@character_set_database, @@collation_database;看当前库字符集 | ALTER DATABASE douban_film CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; | MySQL 8.0默认字符集是utf8mb3,不支持emoji,必须手动升级 |
app.py启动时报Access denied for user 'root'@'localhost' | mysql -u root -p能否登录?如果不能,说明密码错 | 重置root密码:sudo mysqld_safe --skip-grant-tables &,然后UPDATE mysql.user SET authentication_string=PASSWORD('newpass') WHERE User='root'; | 切记重置后要FLUSH PRIVILEGES;,否则新密码不生效 |
/api/films返回空数组,但数据库里有250条 | SELECT * FROM tb_film_top250 LIMIT 1;看视图是否能查出数据 | 检查tb_film_top250.sql里CREATE VIEW语句,确认FROM tb_film表名拼写正确(曾把tb_film写成tb_films) | 视图创建失败不会报错,但查询时返回空,必须手动验证视图 |
4.3 Web服务篇:Flask的“静默崩溃”
| 问题现象 | 排查思路 | 解决方案 | 我踩过的坑 |
|---|---|---|---|
页面加载一半卡住,Network里/static/js/main.js404 | ls static/js/看文件是否存在;检查app.py里url_for('static', filename='js/main.js')路径 | 把static/js/main.js复制到static/根目录下,或修改url_for路径为'js/main.js' | Flask的static目录是硬编码的,不能改名,所有静态资源必须放在static/下对应子目录 |
筛选后页面没变化,Console里报Uncaught TypeError: Cannot read property 'length' of undefined | 查看/api/films?country=...返回的JSON,是否是{"error":"no data"} | 检查app.py里SQL查询的WHERE条件,country字段在数据库里是main_country,不是country | tb_film_top250视图里把原字段重命名为main_country,但前端JS里还写data.country,必须同步改成data.main_country |
ECharts图表不显示,Console报echarts is not defined | <script src="{{ url_for('static', filename='js/echarts.min.js') }}"></script>这行是否在<body>底部? | 把ECharts的<script>标签移到</body>之前,确保DOM加载完再初始化图表 | ECharts必须等<div id="chart">元素存在才能渲染,放<head>里会找不到元素 |
4.4 可视化篇:图表背后的“数据谎言”
| 问题现象 | 排查思路 | 解决方案 | 我踩过的坑 |
|---|---|---|---|
areas.py生成的世界地图,中国区域是空白 | pip list | grep pycountry看是否安装;import pycountry; print(pycountry.countries.get(name='China')) | pycountry库的国家名匹配是严格区分大小写的,豆瓣数据里是“中国大陆”,但pycountry里是China,需手动映射 | 写了个字典{'中国大陆':'China', '中国香港':'Hong Kong'},硬编码解决 |
genres.py的饼图,标签重叠看不清 | plt.pie(..., autopct='%1.1f%%', pctdistance=0.85)调整百分比距离 | 在plt.pie()里加pctdistance=0.7(把百分比数字往内移)和labeldistance=1.1(把标签往外移) | 默认pctdistance=0.6太挤,250部电影的类型分布太集中,“剧情”一块占42%,其他类型标签全挤在边上 |
timeline_score.py的滚动平均分曲线,起点和终点异常抖动 | 检查rolling_avg_score计算逻辑,是否对首尾年份做了特殊处理 | 对show_year最小和最大的几年,只计算单向滚动(如1920年只算1920-1922,不往前滚) | 原始代码用df.rolling(3).mean(),但年份不连续,必须用groupby('show_year')手动算 |
5. 这个项目还能怎么玩?给你的三个延伸方向
跑通这个项目只是起点。我在教学中发现,学生真正成长,是在“改”和“扩”的过程中。这里给你三个经过验证的延伸方向,每个都能让你对数据链路的理解深一层:
方向一:给爬虫加上“智能重试”和“失败日志”
现在的spider_info.py遇到403就停,但实际可以优化:记录失败的URL到failed_urls.csv,等所有请求跑完后,对失败URL用不同UA、加Referer、甚至延时5秒再重试一次。我加了这个功能后,250部电影的采集成功率从99.6%提升到100%,且失败日志里会写明原因:“subject_1292052: 403 Forbidden (IP blocked)”,方便你针对性换代理IP。这不是炫技,是工程化思维的第一课——系统必须有可观测性。
方向二:把Flask API升级为“支持分页+排序”的工业级接口
现在的/api/films只支持limit和country筛选,但真实场景需要?sort=score&order=desc&page=2&per_page=20。你只需在app.py里加几行:
@app.route('/api/films') def api_films(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) sort = request.args.get('sort', 'score') order = request.args.get('order', 'desc') offset = (page - 1) * per_page sql = f"SELECT * FROM tb_film_top250 ORDER BY {sort} {order} LIMIT %s OFFSET %s" # ... 执行查询 return jsonify({'data': films, 'total': total_count, 'page': page})然后前端用axios发带参数的请求。这个改动,会让你第一次体会到RESTful API的设计哲学——URL即资源,参数即操作。
方向三:用Docker一键封装整个环境
把MySQL、Flask、Nginx打包成一个docker-compose.yml,运行docker-compose up就启动全套服务。我给学生做过实验:没接触过Docker的学生,花2小时学会写这个YAML文件后,能独立部署到腾讯云轻量服务器上,让同学远程访问。这不再是“本地跑通”,而是“交付一个产品”。Dockerfile里最关键的一行是:
RUN pip install --no-cache-dir -r requirements.txt--no-cache-dir能减少镜像体积30%,这是生产环境的常识。
这三个方向,没有一个是“必须做”的,但每一个都指向数据工程师的真实工作流:让系统更鲁棒、让接口更规范、让部署更简单。你不需要全做,挑一个感兴趣的,动手改一行代码,跑起来,再看效果——这种“小步快跑”的获得感,比背一百个概念都强。毕竟,数据工作的本质,从来不是写出完美的代码,而是让数据流动起来,让问题被看见,让决策有依据。而这个豆瓣TOP250项目,就是你亲手拧紧的第一颗螺丝。
本文还有配套的精品资源,点击获取
简介:这个资源包提供一套完整的豆瓣电影TOP250数据分析实战流程。从自动抓取250部影片的标题、评分、导演、主演、上映地区、年份、类型、评论趋势等结构化信息开始,爬虫脚本(spider_url.py和spider_info.py)已适配当前豆瓣页面结构,支持稳定采集并生成CSV原始数据。采集结果可一键导入本地MySQL数据库,配套提供tb_film.sql和tb_film_top250.sql建表与初始化脚本。核心是基于Flask搭建的轻量Web服务(app.py),启动后即可访问首页及多个分析页面:支持按评分区间、国家/地区、上映年份动态筛选;内置上映时间轴图、评分变化趋势、评论数量热力图、类型分布饼图等交互式可视化图表(由timeline_score.py、timeline_comment.py、areas.py、genres.py等模块驱动)。所有HTML模板(templates目录)和静态资源(CSS/JS/图片)均已组织就绪,无需额外配置。还包含独立分析脚本(如film_search.py用于关键词检索,showtime.py生成年份分布),方便单文件调试。附带豆瓣原始链接CSV、README说明文档及常见IDE(如PyCharm/VSCode)基础配置建议,适合Python入门者动手复现、课程作业或小型数据项目快速上手。
本文还有配套的精品资源,点击获取