简介面向计算机专业毕业生与Python学习者的豆瓣电影数据分析与可视化毕业设计完整方案整合Flask后端、ECharts可视化、爬虫采集与pandas数据处理配套MySQL数据库与论文文档可缓解选题无思路、代码跑不通、论文缺素材等痛点。包内共1054个文件以688个Python源码为主体辅以pyc缓存、html/css/js前端页面、csv数据样本与sql建库脚本另有jpg/png图表截图及docx论文压缩包约30.07MB目录按爬虫、数据处理、Web服务与静态资源分层组织。已有256人学习下载。内容贯通Pycharm环境搭建、数据抓取、清洗入库到多维动态图表展示的完整链路可复现评分分布、类型占比等分析视图并借助论文与SQL脚本快速搭建可运行系统。1. 一份能跑通的 python毕业设计影评数据从抓到图要经过几道手做 python毕业设计 最容易被卡住的地方往往不是代码写不出来而是数据一抓回来就不知道怎么变成图上那条曲线。这份基于 Flask ECharts 爬虫 pandas 的完整包解决的就是这一整条链路从某影评社区的公开页面抓片名、评分、类型、年份、评价人数用 pandas 做 python数据分析把结果整理成接口能吐的 JSON再交给 ECharts 做 python数据可视化前后端一体附建表 SQL 脚本和论文正文。适合三类人要交毕设但缺一个能端到端跑通的骨架的手上有一堆 CSV 却只会df.describe()的以及需要一份现成表结构和论文章节框架对着改的。下面按我自己重跑一遍的顺序拆。2. 骨架拆解Flask 蓝图、三张表与前后端 JSON 契约拿到这种包第一件事不是急着pip install而是先看目录职责清不清楚。很多毕设项目把爬虫、清洗、路由全塞进一个app.py跑是能跑改一处崩三处。这个包的分层是相对规整的采集、分析、展示三段各自独立中间靠数据库和 JSON 接口解耦这也是我认为它比同类资源更值得拆的原因。2.1 三条流水线各自负责什么边界在哪先把目录摆出来看职责。实际文件名可能略有差异但分层逻辑基本一致# 项目结构按职责划分不是按文件类型划分 movie_analysis/ ├── app.py # Flask 入口只做 create_app 和蓝图注册 ├── config.py # 数据库连接串、分页上限、分箱边界 ├── spider/ │ ├── fetch.py # 请求封装UA、超时、重试、限速 │ ├── parse.py # 页面解析输出 dict │ └── store.py # 幂等入库 ├── analysis/ │ ├── clean.py # pandas 清洗产出干净宽表和类型表 │ └── metrics.py # 指标计算直接产出图表用的结构 ├── api/ │ └── routes.py # /api/* 蓝图 ├── static/js/*.js # ECharts 渲染 ├── templates/*.html └── sql/schema.sql # 建表脚本三条流水线的边界是这样的spider/是离线跑的跑一次落一次库绝不放在请求里analysis/也是离线或半离线算出结果后要么写表要么缓存只有api/和前端是实时响应的而且只读数据库不发网络请求。这个边界看着是常识但翻车的恰恰是它。我见过不少同学为了“数据实时”在/api/rating_dist里现爬现算本地测试几十条数据看不出来一到演示环节页面转圈十几秒直接超时。常见做法是采集和展示彻底分开采集脚本手动或定时跑接口只做SELECT加轻量聚合页面响应压在 200ms 以内答辩演示才稳。config.py里值得单独拎出来的是分页上限。列表接口一定要有limit上限我一般设 200否则前端一个手滑传limit999999几万行 JSON 直接打到浏览器页面卡死还不好排查。2.2 三张表主表、类型表、快照表SQL 脚本是整个包里最容易被忽略、却最影响后期改动的部分。表设计得对不对决定了你后面加一个图表是十分钟还是两天。表名作用关键字段注意点movie影片主表一行一部movie_id主键、rating、rating_count、release_year评分用 DECIMAL(3,1)别用 FLOATmovie_genre类型关联表一部片多行movie_idgenre联合主键类型必须拆行不能存成剧情/爱情comment短评表可选movie_id、content、created_at量大建议加movie_id索引metric_snapshot指标快照进阶用run_date、metric_key、metric_value见第 6 章主表建表语句大致长这样CREATE TABLE movie ( movie_id VARCHAR(16) PRIMARY KEY, -- 站点内唯一 id幂等键就用它 title VARCHAR(128) NOT NULL, release_year SMALLINT NULL, -- 冷门片没有年份允许 NULL rating DECIMAL(3,1) NULL, -- 0.0~10.0一位小数 rating_count INT DEFAULT 0, director VARCHAR(64) NULL, country VARCHAR(32) NULL, crawled_at DATETIME NULL, -- 最后一次抓取时间用于去重取最新 KEY idx_year (release_year), KEY idx_rating (rating) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;三个参数选择值得说明。rating用DECIMAL(3,1)而不是FLOAT是因为浮点在分组统计时会出现8.699999这种值GROUP BY rating直接多出一堆本不该存在的分组这个坑我在别的项目里踩过血泪经验。release_year允许NULL而不是默认 0因为WHERE release_year 2000这种条件里 0 会被当成合法年份混进来均值、占比全歪。movie_id用VARCHAR不用自增 INT是因为站点 id 可能带前缀或补零转成 INT 之后前端拼详情链接会对不上。类型表就是两列加联合主键别嫌它简单CREATE TABLE movie_genre ( movie_id VARCHAR(16) NOT NULL, genre VARCHAR(16) NOT NULL, PRIMARY KEY (movie_id, genre), KEY idx_genre (genre) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;联合主键顺手解决了重复插入问题同一部片的同一类型插两次会被数据库挡掉采集脚本不用自己判重。2.3 接口契约让后端直接按 ECharts 的字段名返回这一步是很多毕设的分水岭。做得粗糙的后端返回一堆[{name:..., num:...}]前端再写一层 map 转成xAxis.data和series.data做得顺手的后端直接把xAxis和series作为字段名吐出来前端setOption一字不改塞进去。# api/routes.py from flask import Blueprint, jsonify, request from analysis.metrics import rating_distribution, genre_share bp Blueprint(api, __name__, url_prefix/api) # 分箱边界写死在这里前后端共用一套口径别让前端再算一遍 BINS [0, 6, 7, 8, 9, 10.1] LABELS [6分以下, 6-7分, 7-8分, 8-9分, 9分以上] bp.get(/rating_dist) def rating_dist(): try: data rating_distribution(BINS, LABELS) return jsonify({code: 0, msg: ok, xAxis: LABELS, series: data}) except Exception as e: # 接口永远返回同一套外壳前端只判断 code return jsonify({code: 500, msg: str(e), xAxis: [], series: []}) bp.get(/movies) def movies(): limit min(int(request.args.get(limit, 20)), 200) # 上限 200防止拖库 offset max(int(request.args.get(offset, 0)), 0) return jsonify({code: 0, data: query_movies(limit, offset)})这段代码有两个设计取舍。第一接口外壳统一code/msg/data异常也走同一个外套前端只写一次错误分支不用每个图表都 try 一遍。第二limit用min(..., 200)夹住offset用max(..., 0)兜住负数避免LIMIT -1在某些驱动下被当成“不限”。分箱边界放在后端而不是前端是因为同一份论文里图表和正文的区间必须一致写在两处早晚会对不上。另外app.py里记得把 JSON 的中文编码关掉ensure_ascii这个放到第 5 章展开说因为它是最常见的“图表能出来但中文全是方块”的元凶。3. 采集层落地requests 会话封装、字段抽取与幂等入库采集这层是整个系统里唯一会跟外部环境打交道的部分也是最脆的一环。我的建议是把它当成一次性工具来写能跑通、能续跑、出错能定位就够了不用追求工业级框架。3.1 会话封装超时、重试、限速三件套直接requests.get(url)的写法在本地跑十次有九次能成跑五百次就开始随机报错。原因很简单没有超时、没有重试、没有间隔。# spider/fetch.py import time, random, requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry HEADERS { User-Agent: (Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36), Accept-Language: zh-CN,zh;q0.9, } def make_session(): s requests.Session() s.headers.update(HEADERS) # 只对连接错误和 5xx 重试4xx 重试没意义只会白等 retry Retry(total3, backoff_factor0.8, status_forcelist[500, 502, 503, 504]) adapter HTTPAdapter(max_retriesretry, pool_maxsize4) s.mount(http://, adapter) s.mount(https://, adapter) return s def get_html(session, url, timeout(3.05, 10), sleep_range(0.8, 1.8)): time.sleep(random.uniform(*sleep_range)) # 随机间隔比固定 sleep 更稳 resp session.get(url, timeouttimeout) resp.raise_for_status() if not resp.encoding or resp.encoding.lower() iso-8859-1: resp.encoding resp.apparent_encoding # 页头没给 charset 时兜底 return resp.text参数逐个说清楚。timeout(3.05, 10)是元组形式前面是连接超时、后面是读取超时只写一个数字的话 requests 会把连接和读取都设成那个值遇到慢响应容易误杀。total3加backoff_factor0.8表示失败后按 0.8、1.6、3.2 秒退避重试这个退避比手写time.sleep(1)循环更省心。sleep_range用区间随机而不是固定值是为了让请求节奏别太规律——同时也是给目标站点留余地采集频率控制在每秒一条上下个人学习用途完全够用。raise_for_status()这一行别省。不写的话404 页面也会返回 HTML解析出来全是空字段日志里一声不响等到入库才发现两万条空记录。3.2 字段抽取用正则切文本块比层层选择器稳影评站点的详情页结构大同小异关键信息集中在一段文本里导演、编剧、类型、制片国家、年份用 BeautifulSoup 一层层选容易因为页面改版全盘失效直接对整段文本跑正则反而更抗造。# spider/parse.py import re from bs4 import BeautifulSoup def parse_detail(html: str, movie_id: str) - dict: soup BeautifulSoup(html, lxml) info_node soup.select_one(#info) # 信息区整块 text info_node.get_text(\n, stripTrue) if info_node else def pick(pattern, defaultNone): m re.search(pattern, text) return m.group(1).strip() if m else default title_node soup.select_one(h1 span) year pick(r\((\d{4})\)) or pick(r(\d{4})) rating_raw pick(r(\d\.\d), 0.0) # 一位小数 count_raw pick(r([\d,])\s*人评价, 0) return { movie_id: movie_id, title: title_node.get_text(stripTrue) if title_node else , release_year: int(year) if year else None, # 抽不到就 None别填 0 rating: float(rating_raw), rating_count: int(re.sub(r\D, , count_raw) or 0), director: pick(r导演:\s*(.)), country: pick(r制片国家/地区:\s*(.)), }逻辑说明三处。第一pick统一返回None而不是空字符串下游 pandas 判断缺失只要一种写法。第二评价人数里有千分位逗号re.sub(r\D, , ...)先去干净再转 int否则12,345会直接抛异常。第三release_year抽不到就None这和 2.2 节表里允许 NULL 是对应的——填 0 会把“年份分布”图的横轴拉到 0 年图直接糊成一片。select_one之后一定要判空再.get_text()。我见过最典型的翻车是详情页有个别条目结构不同h1 span不存在None.get_text()抛异常整个批次中断前面抓的全白跑。3.3 幂等入库重复抓同一部片不能炸主键采集跑一半断了是常态重新跑的时候一定会重复抓已经入库的片子。所以插入语句必须写成幂等的。# spider/store.py —— MySQL 版本 import pymysql from config import DB_CONFIG UPSERT_MOVIE INSERT INTO movie (movie_id, title, release_year, rating, rating_count, director, country, crawled_at) VALUES (%(movie_id)s, %(title)s, %(release_year)s, %(rating)s, %(rating_count)s, %(director)s, %(country)s, NOW()) ON DUPLICATE KEY UPDATE rating VALUES(rating), rating_count VALUES(rating_count), crawled_at VALUES(crawled_at) def save_batch(rows, batch_size200): conn pymysql.connect(**DB_CONFIG, charsetutf8mb4, autocommitFalse) try: with conn.cursor() as cur: for i, row in enumerate(rows, 1): cur.execute(UPSERT_MOVIE, row) if i % batch_size 0: conn.commit() # 分批提交断点损失可控 conn.commit() finally: conn.close()关键在ON DUPLICATE KEY UPDATE后面只更新“会变的字段”评分、评价人数、抓取时间。片名、导演、年份这些静态字段不覆盖万一某次抓到脏数据也不会把已有的正确记录冲掉。分批提交的batch_size200是个经验值太小事务开销大太大断一次回滚两百条以上就心疼。用 SQLite 的话语义一样写法换成INSERT INTO movie (movie_id, title, release_year, rating, rating_count, crawled_at) VALUES (?, ?, ?, ?, ?, datetime(now)) ON CONFLICT(movie_id) DO UPDATE SET rating excluded.rating, rating_count excluded.rating_count, crawled_at excluded.crawled_at;excluded是 SQLite 里代表“本次想插入的那行”的关键字等价于 MySQL 的VALUES()。这两个写法记住了以后任何采集任务都能安全重跑。4. pandas 指标口径评分分箱、类型拆分与年份趋势怎么算才站得住数据入库之后进入分析层。这一层真正难的不是 API 调用而是“口径”——同一个“类型占比”按影片数算和按评价人数加权算结果能差出十几个百分点。论文里被问到的往往就是这一句“你这个百分比是怎么来的”。4.1 清洗流水线五步之内把脏数据压下去# analysis/clean.py import pandas as pd def load_and_clean(csv_path: str): df pd.read_csv(csv_path, dtype{movie_id: str}) # id 必须按字符串读否则前导零丢失 # 1) 去重同一 id 保留最后一次抓取评分人数更全 df df.sort_values(crawled_at).drop_duplicates(movie_id, keeplast) # 2) 年份1900 以前和未来年份都当脏数据 df[release_year] pd.to_numeric(df[release_year], errorscoerce) df.loc[(df.release_year 1900) | (df.release_year 2025), release_year] pd.NA # 3) 评分超出 0~10 的直接丢弃 df df[df[rating].between(0, 10)].copy() # 4) 类型拆行一行一个类型供占比统计用 genre (df[[movie_id, genres]] .assign(genresdf[genres].fillna().str.split(/)) .explode(genres)) genre[genres] genre[genres].str.strip() genre genre[genre[genres] ! ].drop_duplicates([movie_id, genres]) return df.reset_index(dropTrue), genre.reset_index(dropTrue)五步里有两个必须解释。第一步dtype{movie_id: str}不写的话 pandas 会把0012读成12后面 join 类型表一条都匹配不上。第四步explode之后行数会膨胀所以最后一定要drop_duplicates否则同一部片的同一类型出现两次占比直接翻倍——这个问题放到第 5 章细说。年份用pd.to_numeric(..., errorscoerce)而不是astype(int)是因为原始 CSV 里可能存在空串或带“年”字的脏值astype遇到就整列报错coerce会把它们变成 NaN 再统一清洗。4.2 四个指标的算法与参数指标计算方式关键参数常见误用评分分布pd.cut分箱后计数rightFalse区间左闭右开用默认rightTrue8 分被算进 7-8 档类型占比类型表 groupby 计数按片数 或 按评价人数加权主表 join 后算行重复导致总数虚高年份趋势按release_year分组求均值/数量只统计样本量 ≥30 的年份把只有 1 部片的年份也画上去曲线剧烈抖动高分导演榜按导演分组过滤作品数作品数 ≥3 才进榜不设阈值榜首全是只拍过一部片的导演落成代码是这样# analysis/metrics.py import pandas as pd def rating_distribution(df: pd.DataFrame, bins, labels): # rightFalse区间左闭右开8.0 落在 8-9分 s (pd.cut(df[rating], binsbins, labelslabels, rightFalse) .value_counts() .reindex(labels, fill_value0)) # reindex 保证空档位也占位 return [int(v) for v in s.values] def genre_share(genre_df: pd.DataFrame, movie_df: pd.DataFrame, weighted: bool False, top_n: int 10): g genre_df.merge(movie_df[[movie_id, rating_count]], onmovie_id, howleft) if weighted: s g.groupby(genres)[rating_count].sum() # 按观众关注度加权 else: s g.groupby(genres)[movie_id].count() # 按市场供给计数 s s.sort_values(ascendingFalse).head(top_n) return [{name: k, value: int(v)} for k, v in s.items()] def year_trend(df: pd.DataFrame, min_samples: int 30): g df.dropna(subset[release_year]).groupby(release_year)[rating] stat g.agg([mean, count]).query(count min_samples) return { xAxis: [int(y) for y in stat.index], series: [round(float(v), 2) for v in stat[mean]], }reindex(labels, fill_value0)这行看着不起眼实际很关键如果某个评分区间一部片都没有比如 9 分以上value_counts会直接跳过这一档前端 x 轴就少一根柱子视觉上“9 分以上没人”变成了“根本没有 9 分以上这个档”。query(count min_samples)里的是 pandas 引用外部变量的写法阈值 30 是经验值样本太少的年份均值没有统计意义画上去只会让折线在早年区间上下乱跳。加权与否这件事论文里最好两个都放按影片数统计回答“市场上什么类型最多”按评价人数加权回答“观众实际在看什么”两个结论经常不一致反而是一个不错的分析点。4.3 从 DataFrame 到 ECharts别在前端做第二次计算后端算完直接吐图表结构前端只负责渲染这条约定能省掉大量“图上的数和论文里的数不一样”的扯皮。// static/js/rating.js fetch(/api/rating_dist) .then(r r.json()) .then(res { if (res.code ! 0) { console.error(接口异常:, res.msg); return; } const dom document.getElementById(ratingChart); const chart echarts.init(dom); // 容器必须有确定高度否则画不出来 chart.setOption({ tooltip: { trigger: axis }, grid: { left: 48, right: 24, top: 32, bottom: 40 }, xAxis: { type: category, data: res.xAxis }, yAxis: { type: value, name: 影片数 }, series: [{ type: bar, data: res.series, barWidth: 55%, itemStyle: { borderRadius: [4, 4, 0, 0] } }] }); // 窗口尺寸变了必须手动 resizeECharts 不会自动跟随 window.addEventListener(resize, () chart.resize()); });echarts.init之前容器必须已经完成布局。写在head里不套DOMContentLoaded的话拿到的clientHeight是 0不报错但屏幕上一片空白这个现象下面还会遇到。grid四个方向留白建议手动给默认值在中文标签较长时容易被裁掉。5. 避坑与排查图表空白、中文乱码、抓二十条就停的五条记录这几条都是我自己重跑时真实撞上的按“现象 → 原因 → 解决”写遇到同款可以照着对。5.1 页面能打开图表区域一片白现象浏览器访问首页正常标题、导航都在图表容器位置是一块空白控制台没有红色报错。原因大概率有三个容器 CSS 没给高度echarts.init在 DOM 布局完成前执行拿到的高度是 0或者 JS 文件 404echarts根本没加载到。解决顺序是先在 F12 的 Network 面板看echarts.min.js和rating.js是不是 200再看 Elements 里#ratingChart的计算高度是不是 0。CSS 里补一句#ratingChart { width: 100%; height: 360px; }把init包进DOMContentLoaded九成能好。剩下那一成是接口返回了code: 500但前端没判断res.series是空数组图上自然什么都没有——所以 4.3 那段if (res.code ! 0)判断别省。5.2 接口返回的中文全变成\u8bc4\u5206现象浏览器直接访问/api/rating_dist看到的 JSON 里中文全是 Unicode 转义图表的 x 轴标签要么显示转义串要么显示成方块。原因Flask 的 JSON 序列化默认ensure_asciiTrue会把非 ASCII 字符全部转义。浏览器fetch之后r.json()其实能正确还原但如果页面meta charset不是 UTF-8或者用innerHTML直接塞了未解析的字符串就会露出来。解决Flask 2.3 以后用app.json.ensure_ascii False2.2 及更早版本在配置里写app.config[JSON_AS_ASCII] False。新版配置项已经废弃照旧教程写会静默失效这个坑很多人卡半天。顺便把 HTML 头部的meta charsetutf-8和数据库连接串的charsetutf8mb4一起核对三处编码统一了才不会出现半截乱码。5.3 日志全是 200但抓了二十来条就没新数据了现象采集脚本跑完控制台没有异常数据库里只有二十几条记录和页面上显示的总数差了两个数量级。原因通常是分页参数猜错了。不少站点的列表页不是?page2而是?start20limit20这种偏移量形式脚本里照抄page参数每次请求拿到的都是第一页二十条正好是一页的量。另一种情况是请求被引导到了一个提示页status_code依然是 200但内容不是目标数据。解决手工在浏览器里翻到第二页把两页 URL 复制出来逐字符对比差异点就是真正的分页参数同时在解析函数里加一句长度断言if len(items) 10: logger.warning(疑似非目标页实际长度 %s, len(html))让问题在日志里现形而不是等到入库才发现。采集频率按 3.1 节的区间控制个人学习用途下没必要压得太狠。5.4 类型占比加起来 130%饼图没法看现象饼图各个扇区百分比相加明显超过 100%或者“剧情”这一项的数值比影片总数还大。原因一部电影有多个类型直接在主表上groupby(genres)时剧情/爱情被当成一个独立类型统计出来是一堆长尾改成explode拆行之后如果没做drop_duplicates或者算占比时用的是“类型行数 ÷ 影片总数”分子分母口径不一致就会出现超过 100% 的结果。解决分母必须用“类型行总数”而不是“影片总数”或者干脆在图上标“按类型标签计数一部影片可计入多个类型”。4.1 里的drop_duplicates([movie_id, genres])和 4.2 里的genre_share配套使用两个都到位才不会重复计数。5.5 换台机器就跑不起来ModuleNotFoundError和中文路径现象在自己电脑上一切正常拷到同学机器或者实验室机房里python app.py直接抛ModuleNotFoundError: No module named flask或者 SQLite 报unable to open database file。原因有两个。依赖没锁版本pip install flask装到最新版而代码写的是旧版 API比如前面提到的JSON_AS_ASCII行为对不上。SQLite 连接串写成了绝对路径D:\毕设\db.sqlite3换台机器盘符不存在或者路径里带中文某些环境直接打不开。解决导出一份requirements.txt把主版本钉住Flask2.3.*、pandas2.*、requests2.*这种粒度就够数据库路径改成基于__file__的相对路径例如os.path.join(os.path.dirname(__file__), data, movie.db)。机房机器上如果控制台打印中文报UnicodeEncodeError那是终端编码问题不是程序问题把日志里的中文换成英文关键字更省事。6. 把脚本变成可复跑任务指标快照与三条对账校验这套包跑通之后最容易被忽略的是“第二天再跑一次会怎样”。页面结构一改、某天网络抖动、某次只抓到半页脚本依然会正常退出并写库页面上依然有图只是图错了。我在别的项目里吃过这个亏后来养成一个习惯每次采集完先存一份指标快照下次跑之前先对账。做法很简单加一张表每次跑完写一行# analysis/snapshot.py import datetime as dt import pandas as pd from sqlalchemy import create_engine, text def save_snapshot(engine, metrics: dict, run_date: str | None None): run_date run_date or dt.date.today().isoformat() rows [{run_date: run_date, metric_key: k, metric_value: float(v)} for k, v in metrics.items()] pd.DataFrame(rows).to_sql(metric_snapshot, engine, if_existsappend, indexFalse) def check_drift(engine, metrics: dict, threshold: float 0.2): last_date pd.read_sql(text(SELECT MAX(run_date) AS d FROM metric_snapshot), engine)[d][0] if last_date is None: return last pd.read_sql(text(SELECT metric_key, metric_value FROM metric_snapshot WHERE run_date :d), engine, params{d: last_date}) base dict(zip(last[metric_key], last[metric_value])) for k, v in metrics.items(): old base.get(k) if old and abs(v - old) / old threshold: # 偏差超 20% 基本是采集出了问题 print(f[告警] {k}: {old:.2f} - {v:.2f}请检查采集结果)threshold设 0.2 是经验值正常的内容增量一天不会超过两成一旦超过八成是分页断了或者解析规则失效先别急着出图回去看日志。metric_key我一般固定存四个movie_total、avg_rating、year_max、genre_top1_ratio分别对应总量、均值、最新年份、头部类型占比这四项能覆盖绝大多数“抓歪了”的情况。除了快照还有三条对账我每次都走一遍。总量对账入库条数跟列表页显示的总数比差 5% 以内算正常差一半就是分页没翻完。抽样人工随机抽 20 条逐条点开原页面比评分和年份这一步最土但最有效字段错位一眼就能看出来。分布合理性正常影评站点的评分均值落在 6 到 8 之间如果某次跑出来是 2.3那基本不是数据结论而是评分字段抓到了“星级”或者“时长”。这三条加起来不到十分钟比事后改论文省事得多。从那以后我每次跑完采集都强制先看一眼快照表和对账结果再做图、再改论文绝不跳过。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?