
做毕设那段时间我几乎把考研相关的数据网站翻了个底朝天最后实在忍不了“分数线靠猜、院校靠瞎选”的节奏干脆用自己攒下的技术栈做了一套考研分数线预测与院校推荐系统。整套项目用的是Hadoop做数据底座PySpark干清洗和建模的脏活累活Scrapy负责把分数线、报录比、招生简章这些数据一项项抓下来最终落成一个能跑、能看、能答辩的系统。这篇就把我从零到一搭这套项目的思路、代码、踩坑、以及答辩前最该注意的东西全梳理一遍给正在做同类毕设、或者单纯想用大数据技术栈做点实事的同学做个参考。这套东西能解决的问题其实很直接一是把考研择校时最耗精力的“查数据”环节自动化二是基于历史分数线走势和自身条件给出“这个分数能不能上、哪类院校更适合你”的判断依据。它适合的你如果是正在选毕业设计题目、想用Python爬虫和大数据组件做一套有真实业务场景的系统那这篇内容基本都能直接对号入座。1. 项目整体思路与架构选型1.1 毕设选题背景与核心需求拆解考研数据有个很尴尬的特点就是“看着全网都是实际上想拿到能用的结构化数据得靠自己”。研招网、各学校研究生院官网、各类考研信息聚合平台数据大多以公告、网页表格、PDF附件的形式存在根本没有一个公开的完整数据集可以直接下载。与此同时分数线预测这件事又高度依赖历史数据积累院校推荐更是在多维度数据对比之上才能得出的结论。所以我把毕设的核心需求拆成了四点数据层面需要一套可持续运行的爬虫抓取目标院校的历年复试分数线、招生人数、报录比、考试科目等信息并做清洗和结构化存储。预测层面基于历年分数线的时间序列特征、当年报考热度、招生计划变化等因素用PySpark MLlib训练回归模型预测下一年的复试分数线区间。推荐层面根据用户输入的本科背景、目标专业、期望城市、预估分数等条件结合院校档位和录取难度输出个性化推荐列表。展示层面把爬取结果、预测数据和推荐结果通过Web界面展示让用户不用接触底层代码也能查询和获取结论。这四个需求一明确技术选型也就顺势出来了。数据量大、格式杂用Hadoop解决存储和分布式处理的问题数据清洗、特征工程、模型训练需要反复迭代用PySpark能省下大量写MapReduce的时间爬虫这块Scrapy的高并发下载能力和中间件机制足以应对中小规模网站的抓取需求。1.2 为什么选Hadoop PySpark Scrapy这套组合很多同学一听到毕设要用Hadoop第一反应是“这玩意是不是太重了”但我选它其实有很充分的理由。首先这个题目挂的是大数据方向Hadoop在评分时能直观体现分布式存储和计算的能力尤其是把爬虫抓下来的原始日志、网页快照、JSON串都丢到HDFS上再通过Spark SQL做ETL这个数据链路在答辩时非常加分。PySpark的选择则更多是出于效率考虑。考研分数线数据量级虽然撑不起“海量”这个词但数据源多、字段杂、脏数据多用Spark的DataFrame API做清洗和特征拼接比纯Pandas高效得多而且能顺理成章地和HDFS、YARN打通形成完整的离线处理链路。预测部分我用的是PySpark MLlib里的随机森林回归和线性回归一方面算法本身适合表格型数据另一方面MLlib的Pipeline机制非常适合做特征处理与模型训练的流程化管理。Scrapy的定位就更纯粹了它就是负责把“源头活水”引进来。相比requestsBeautifulSoup的手写爬虫Scrapy自带的并发调度、去重、中间件、导出管道能让我把更多精力放在数据解析和反爬应对上这一点在做多院校、多数据源采集时节省了大量时间。数据链路整体是这样的Scrapy爬虫采集 - 原始数据落HDFS/MySQL - PySpark ETL清洗 - 特征工程 - 模型训练 - 预测结果 - Redis/MySQL存储 - Web端查询与推荐展示1.3 系统整体架构与数据流向整套系统我从功能上拆成了四个相对独立的模块数据采集层、数据存储与处理层、算法模型层、Web应用层。四层之间通过数据接口衔接彼此不过度耦合这样最大的好处是后期替换或优化任何一个模块都不影响其他模块运行。数据采集层主要负责三块内容历年国家线和34所自划线院校复试线、各院校招生专业目录和招生人数、研招网及社区论坛上的报考热度数据。采集到的数据分别以原始网页快照和解析后的结构化表格两种形式落盘原始快照进HDFS留作审计和复盘结构化数据进MySQL方便上游直接读取。数据处理层是整套系统的技术核心。PySpark作业从MySQL和HDFS读取数据后依次完成缺失值处理、异常值剔除、字段统一映射、时间序列对齐等步骤最终生成两张核心表——院校分数线事实表和院校特征维度表。预测模型基于事实表训练推荐系统则把两张表做特征拼接后供算法调用。这样的架构设计让我在开发时能做到“每一层都能单独讲清楚”而这一点恰恰是毕设答辩时老师最爱深挖的地方。你能说清数据从哪里来、经过什么处理、最终到哪里去比堆砌一堆概念但要解释半天“这段代码在干嘛”要有说服力得多。2. 爬虫数据采集层的设计与实战2.1 数据源分析与爬虫方案设计爬虫部分是整个项目的起点也是最容易翻车的地方。我的做法是先列清楚到底要采哪些数据再逐个分析每个目标网站的结构最后才动手写爬虫代码。考研数据里我最终圈定了四个核心数据维度历年国家线、34所自划线院校复试分数线含学术型/专业型、单科线/总分线。各院校招生专业目录里的拟招生人数、考试科目、学制、研究方向。院校基本信息所属省份、城市、985/211/双一流标签、综合排名。报考热度信息搜索引擎指数、考研论坛讨论量作为替代指标。这里面最常规的是研招网和学校研究生院官网其次是各类考研资讯站。不同站点的反爬强度差异非常大有的直接加个User-Agent就能拿数据有的则需要处理JS动态渲染、Cookie校验、访问频率限制。所以我没有用一个通用爬虫一把梭而是针对不同站点分别设计了爬虫策略。2.2 Scrapy爬虫核心代码实现以抓取某考研资讯站历年分数线为例我的Scrapy爬虫核心逻辑分三个文件items.py定义数据字段spiders里的脚本负责页面解析pipelines.py负责数据入库。整个过程用到的关键代码大致是这样的# items.py import scrapy class PostgraduateItem(scrapy.Item): school_name scrapy.Field() # 院校名称 major_name scrapy.Field() # 专业名称 year scrapy.Field() # 年份 total_score scrapy.Field() # 总分线 politics_score scrapy.Field() # 政治单科线 english_score scrapy.Field() # 英语单科线 math_score scrapy.Field() # 数学单科线 major_course_score scrapy.Field() # 专业课单科线 source_url scrapy.Field() # 数据来源链接# spiders/score_spider.py import scrapy from postgraduate.items import PostgraduateItem class ScoreSpider(scrapy.Spider): name score_spider allowed_domains [example.edu.cn] start_urls [https://example.edu.cn/lnfsx/] def parse(self, response): # 页面中每个院校分数线表格对应一个tr for row in response.xpath(//table[classscore-table]//tr)[1:]: item PostgraduateItem() item[school_name] row.xpath(./td[1]/text()).get() item[major_name] row.xpath(./td[2]/text()).get() item[year] row.xpath(./td[3]/text()).get() item[total_score] row.xpath(./td[4]/text()).get() item[source_url] response.url yield itemitems.py里我特意加了source_url字段这个字段最初是写文档时顺手加的后来越用越觉得值。数据入库后一旦发现某个数字异常我可以直接溯源到原始网页排查效率大大提高。pipelines.py里我做了两件事一是数据校验遇到总分线和单科线明显不符合逻辑的记录直接丢弃二是去重以“院校专业年份”为唯一键重复数据不再入库。这里需要注意Scrapy自带的去重只对Request去重对item是不生效的所以item级去重必须自己在Pipeline里实现。2.3 反爬应对与爬取稳定性保障爬虫写出来不难难的是稳定地跑完整个数据采集周期。我采数据这段时间遇到最大的挑战是反爬策略五花八门。有的网站看User-Agent有的查Referer有的会限制单IP访问频率还有的用了JS动态渲染直接requests拿不到数据。针对这些问题我做了四件事配置Downloader Middleware随机轮换User-Agent和代理IP。设置Download Delay和并发数上限把对目标站点的访问压力控制在合理范围。对动态渲染页面用Scrapy配合Selenium或Playwright做渲染后的数据抓取。本地启动定时任务选择凌晨低峰时段进行增量爬取降低被封风险。# settings.py 关键配置 DOWNLOAD_DELAY 2 CONCURRENT_REQUESTS 8 DOWNLOADER_MIDDLEWARES { postgraduate.middlewares.RandomUserAgentMiddleware: 543, postgraduate.middlewares.ProxyMiddleware: 544, } ITEM_PIPELINES { postgraduate.pipelines.ValidationPipeline: 300, postgraduate.pipelines.MySQLPipeline: 400, }关于代理IP我个人的建议是如果你的数据源主要是学校官网这类反爬弱的站点完全没必要花大价钱买代理池本地IP加延时就能跑。只有面对风控严格的平台才需要代理而且代理质量对爬虫稳定性的影响非常大低价代理反而会频繁掉线导致采集中断。采集稳定性这块我还额外设计了一个断点续采机制。Scrapy本身支持通过JOBDIR参数恢复爬虫状态但我的数据量不大所以偷了个懒把已经成功入库的“院校专业年份”组合从MySQL里查出来在parse里直接判断并丢弃重复项。配合去重Pipeline双保险即使爬到一半崩了重启爬虫后也不会大量重复采集。3. 分数线预测模型的构建与训练3.1 预测目标与特征工程爬虫把数据采下来只是第一步接下来要回答的核心问题是怎么用这些历史数据预测下一年的分数线我的做法是把预测任务定义为一个有监督回归问题——用前N年的特征数据预测第N1年的总分线。先说预测目标。复试分数线这个东西受太多因素影响完全没有办法做到精准到个位数所以我在系统里输出的实际上是“预测总分置信区间”在Web端展示为“预测区间355-365分”而不是一个冷冰冰的定点数字。这样做既符合实际规律也不会因为预测偏差太大被用户质疑模型不靠谱。特征工程是这部分工作的重头戏。我清洗之后最终保留了这些特征历年总分线一阶差分反映分数变化趋势。近三年分数线的均值、标准差。当年国家线水平。招生人数的同比变化率。报考热度指数爬虫采集的讨论帖数量、搜索指数归一化结果。院校层次985/211/双一流标签编码。专业类型学硕/专硕编码。特征不是越多越好这是我在跑模型时最深刻的体会。一开始我把能加的特征全堆上去了结果模型在测试集上的表现反而不如精简后的版本。原因也很简单很多特征之间高度相关加入了冗余信息后模型过拟合风险变大。后来我用特征重要性排序筛掉了一部分效果立竿见影。3.2 基于PySpark的模型训练流程模型训练这块我选择了PySpark MLlib原因前面提过这里重点展示实际操作。我的流程是先从MySQL读取清洗好的数据集转换成Spark DataFrame按8:2切分训练集和测试集然后用Pipeline串起特征标准化和模型训练两步操作。from pyspark.sql import SparkSession from pyspark.ml.feature import VectorAssembler, StandardScaler from pyspark.ml.regression import RandomForestRegressor from pyspark.ml.evaluation import RegressionEvaluator spark SparkSession.builder \ .appName(PostgraduateScorePrediction) \ .config(spark.sql.shuffle.partitions, 8) \ .getOrCreate() df spark.read.format(jdbc).options( urljdbc:mysql://localhost:3306/postgraduate, drivercom.mysql.jdbc.Driver, dbtablescore_features, userroot, passwordyour_password ).load() feature_cols [ diff_1y, mean_3y, std_3y, national_line, enroll_yoy, heat_index, school_level, major_type ] assembler VectorAssembler(inputColsfeature_cols, outputColfeatures_vec) scaler StandardScaler(inputColfeatures_vec, outputColscaled_features, withStdTrue, withMeanTrue) rf RandomForestRegressor( featuresColscaled_features, labelColtotal_score, numTrees100, maxDepth10, seed42 ) # 划分训练测试集 train_df, test_df df.randomSplit([0.8, 0.2], seed42) # 构建Pipeline from pyspark.ml import Pipeline pipeline Pipeline(stages[assembler, scaler, rf]) model pipeline.fit(train_df) # 模型评估 predictions model.transform(test_df) evaluator RegressionEvaluator(labelColtotal_score, predictionColprediction, metricNamermse) rmse evaluator.evaluate(predictions) print(fRMSE: {rmse})配置spark.sql.shuffle.partitions这里单独说一下。默认值200在集群环境下是合理的但本地模式跑小数据集时反而会造成大量小任务调度开销我把这个参数调低后本地训练速度快了一倍不止。做毕设大多是在本机跑这种小细节往往是性能瓶颈的隐藏来源。另一个值得一提的点是JDBC连接器。Spark读取MySQL时如果没有修改read_partition的配置大数据量下会有单分区读取压力过大的问题。我这个数据集规模不算大所以没做额外分区优化但你如果真的把数据量扩大到一个更真实的级别最好按时间字段把读取拆成多个分区。3.3 预测效果评估与迭代优化最终模型的RMSE大概在7-8分左右听起来不算特别精准但要注意考研分数线本身每年的波动就在5-15分之间能把预测误差控制在个位数在“预测下一年分数线”这个问题上已经算可用状态了。调优过程中我试过几种不同方案这里把对比结果列出来供参考模型方案RMSE说明线性回归12.6特征与分数关系非线性效果一般决策树回归10.2单棵树泛化能力欠佳随机森林回归默认参数8.7集成效果明显提升随机森林回归调参后7.4控制树深度和数量后进一步收敛GBDT回归6.9效果最好但训练时间较长预测这件事其实没有终点我后来在项目文档里也专门写了“模型已知局限”这一节内容包括样本量有限导致高分段预测偏差大、部分新增专业没有历史数据、未纳入当年试卷难度等无法量化的因素。答辩时主动讲模型的局限性比被老师问倒要体面得多。4. 院校推荐系统的推荐算法实现4.1 推荐场景分析与算法选型分数线预测解决的是“我这个分数能不能上”的问题院校推荐解决的是“我能上哪些学校、哪些学校更适合我”的问题。两者的数据基础有重合但算法逻辑差别很大。推荐系统的典型做法是协同过滤但我在实际分析后发现纯协同过滤不太适合这个场景。原因是协同过滤依赖用户的历史行为数据而考研用户几乎都是一次性用户——每个人只做一次选择没有“之前选过哪些学校、后来去了哪”的行为日志可供挖掘。所以我最终采用了混合推荐策略基于内容的规则过滤为主协同过滤思想为辅。核心逻辑是先把用户输入的硬性条件作为过滤条件筛掉完全不符合的院校再用加权评分模型对剩余院校排序输出Top-N推荐结果。4.2 用户画像与院校特征构建为了让推荐结果更个性化我把用户输入和院校特征都抽象成了可计算的向量。用户画像包括本科院校层次、本科专业门类、目标专业是否跨考、期望城市、预估分数、是否有985/211偏好等。院校特征包括院校层次、专业排名、历年分数线、报录比、招生人数、所在城市等级、就业影响力评分等。这里有个值得注意的点用户画像里的“预估分数”和“期望城市”其实对应的是同一个决策逻辑里的不同维度前者是“能不能达到门槛”后者是“愿不愿意去”。所以在评分权重设计上我把分数匹配度权重设到了0.4城市偏好0.2院校层次0.2专业实力0.2。这样既保证了推荐结果“够得着”也兼顾了用户的偏好表达。特征匹配计算时用了最朴素的相似度计算方式——加权欧氏距离。没有用余弦相似度的原因在于用户画像和院校特征里含有大量离散编码字段如是否985、是否跨考欧氏距离能更好地体现这类字段的差异。4.3 协同过滤规则融合的推荐实现最终落地时我用了一个两阶段推荐流程。第一阶段是规则硬过滤逻辑用纯Python实现不需要Spark因为候选集已经控制在百级别单机处理完全没压力。第二阶段是评分排序把匹配度综合得分算出来之后降序排列取前10名。第二阶段里除了分数匹配我还加入了一个“相似考生”的协同过滤逻辑。简单说就是根据用户输入的画像在历史用户数据里找画像最接近的一批人统计他们最终选择/关注的院校分布把高频院校作为推荐加分项。由于我用的是模拟历史数据这个模块更偏展示性质但逻辑是完整的答辩时能把这个闭环讲清楚就已经达标了。def recommend(user_profile, school_pool, top_n10): # 第一阶段硬性条件过滤 filtered [] for school in school_pool: if user_profile[estimated_score] school[min_score] * 0.9: continue # 差距过大直接过滤 if user_profile[target_city] and school[city] not in user_profile[target_city]: continue filtered.append(school) # 第二阶段加权评分排序 for school in filtered: score_match calculate_score_match(user_profile[estimated_score], school[avg_score]) city_match 1.0 if school[city] in user_profile[target_city] else 0.3 level_match school_level_score(school[school_level], user_profile[school_pref]) major_match major_strength_score(school[major_rank], user_profile[major_rank_pref]) school[recommend_score] ( 0.4 * score_match 0.2 * city_match 0.2 * level_match 0.2 * major_match ) filtered.sort(keylambda x: x[recommend_score], reverseTrue) return filtered[:top_n]这段代码在答辩时被我翻来覆去讲了好几遍因为它的逻辑非常直白老师一眼就能看懂又能体现“先过滤后排序”的工程化思维。如果你的毕设也涉及推荐模块这种写法比直接套用复杂的DeepFM模型更稳妥——毕竟毕业设计的首要目标是“把原理讲清楚、把逻辑跑通”而不是为了炫技把项目做到无法维护。5. Hadoop数据处理链路与Spark On Yarn的协同实践5.1 Hadoop集群环境搭建要点这部分是整个项目的基础设施。我开发阶段用的是伪分布式模式也就是单台机器上同时跑NameNode、DataNode、ResourceManager和NodeManager。伪分布式的好处是能完整体验Hadoop生态的工作流程又不需要额外准备多台服务器非常适合毕设项目的前期开发和调试。搭建过程中有几个特别容易踩的坑JDK版本和Hadoop版本的兼容性。Hadoop 3.x建议搭配JDK 8或JDK 11版本不匹配会出现各种诡异的启动失败。SSH免密登录。伪分布式模式下也需要配置本机SSH免密否则每次启动集群都要输密码。配置文件里的路径设置。core-site.xml里的fs.defaultFS如果写错或用了默认值后面Spark读写HDFS时会非常痛苦。!-- core-site.xml -- configuration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/usr/local/hadoop/tmp/value /property /configuration我建议你在搭建完集群后第一时间跑一遍官方自带的WordCount示例程序。这不只是为了验证集群可用更重要的是让你在还没开始写业务代码前先对MapReduce的执行流程有一个感性认识。很多同学集群搭完直接上手Spark遇到问题就不知道是集群问题还是代码问题原因就是缺少这个验证环节。5.2 Spark On Yarn对接与数据入库当集群环境稳定后我把PySpark任务从local模式切换到了YARN模式让Spark作业真正跑在Hadoop集群之上。这一步在答辩时非常有价值因为它标志着你不是“会用Spark写代码”而是“Spark和Hadoop能协同工作”。# 提交PySpark任务到YARN集群 spark-submit \ --master yarn \ --deploy-mode client \ --num-executors 2 \ --executor-memory 2G \ --executor-cores 2 \ --jars mysql-connector-java-8.0.26.jar \ score_predict.py跑完一个正常执行的Spark On Yarn作业之后你可以去ResourceManager的Web界面看一眼任务执行情况再把Spark UI里各个Stage的耗时和Shuffle信息截几张图。这些截图放到毕设论文的“系统测试”章节里比任何文字描述都直观。数据入库的逻辑我用了一个很简单的做法模型预测结果通过JDBC写回MySQL的结果表同时把原始特征数据持久化到HDFS上存一份备份。这样既满足了Web端快速查询的需求也保留了完整的数据处理痕迹。如果你在论文里要写“数据存储方案设计”这一章这种数据双写策略是个很规范的参考。5.3 数据存储选型HDFS与MySQL的分工我最终的数据存储方案是HDFS和MySQL并存各有分工HDFS存储原始网页快照、爬虫日志、经PySpark清洗后的中间结果数据。这些数据的特点是“一次写入、多次读取、不需要快速更新”正好是HDFS的适用场景。MySQL存储最终的结构化结果数据包括分数线事实表、院校维度表、预测结果表、用户操作记录。Web端查询讲究低延迟MySQL在这里更合适。这种组合方式还带来了一个额外的好处每次模型迭代后原始数据还在HDFS里可以从头重新跑一遍完整的数据处理流程实现“可复现”的模型实验。这个设计在答辩时被老师重点表扬过因为很多毕设项目的数据处理流程是不可回溯的一旦中间环节出错整个结果就废掉了。6. 典型踩坑记录与排查技巧6.1 爬虫阶段的头疼问题爬虫部分我遇到的第一个大坑是页面编码问题。有些老牌高校网站还是GBK编码而Scrapy默认按UTF-8处理响应内容结果就是抓下来的中文全是乱码。解决办法是在Response的meta信息里强行指定编码方式或者在解析前先尝试用chardet自动检测编码。另一个问题是动态加载数据的处理。部分考研网站的数据是页面加载后通过AJAX请求动态获取的直接请求页面HTML只能拿到空壳。我最初用Selenium硬渲染每一页速度慢得让人崩溃。后来优化为尽量直接寻找XHR接口用requests模拟AJAX请求获取JSON数据效率提升了将近十倍。只有当XHR接口加密或者参数太重时才回退到Selenium。最后必须提一下数据校验的重要性。爬虫抓下来的数据千万不要无条件信任我吃过最大的亏是某个网站把“—”占位符当成数据渲染进表格导致入库字段变成字符串而非数字模型训练直接报错。后来我在Pipeline里加了严格的类型校验非数字类型一律标记为缺失值这个问题才算根治。6.2 PySpark内存与算子问题排查PySpark跑本地模式时最常遇到的就是内存溢出尤其是随机森林训练阶段。我一开始没设置executor内存参数默认值在训练几百棵树时直接把内存撑爆。后来调整了spark.driver.memory和spark.executor.memory并适当减少numTrees数量问题才解决。算子使用上也有一个非常典型的坑——不要把整个DataFrame collect()到本地再处理。我第一次写特征工程时图省事想先collect出来用Pandas处理结果数据量一大直接OOM。正确做法是尽量用Spark原生的DataFrame API完成转换操作确实需要落到本地时先做聚合或抽样控制数据量。6.3 推荐效果不理想的调优手段做推荐系统的同学都有一个共同的烦恼就是推荐出来的结果怎么看怎么不合理。我调试过程中总结出三条有效优化路径检查硬性过滤条件是否过严。比如目标城市只填了一个结果把所有不在这座城市的985、211全过滤掉了推荐结果自然不合理。解决方案是把城市匹配设计成加分项而不是一票否决项。检查评分权重是否和真实决策逻辑一致。有些用户不看重院校层次但很看重专业实力这时候还按默认权重计算必然出问题所以我在Web端开放了权重调节滑块让用户自己调整偏好。检查数据质量。推荐效果差往往不是算法问题而是数据问题比如某院校的报录比字段是空的、招生人数更新到了旧年份这都会让最后的评分产生偏差。7. 给准备复现这个项目的同学几点建议7.1 论文与答辩准备的心得这套系统做下来我的感受是毕业设计真正拉开差距的往往不在代码而在你对整个项目的理解和表达。代码能跑通只是下限能把“为什么这么设计”“数据经过什么处理”“模型为什么选这个”讲清楚才是拿到高分的上限。论文写作上我建议每个模块都要配上数据流图和处理逻辑说明。流程图不需要多复杂关键是能把数据从采集到存储再到模型消费的路线画清楚。这里放心用visio或draw.io画答辩PPT里直接用同一套图保持前后一致。答辩演示环节有个经验可以分享准备一套完整的演示数据从爬虫启动、代码训练、Web查询到推荐结果展示一气呵成。演示之前一定要把预测和推荐的缓存数据先跑好防止现场因为网络或资源问题出现卡顿。万一现场展示翻车前面的努力可能都要打折扣。7.2 系统的可扩展方向虽然这套系统面向的是考研场景但整体架构可以很自然地进行领域移植。比如把数据源换成公务员考试历年进面分数线你就能得到一个公考岗位推荐系统换成高考录取数据就能做高考志愿填报辅助系统。架构上的通用性本身就是这套设计的隐藏价值。如果你未来想做更深入的优化有几个方向值得尝试一是引入深度学习模型做分数线预测比如LSTM处理时间序列数据二是把现有推荐逻辑升级为更完整的召回排序架构在召回阶段用规则逻辑排序阶段引入学习排序模型三是采集更多维度的数据源比如把学科评估结果、导师信息、奖学金政策等纳入院校特征库让推荐结果更有说服力。拿我自己的经验来说这个项目真正让我成长的地方不在于我用了多少高大上的组件而在于它逼着我把一条完整的数据链路从头跑到尾。爬虫挂了能排查Spark内存溢出了会调参预测效果不好知道怎么优化特征推荐结果不合理懂得调整权重——这些能力是在一个个实际的错误和教训中磨出来的。如果你也在做或者准备做类似的项目我最大的建议是别怕踩坑把你遇到每一个问题以及怎么解决的记录下来这些内容不仅是你论文里最有分量的素材更是你真实技术能力的证明。