做计算机毕业设计最怕的是什么?我最常听到的一句话是:"我选了个管理系统,开头两周很轻松,写到后面发现自己根本没有可写的东西,论文里全是配置截图和CRUD。"说白了就是技术点太单薄。今天拆解的这套Python租房大数据分析平台,属于典型的"爬虫+大数据存储+数据分析+可视化+推荐系统"多技术栈组合题,覆盖面广、展示效果好、论文素材充足,而且每块都能说清楚"做了什么、为什么这样做"。无论你是要找毕设方向、想复现一个完整项目,还是单纯想看看大数据分析类课题怎么组织技术方案,这篇都能给你一个能直接落地的思路。
先给你交个底:这套项目并不是什么高不可攀的算法研究,它的核心就是把"数据从哪里来、存到哪里、怎么算出价值、怎么展示出来"这条链路完整走通。市面上流传的各种版本标题五花八门,但骨干技术基本都是同一套:用Scrapy把公开租房网站的房源数据抓下来,清洗后存进MySQL,再通过Pandas做统计分析,最后用ECharts做可视化大屏,再叠加一个基于内容相似度的房源推荐模块。如果愿意加分,还可以在分析层引入大模型做房源描述的关键词抽取和智能摘要。下面我从整体设计开始,把每一步的细节和坑都拆给你看。
1. 项目定位与整体架构拆解
1.1 这套课题到底考的是什么
很多同学看到"大数据分析平台"这类标题就发怵,以为要搭Hadoop集群、写MapReduce。其实本科毕业设计题目里的"大数据",绝大多数指的是"数据量较大、数据结构复杂到Excel处理不了"的场景,核心考察的是你对数据处理流程的理解。租房子这个选题之所以被反复选用,是因为它有几个非常适合毕设的特点:数据模型清晰、字段丰富程度介于结构化和非结构化之间、可视化维度多、业务逻辑贴近日常生活。
具体到本课题,它涵盖了计算机专业里几个核心方向的"可展示成果":
- 爬虫方向:Scrapy框架编写Spider,处理翻页、字段提取、反爬策略。
- 数据库方向:MySQL表结构设计、去重策略、数据导入导出。
- 数据分析方向:Pandas清洗、按城区/户型/面积区间聚合统计、租金分布规律。
- 可视化方向:ECharts大屏、地图Heatmap、词云、趋势折线。
- 推荐系统方向:基于内容特征的相似度计算,给用户推相似房源。
你把这几块任意抽一块出来,都能在论文里独立成章。这也是为什么我建议选题尽量选"链路型"项目,而不是单点功能型项目——链路越长,你能讲的故事越多,答辩时老师问什么你都有东西回答。
1.2 技术选型背后的取舍逻辑
技术栈看起来杂,但每一层选型都是有理由的,答辩时讲清楚"为什么不用XX"和"为什么用XX"同样加分。
采集层用Scrapy而不是requests+BeautifulSoup,核心原因是Scrapy自带并发调度、去重中间件、Item Pipeline和扩展机制。手写requests爬虫单线程抓几千条数据可能要跑几小时,Scrapy默认的并发核数五分钟就能搞定,而且中间件可以很方便地插入UA伪装和延时控制。到了后期要扩展多城市多区域爬虫,只需要新增Spider类而不改框架。
存储层选MySQL不用MongoDB,主要考虑两点。第一,房源数据是强结构化数据:价格、面积、户型、朝向都是定长字段,SQL聚合非常方便;第二,学校答辩环境里MySQL比MongoDB普及得多,老师在检查数据库时熟悉度更高,沟通成本低。后面做可视化时直接用SQL语句查出聚合结果,少写很多程序内统计代码。
可视化层用ECharts而不是Matplotlib或Tableau,原因更实在——静态图撑不起"大屏"这个展示效果,ECharts的全国地图、动态轮询、炫酷样式都适合在答辩现场制造视觉冲击,而且它前后端分离的模式让你可以把图表配置成JSON下发,数据一换图表自动更新。
推荐模块用基于内容的相似度计算,而不是协同过滤,是因为毕设项目往往没有真实的用户行为数据。协同过滤需要"用户-物品-评分"三元组,租赁平台不公开谁看过哪套房子,硬做只能伪造数据,答辩时站不住脚。基于内容的推荐只需要房源本身的结构化属性就可以实现,逻辑也更容易解释清楚——"给用户推荐与他当前浏览房源相似的小区、价格段、户型",这个逻辑评委很容易理解。
1.3 系统模块划分与数据流向
整个系统我建议分成四个模块:采集模块、存储模块、分析模块、展示模块。采集模块负责从目标网站抓数据并做初步结构化;存储模块负责建库建表、去重、索引优化;分析模块负责对数据库中的数据做聚合计算,生成统计结果和推荐结果;展示模块负责把结果通过接口交给前端大屏渲染。
数据流向是单向的:网页→Scrapy Item→MySQL表→Pandas/DataFrame/SQL聚合→JSON接口→ECharts。这个单向流向一定要在论文里画成架构图,它体现了你对系统整体性的理解。另外还可以加一条旁路:房源描述文本→大模型解析→输出标签和摘要→写回MySQL,这条线是当前比较热门的技术融合方向,推荐有余力的同学做。
2. Scrapy爬虫实战:从页面分析到稳定抓取
2.1 目标网站分析与爬虫设计
我以常见的房产信息平台为参考来说明,不要纠结具体域名,思路完全一致。这类网站的房源列表页结构通常是:一个城市首页→区域标签→列表页→详情页。列表页包含标题、小区、朝向、面积、价格,详情页补充更多文本描述、标签、发布时间等字段。
在设计Spider前,我先做了一件看似简单但很重要的事情:用浏览器开发者工具分析页面结构。具体来说,先确认列表数据是HTML直接渲染的还是Ajax异步加载的。如果是Ajax,你需要去Network面板找以/api/开头的XHR请求,往往能直接拿到JSON格式的数据,比解析HTML省力得多。这个判断决定了你的Spider写法和数据解析成本,别上来就写代码。
爬虫的类结构我建议拆成两个Spider:
ListSpider:负责爬城市列表页,循环遍历每个区域的列表,收集所有房源详情页URL。DetailSpider:负责访问每个详情页,抓取完整字段。
这样一个负责"找页面",一个负责"提数据",职责清晰。中间用Scrapy的Request对象把URL传递过去,配合allowed_domains限制爬取范围,防止爬虫跑偏。示例代码如下:
import scrapy class ListSpider(scrapy.Spider): name = "zufang_list" allowed_domains = ["example.com"] start_urls = ["https://www.example.com/zufang/"] def parse(self, response): # 获取区域标签里的链接 regions = response.css("div.region a::attr(href)").getall() for region_url in regions: yield scrapy.Request( url=response.urljoin(region_url), callback=self.parse_region ) def parse_region(self, response): # 翻页逻辑:取下一页链接,同时取当前页所有详情链接 detail_urls = response.css("div.title a::attr(href)").getall() for detail in detail_urls: yield scrapy.Request( url=response.urljoin(detail), callback=self.parse_detail ) next_page = response.css("a.next::attr(href)").get() if next_page: yield scrapy.Request( url=response.urljoin(next_page), callback=self.parse_region ) def parse_detail(self, response): # 详情页字段提取逻辑 yield { "title": response.css("h1::text").get(), "price": response.css("span.price::text").get(), "area": response.css("div.area::text").get(), "district": response.xpath("//a[contains(@class,'district')]/text()").get(), }这段代码虽然简单,但支撑起了完整的爬虫框架。实际项目里,你只要把选择器换成目标网站的真实结构,再补其他字段提取规则就可以了。
2.2 Item字段设计与Pipeline处理
Scrapy官方的推荐做法是用Item类定义统一的字段结构,这样后续所有数据能保证格式一致。字段设计直接决定数据库表怎么建,所以这一步要想清楚。我建议的房源Item字段如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| house_id | string | 房源唯一ID,用于去重 |
| title | string | 标题 |
| district | string | 行政区 |
| biz_circle | string | 商圈 |
| community | string | 小区名 |
| layout | string | 户型,如"2室1厅" |
| area_size | float | 面积,单位平方米 |
| floor | string | 楼层信息 |
| orientation | string | 朝向 |
| rent_price | int | 月租金,单位元 |
| decoration | string | 装修情况 |
| description | string | 详细描述文本 |
| publish_time | datetime | 发布时间 |
| house_url | string | 房源链接 |
在Pipeline里要处理三件重要的事。第一是去重,用scrapy.exceptions.DropItem判断重复,配合Redis的Set结构记录已处理的房源ID,能做到千万级数据下依然高速判断。第二是字段规范化,比如把"押一付三"这类付款方式从文本里单独抽出来存成字段,把面积字段的"㎡"去掉转成float。第三是入库,使用twisted的线程池连接MySQL,避免阻塞爬虫主循环。
import redis import pymysql from scrapy.exceptions import DropItem class DuplicatesPipeline: def __init__(self): self.redis_cli = redis.Redis(host="localhost", port=6379, db=0) def process_item(self, item, spider): unique_key = item.get("house_id", "") if self.redis_cli.sismember("seen_house_ids", unique_key): raise DropItem(f"Duplicate house: {unique_key}") self.redis_cli.sadd("seen_house_ids", unique_key) return item2.3 反爬应对策略与合规注意事项
租房平台不比普通博客站点,对爬虫的识别逻辑很成熟。最常见的拦截方式包括:请求频率检测、User-Agent指纹、Headers合法性校验、验证码。应对思路有几种,但执行顺序有讲究。
先做"像个正常用户",再做"群体伪装"。具体操作上,我会维护一个UA池,随机切换;把每个请求的Accept-Language、Referer都补完整;两次请求之间加0.5~1秒的随机延时。这些做完基本能解决一半以上的拦截问题。如果还报403,就上代理IP池,每次请求随机取一个IP发出。这里要特别强调的是,所有技术手段都是在合规的前提下进行的。你需要先查看目标网站的robots.txt,遵守网站的访问频率要求,只采集公开可见的非个人信息数据,控制单次采集规模,并且明确项目仅用于学习和研究用途。毕业论文方向选择时务必注意合规性,如果条件允许,优先使用平台开放的API或者官方提供的数据集,省心省力还不碰红线。
另外有一个非常隐蔽的坑:很多网站对无登录用户会返回一个"疑似爬虫验证页",但状态码依然是200。你处理response时会发现HTML结构完全变了,字段提取全部为空。解决方法是检测页面里有没有包含"验证""captcha"之类的关键词,出现时直接丢弃该请求并记录URL,后期人工核查。
3. 数据存储与预处理:让数据"可用"
3.1 MySQL表结构与索引设计
抓下来的数据如果直接塞进一张大表,后面查询会越来越慢。我建议至少拆三张表:house_info存房源主数据,district_info存行政区与商圈字典,crawl_log存爬虫运行日志。这样分区清晰,也方便后期做不同维度的统计。
house_info表结构示例:
CREATE TABLE house_info ( id INT PRIMARY KEY AUTO_INCREMENT, house_id VARCHAR(64) UNIQUE NOT NULL COMMENT '平台房源唯一标识', title VARCHAR(255) COMMENT '标题', district VARCHAR(64) COMMENT '行政区', biz_circle VARCHAR(64) COMMENT '商圈', community VARCHAR(128) COMMENT '小区', layout VARCHAR(64) COMMENT '户型', area_size DECIMAL(6,1) COMMENT '面积(㎡)', floor_info VARCHAR(64) COMMENT '楼层', orientation VARCHAR(16) COMMENT '朝向', rent_price INT COMMENT '月租金(元)', decoration VARCHAR(32) COMMENT '装修情况', description TEXT COMMENT '详情描述', publish_time DATETIME COMMENT '发布时间', house_url VARCHAR(512) COMMENT '详情链接', parse_time DATETIME COMMENT '入库时间', KEY idx_district_price (district, rent_price), KEY idx_area (area_size), KEY idx_layout (layout) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源信息表';这里有个容易忽略的点:把house_id加上唯一索引,目的不只是防止主键冲突,更重要的是配合Pipeline里的去重逻辑做双层保险。总价、均价这些字段不要直接在数据库里存,而是冗余一个"面积"和"租金"字段,算均价时用SQL表达式动态计算,既省空间又避免字段不一致。
索引设计上,要围绕后面查询会用的WHERE条件来建。最常见的聚合查询是"按区统计平均租金"和"按面积段筛选房源",所以district和rent_price的联合索引、area_size的单列索引要优先建。别把索引堆得过多,写入会变慢,采集阶段会拖后腿。
3.2 数据清洗规则与质量校验
数据抓回来之后你会发现问题非常多,"脏数据"的类型我总结出来有这几种:
- 字符串型数字:如
"80㎡"、"¥4500",需要正则提取。 - 同义字段不统一:
"南"、"朝南"、"南向"都是一回事,需要映射归一。 - 缺失字段:部分详情页没有面积或户型字段。
- 异常值:租金为1元(可能是标错价)、面积超过500平米(可能是厂房误标)等。
清洗时我用Pandas写了一个独立的预处理脚本,不放在爬虫里,原因也很简单:爬虫负责采集,清洗是分析层的事,两者解耦后,采集出错不需要重跑全量爬虫,只要清洗脚本支持断点续做就行。
import pandas as pd import re df = pd.read_sql("SELECT * FROM house_info WHERE area_size > 0", engine) # 面积字段规范化:去掉"㎡"等字符 df["area_size"] = df["area_size"].astype(str).str.extract(r"(\d+\.?\d*)")[0].astype(float) # 租金过滤:只保留月租100-100000之间的合理值 df = df[(df["rent_price"] >= 100) & (df["rent_price"] <= 100000)] # 朝向归一化 orientation_map = {"南": "南", "朝南": "南", "南向": "南", "北": "北", "朝北": "北"} df["orientation"] = df["orientation"].map(orientation_map).fillna("未知")清洗后的数据还要做一轮抽样校验。我的习惯是随机抽查3%的记录,人工核对是否和原始页面一致,校验通过才把数据标记为"clean",分析层只读取这部分数据。这个流程看起来繁琐,但能避免后期可视化时出现"北京出现25000元/月的城中村单间"这种尴尬错误。
4. 分析引擎与推荐系统:让数据产生业务价值
4.1 数据分析指标体系的搭建
"大数据分析平台"不能只有一个可视化大屏,背后还要有一套分析指标体系。租房场景下,最核心的指标不外乎这些:
- 平均租金:按行政区、商圈两级维度聚合。
- 租金单价:单位面积租金,用于对标同一城市不同区域性价比。
- 供需热度:更准确地说是"房源量排行",某商圈挂牌房源越多,说明该区域租赁市场越活跃。
- 户型占比:不同户型的供应结构,判断市场主流需求。
- 租金分布:频率分布直方图,看价格中位数和众数区间。
计算这些指标时,能用SQL聚合就不要拉到Python里算。比如"各行政区平均租金排行",一条SQL就完成:
SELECT district, ROUND(AVG(rent_price), 2) AS avg_price, COUNT(*) AS cnt FROM house_info WHERE rent_price > 0 GROUP BY district ORDER BY avg_price DESC;跑完SQL拿到的结果直接封装成JSON接口给前端图表用,效率最高。如果要做更复杂的分析,比如租金与面积的关系,或者不同户型的单价中位数对比,再用Pandas读全表后分组聚合。
这里我想专门提一下"为什么要用BigData这个词"。在论文里你可以写:平台采集了数千条有效房源数据,在百万级数据量下依然能在毫秒级返回聚合结果,体现了从数据处理到存储的工程优化,而不是真的需要跑分布式计算。很多同学在毕设里生搬硬套Hadoop全家桶,答辩时被问到小数据量为什么要用分布式,答案往往说不圆。老实说"设计了一个面向中等规模数据的分析平台"反而是加分的。
4.2 基于内容特征的房源推荐实现
推荐系统是这个项目最能讲出花来的模块。我用的是"基于内容推荐"路线,总体思路分三步:特征抽取、相似度计算、Top-N排序。
特征抽取环节,我把每个房源表示成一个"特征向量",特征分两类。第一类是结构化特征:行政区、户型、租金段、面积段。第二类是非结构化特征:标题和描述文本的分词结果。结构化特征好处理,一个One-Hot编码就搞定;文本特征我用了jieba分词加TF-IDF权重,实现时可以直接用scikit-learn库的TfidfVectorizer:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity # 假设house_corpus是每个房源的描述文本列表 vectorizer = TfidfVectorizer(tokenizer=jieba.lcut, max_features=500) tfidf_matrix = vectorizer.fit_transform(house_corpus) # 计算所有房源之间的两两相似度 sim_matrix = cosine_similarity(tfidf_matrix) # 给某个房源推荐最相似的top10 target_index = 123 rec_indices = sim_matrix[target_index].argsort()[-11:-1][::-1]这套代码还有个可讲的升级点:把结构化特征和文本特征拼接成综合向量,或者对结构化特征单独算欧氏距离,最后做加权融合。一般来说,结构化特征的权重可以给高一点,因为区域和价格段对租房决策的影响远大于描述文本的语气。合理的权重配比是文本相似度占三成、结构化占据七成,当然这个比例要通过实验效果调整。
4.3 用户交互的推荐场景与冷启动处理
推荐系统不能只在后台算相似度,得有用户入口才有演示效果。我的做法是做一个"偏好推荐"接口,用户输入城市区域、租金上限、期望户型,系统先根据硬性条件筛选出候选房源,再按与首选房源的相似度排序输出结果。
这个设计里有一个几乎必考的面试问题:冷启动怎么办?当用户第一次登录,没有浏览历史,没有收藏行为时,推荐系统无从算相似度。我在系统里设计了兜底策略:新用户不推荐"最相似",改推"最热门"。统计全局点击排行榜和各区域房源热度,输出Top10。等到用户产生了第一次浏览行为,再切换到个性化推荐逻辑。这个方案不复杂,但逻辑闭环,答辩老师一般不会在这个环节继续为难你。
如果你想引入大模型做亮点,我建议这么做:调用大模型对房源描述文本做三件事——抽取户型特点关键词、提炼"近地铁""南北通透""精装修"等卖点标签、生成一段50字的推荐摘要。输出结果写入表,作为额外标签字段参与特征向量拼接,这样推荐效果会比纯TF-IDF好一截。不需要自己训练模型,用开放平台的通用大模型即可,只要在论文里写清楚"预训练语言模型在领域文本特征提取中的应用"即可,属于成本低、视觉效果好、论文内容丰富的加分项。
5. 可视化大屏与前端交互设计
5.1 大屏布局与图表选型
可视化部分是整个项目的"面子工程",也是答辩时最能快速让评委直观了解系统价值的环节。一个完整的租房分析大屏,建议至少包含以下模块:
- 顶部指标卡:总房源数、在租均价、最高租金区域、最新抓取时间。
- 地图热力图:按行政区展示房源量分布和租金水平。
- 区域均价条形图:横向条形图排序。
- 户型占比环形图。
- 租金价格区间与面积区间分布直方图。
- 房源描述词云。
这里我用的方案是Flask后端渲染模板,前端直接引入ECharts。后端每5分钟从数据库统计一次结果生成JSON数据,前端用Ajax轮询拉取数据更新图表。大屏布局用Grid布局即可,每个图表占一个卡片区域。要注意的是,地图Heatmap需要知道各行政区的经纬度中心点,这个可以在静态配置里维护一个字典,否则ECharts地图无法定位。我做的时候就是在这里卡了半天,一直以为是数据格式错了,最后才发现是忘记把地区名称映射成地图标准名称。
5.2 后端接口设计与图表联动
后端接口设计上,我按照"一个图表一个接口"的原则拆分。比如/api/avg_price_by_district返回各城区均价列表,/api/layout_distribution返回户型占比。接口返回值格式统一为{"code":200, "data":[...]},这样前端处理起来整齐,也方便做错误状态判断。
联动交互是整个平台里最体现"设计感"的部分。我做了这样一个交互:点击地图上的某个区,下方柱状图和环形图自动联动显示该区的数据。原理很简单,前端给ECharts地图实例绑定click事件,事件触发后用当前区域名发起新的Ajax请求,把改动后的数据替换进图表中。
myChart.on("click", function(params) { var district = params.name; fetch("/api/get_district_detail?district=" + encodeURIComponent(district)) .then(res => res.json()) .then(data => { avgPriceChart.setOption({ series: [{ data: data.avgPrice }] }); layoutChart.setOption({ series: [{ data: data.layout }] }); }); });联动不仅演示效果好,还让评委明白你不是只会"画图",而是真的理解了从"数据"到"信息"到"交互反馈"的完整链条。如果还有精力,还可以加一个词云联动:点击词云中的关键词,列表页展示包含该关键词的房源,整个平台从宏观分析到微观钻取就形成了一个闭环。
5.3 大屏性能优化与部署细节
数据量上来之后,每次图表刷新都重新全表聚合会非常慢。我在后端做了缓存层,把常用的图表数据结果放进Redis,设置5分钟过期,过期后重新查库更新。这样前端轮询只是从Redis取数,响应时间稳定在几十毫秒以内,演示效果就很流畅。
部署层面,最简单的方案是把Flask服务跑在服务器上,静态资源放在同机Nginx下,实际上一个systemd服务加一个Nginx配置就能搞定。我更推荐的做法是同时准备Docker镜像,因为答辩现场往往需要快速在另一台电脑上启动环境,用Docker先构建好镜像,docker compose up一键起全部服务,省去现场配环境翻车的风险。当然,如果没有服务器条件,直接在答辩机器上跑开发服务器也不是不行,但提前把静态资源打成包能避免现场加载慢的尴尬。
6. 常见问题与避坑指南
6.1 爬虫阶段的典型故障排查
我在实际做这类项目时,踩过的坑比想象中要多。最常见的第一类是"能跑通但抓不到数据"。排查思路是:先在浏览器里复制一个详情页URL手动访问,看返回页面里有没有你需要的字段,如果浏览器里有但爬虫抓不到,八成是网站对你做了降级返回——你看到的是一份被处理过的静态页面,而不是后端真实数据。此时检查请求头缺失项、增加Cookie、或者改用页面里内嵌的window.__INITIAL_STATE__数据,一般都能解决。
第二类是"跑几分钟就被封"。首要检查请求频次,Scrapy默认并发很高,对目标站来说攻击性太强。我的做法是在DOWNLOAD_DELAY设置为2秒,同时限制并发为8,爬一个城市几千条房源数据也就二三十分钟,完全可以接受。没有任何必要追求极限速度。
第三类是最阴间的"数据库写入中文乱码"。这是因为Scrapy的Item的编码和MySQL表编码不一致。解决方法是把表字符集统一设置为utf8mb4,同时数据库连接串中显式声明charset='utf8mb4',否则即使表建对了,连接层仍可能使用更老的字符集。
6.2 可视化图表显示异常的排查思路
图表不显示通常有三个层面。第一层是前端脚本报错,打开浏览器控制台看有没有红色报错;第二层是接口返回格式不对,可能是空数据或者数据结构与图表配置不匹配;第三层是数据本身有问题,比如地图数据里的区域名称与地图自带的名称不匹配,导致部分区域不渲染。
我在调试时的一个小技巧是:后端接口直接返回纯JSON而不是HTML模板,前端只负责展示。这样出问题时,直接用浏览器访问接口地址就能判断"数据是好的还是界面有问题"。如果是"数据为空",再回到SQL层面排查Where条件;如果是"数据显示了但不正常",再去看ECharts配置。
6.3 推荐效果"看起来没道理"的分析
推荐结果不准是另一个高频问题。比如"这套房源在城东,推荐的Top5却有城西的",一看就是特征权重没调好。我会先打印出相似度得分最高的几套房源,对比它们与目标房源在结构化属性上的差异,很快能发现是文本特征的贡献超过了结构化特征。解决办法是把地区和租金字段单独抽出来做精确匹配过滤,在过滤后的候选中再做相似度排序,而不是把地区编码直接拼进向量里——这种"硬过滤+软排序"的做法在算法实现上更符合直觉,效果也更稳定。
如果你发现推荐列表总体合理但偶尔混入奇怪结果,往往是因为分词把"近地铁"和"地铁站"看成了完全不同的词。建议引入用户自定义词典,把"近地铁""精装""可短租"这类业务词汇预先加入jieba词典,能大幅提升文本特征质量。
一些个人体会与项目扩展建议
这类计算机毕业设计项目,技术上真的不需要每一个环节都挖到很深,关键是保证整条链路没有断点。把采集、存储、分析、推荐、可视化每个环节都做出可见的成果,你就拥有了一个结构完整、故事丰满的项目。我个人在实际操作中的体会是,耗时最大的永远不是写代码,而是数据清洗和接口调试,这两个环节至少占了全部开发时间的四成。
最后再分享一个小技巧:无论你最终用什么方案,把每个模块独立成包,留好清晰的README,把启动步骤写明白。答辩前多花二十分钟把整个流程从零跑一遍,你会感谢这个习惯的。如果以后想扩展这个平台,可以从两个方向入手:增加多个城市的数据接入,让分析维度从单城对比变成多城横向对比;或者把推荐模块从静态"相似房源"升级成带用户偏好的实时个性化推荐,这就够得上一个研究生课题的体量了。