Python全栈租房数据采集分析与推荐平台实现
租房数据的坑我替你们踩了一遍。去年帮一个学弟做毕业设计他原本想用现成的爬虫框架抓几个网站拼个页面交差结果发现房源数据根本没有想象中那么好拿——反爬、字段缺失、价格单位混乱、户型文本不统一随便哪一项都能让答辩演示当场翻车。后来我们把整个项目重构成了一个完整的链路Scrapy采集、Pandas清洗、可视化看板、协同过滤推荐、大模型问答接入全部跑通之后他才算真正理解了一个数据类毕设该有的深度。这个项目最终定名为Python全栈租房数据采集分析与推荐平台如果你也在准备计算机毕业设计或者想做一个能写进简历的大数据全栈项目这篇文章会把整个系统的设计思路、技术选型原因、关键代码实现和踩坑记录完整拆给你看。里面没有花里胡哨的概念堆砌每一步都是能直接跑起来的东西。1. 选题逻辑为什么租房数据适合做成毕设项目毕业设计最怕的不是题目难而是题目空。很多同学喜欢选基于XX的YY系统这种题目做到最后发现就是个增删改查的网页答辩老师一问数据哪里来的分析做了什么就直接卡壳。租房数据这个方向有天然优势数据量够大、字段够丰富、业务场景明确而且从采集到展示每个环节都能讲出技术点。1.1 一个合格数据类毕设的核心评价维度先搞清楚答辩老师到底在看什么。我观察过几场答辩数据类项目的评价基本集中在四个维度数据从哪来是自己写的假数据还是真实爬取的数据。真实数据意味着你处理过反爬、清洗过脏数据这是加分项。数据做了什么光有爬虫不够你用什么方法从数据里提炼信息比如价格分布、区域均价、户型结构这些结论。结果怎么呈现图表的合理性、交互性能不能一屏看懂数据规律。有没有应用价值爬到数据之后还能干什么推荐系统和大模型问答就是这个环节的亮点。租房数据天然满足这几个维度。房源信息是半结构化数据既有数字字段价格、面积、经纬度又有文本字段标题、描述、户型非常适合展示数据分析基本功。1.2 项目规模控制的建议我知道很多同学担心项目做太大完不成。这个项目的合理规模应该是爬虫抓2到3个主流租房平台每个平台控制在5000条以上有效数据总数据集争取到15000条左右。别贪多数据量够分析就行重点是把每个环节做扎实。我当时给学弟的配置是三个数据源但结构上有区分一个主攻房源列表页一个主攻详情页描述文本一个作为补充数据源用于交叉验证。这样做的好处是代码复用率高而且爬下来的数据字段可以互补后面做推荐系统时特征更全。2. 技术选型全栈链路里每个环节的取舍理由很多教程喜欢直接给一套技术栈然后让你照着装但我更想先讲清楚每个组件为什么出现在这里。只有理解了选型逻辑答辩被追问时你才能应对。2.1 爬虫层Scrapy为什么比RequestsBeautifulSoup合适用Requests写爬虫很多人都会手撸几十行也能抓数据但一旦面对多页面、多平台、需要断点续爬的场景管理成本会迅速失控。Scrapy的核心优势在于框架自带的请求调度与并发控制。你只需要定义Item数据结构、编写Spider解析规则剩下的去重、队列、并发都由框架处理。尤其是scrapy.Request的回调链可以很自然地处理列表页到详情页的跳转先解析列表页拿到房源链接再逐条回调解析详情页。另外一个实用功能是AutoThrottle自动限速。这个一定要开因为租房平台的反爬策略很敏感请求频率稍微快一点就会被封IP。开启之后框架会根据响应时间自动调节延迟比手动写time.sleep聪明得多。# settings.py 中的关键配置 DOWNLOAD_DELAY 1.5 # 基础延迟 AUTOTHROTTLE_ENABLED True # 自动限速 AUTOTHROTTLE_START_DELAY 1.0 AUTOTHROTTLE_MAX_DELAY 10.0 CONCURRENT_REQUESTS_PER_DOMAIN 8 COOKIES_ENABLED False # 大多数情况不需要cookies反而减少被追踪注意这个DOWNLOAD_DELAY我用的是1.5秒实际要根据目标网站的响应速度调整。太快容易被封太慢采集时间会拖到几小时。2.2 存储层MySQL还是MongoDB两条路都有人走我的建议是如果你更熟悉关系型数据就用MySQL不要犹豫。租房数据虽然来源多样但核心字段是稳定的比如标题、价格、面积、区域、户型、朝向、楼层、发布时间、来源链接。这种结构完全可以用一张表搞定查询分析时SQL比NoSQL直观得多。而且毕业设计答辩时老师看到你能写出带JOIN和聚合统计的SQL印象分会好很多。当然如果你希望展示更丰富的技术栈可以把原始响应数据存一份MongoDB作为备份再清洗后存入MySQL。我当时没这么做因为时间成本实在太高维护两套存储对本科生来说有点吃力。建表的时候有一个细节给source_url字段加唯一索引。因为爬虫可能重复启动同一个房源会被多次抓取这个唯一索引配合INSERT IGNORE就能天然去重比在业务层做判断省事得多。2.3 后端与可视化FlaskECharts组合的现实理由后端框架我见过用Django的也见过用FastAPI的最后我们还是选了Flask。理由很务实毕业设计项目规模不大Flask的轻量正好匹配而且Flask配Jinja2模板可以直接渲染页面不用前后端分离那一套复杂工程。可视化是另一个考量重点。ECharts的图表类型丰富、文档中文友好而且支持异步加载数据后动态渲染。我们的方案是后端提供JSON数据接口前端用ECharts绘图。比如房价分布直方图前端只需要向后端发一个请求拿到各价格区间的频数数组就能在五分钟内完成一个可交互图表。// 前端房价分布图表的简单示例 fetch(/api/price_distribution) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(priceChart)); chart.setOption({ xAxis: { type: category, data: data.ranges }, yAxis: { type: value }, series: [{ type: bar, data: data.counts, itemStyle: { color: #4e9f3d } }] }); });这里的/api/price_distribution接口我们用Flask写内部就是一条SQL聚合查询加一些Python处理。整套链路短平快非常适合毕设周期。2.4 推荐与大模型如何避免为了用而用的嫌疑推荐系统和大模型是加分项但也是最容易被答辩老师追问你这个推荐到底解决了什么问题的地方。我当时给学弟设计的逻辑是这样的推荐系统解决的是用户找房效率问题房源太多用户不可能逐一浏览系统根据用户浏览行为推荐可能感兴趣的房源。大模型解决的是自然语言交互问题用户想找朝阳区附近朝南、月租4000以内的两居室传统筛选条件找不到但用大模型辅助生成SQL或解析关键词就能解决。这样讲整个系统的数据链路就形成了闭环采集数据 - 分析数据 - 可视化展示 - 推荐服务 - 自然语言问答。每个模块都有明确职责不是技术堆砌。3. 爬虫层实现从列表页到详情页的完整链路爬虫是整个数据链路的第一步也是最容易翻车的一步。这里我把核心代码逻辑拆开讲包括解析规则、字段映射和反爬应对。3.1 Item设计与字段生命周期我们先定义Item结构把所有可能的房源字段一次性列全。字段定义有几个原则宁多勿缺、类型明确、预留扩展。# items.py import scrapy class RentItem(scrapy.Item): title scrapy.Field() # 标题 price scrapy.Field() # 月租金元/月 area scrapy.Field() # 面积平方米 layout scrapy.Field() # 户型如 2室1厅 region scrapy.Field() # 行政区如 朝阳区 address scrapy.Field() # 详细地址 longitude scrapy.Field() # 经度 latitude scrapy.Field() # 纬度 floor scrapy.Field() # 楼层信息 direction scrapy.Field() # 朝向 publish_time scrapy.Field() # 发布时间 source_platform scrapy.Field() # 来源平台 source_url scrapy.Field() # 来源链接 description scrapy.Field() # 描述文本详情页才有注意description字段很多同学会忽略它。但在推荐系统和大模型问答环节描述文本非常关键。大模型理解用户需求时需要通过文本相似度匹配房源描述所以这个字段一定要在详情页爬取阶段就留好。3.2 列表页解析与请求调度策略列表页解析的核心是从HTML中提取房源链接和基础信息。用Scrapy的Selector配合XPath或CSS选择器完成。示例spider如下# spiders/rent_spider.py import scrapy from scrapy.loader import ItemLoader from rentCrawler.items import RentItem class RentSpider(scrapy.Spider): name rent_spider def start_requests(self): urls [ fhttps://example-rental-platform.com/rent/{page} for page in range(1, 51) ] for url in urls: yield scrapy.Request(urlurl, callbackself.parse_list) def parse_list(self, response): # 提取每个房源详情页链接 house_links response.xpath(//div[contains(class,house-item)]/a/href).getall() for link in house_links: yield scrapy.Request(urlresponse.urljoin(link), callbackself.parse_detail)代码逻辑很简单但有几个容易踩的坑URL拼接必须用response.urljoin很多房源链接是相对路径直接拿过来请求会404。列表页和详情页要区分回调函数列表页只做链接提取详情页才做完整数据解析职责分离复爬时也好维护。分页循环要设置终止条件我的示例里写死了50页实际应该根据页面上的总页数动态计算否则网站改版后爬虫会默默空转。3.3 详情页解析与字段清洗时机详情页通常包含最完整的信息比如小区名、具体楼层、房屋朝向、配套设施。这些字段在列表页可能只有简略版本所以主数据以详情页为准。def parse_detail(self, response): loader ItemLoader(itemRentItem(), responseresponse) loader.add_xpath(title, //h1[classhouse-title]/text()) loader.add_xpath(price, //span[classprice-num]/text()) loader.add_xpath(area, //div[classarea]/span[2]/text()) loader.add_xpath(layout, //div[classlayout]/text()) loader.add_xpath(region, //div[classregion]/a[1]/text()) loader.add_value(source_url, response.url) loader.add_value(source_platform, self.platform_name) yield loader.load_item()需要提醒的是这个阶段拿到的字段大部分是带杂质的字符串。比如价格可能是4500元/月或者4500面积可能是85㎡ 朝南混在一起。我建议不要在爬虫阶段做太多清洗先原样入库存一份清洗逻辑统一放到后面的数据处理模块。原因有两个一是爬虫阶段做清洗容易出错且难以追溯二是保留原始数据可以在清洗出问题时重新处理不用重新爬一遍。不过有一个例外source_url去重一定要在入库前解决。正如前面所说给这个字段加唯一索引让数据库替你挡下重复数据。3.4 反爬策略的实操经验UA、代理与请求头特征租房平台的反爬通常比电商平台温和但也不容小觑。我总结出三个最实用的手段常见UA池从几十个真实浏览器的User-Agent里随机挑选至少保证每次请求的UA不重复。Scrapy可以在middlewares.py里写一个简单的随机UA中间件。请求头完整性注意Referer、Accept-Language这些字段要保持一致很多反爬只校验Referer。代理池如果有预算可以买代理没有的话重点做延迟控制和日常频率控制尽量别触发封IP。# middlewares.py 中随机UA中间件的核心逻辑 import random class RandomUserAgentMiddleware: def __init__(self, user_agents): self.user_agents user_agents classmethod def from_crawler(cls, crawler): return cls(crawler.settings.get(USER_AGENTS, [])) def process_request(self, request, spider): request.headers[User-Agent] random.choice(self.user_agents)这里要注意RandomUserAgentMiddleware需要你在settings.py里配置USER_AGENTS列表并启用这个中间件。有一个隐藏坑是某些网站会校验UA与操作系统、浏览器版本的匹配度如果你把所有UA五花八门列在一起看起来反而更可疑。我当时做法是只保留Chrome和Edge的主流版本UA并且把对应的Sec-Fetch-*头也一并配上。4. 数据清洗与特征工程从原始数据到可用数据集爬虫跑完之后你会得到一堆脏乱差的原始数据。以我这次项目为例原始数据量大概18000条清洗之后只剩15000条左右能用这个损耗率很正常。4.1 常见脏数据的形态与处理策略我让学弟把清洗前的数据导出了一份肉眼扫了一遍发现的问题基本是这几类脏数据类型示例处理策略价格单位不统一4.5千/月、4500元/月、面议统一转为数字面议价格删除或置空面积格式混合85㎡、85平、85平米正则提取数字部分户型字段不规整2室1厅1卫、两室一厅、2-1-1按数字提取格式化为2室1厅区域缺失只有小区名没有行政区根据地址库或已知坐标反查补齐重复房源同一链接被抓两次依靠source_url唯一索引去重经纬度偏移坐标明显不在城市范围内调用逆地理编码接口修正价格单位不统一是最常见的坑好几种表述都是5000左右或者4.5千。写一个统一转换函数将所有价格归一化为以元/月为单位的整数。import re def normalize_price(raw_price): if not raw_price: return None raw_price raw_price.strip() # 匹配4.5千这种格式 match re.search(r([\d.])\s*(千|万)?, raw_price) if not match: return None value float(match.group(1)) unit match.group(2) if unit 千: value * 1000 elif unit 万: value * 10000 return int(value)这种函数写起来不难但很体现基本功。现在很多工具都有现成的解析库但毕设场景我更推荐自己手写因为老师会问细节。4.2 特征工程给推荐系统准备料清洗后的数据还不能直接喂给推荐算法你需要先完成特征工程。对房源这种结构化数据特征工程主要做三件事第一为文本类字段生成向量。房源标题和描述属于长文本我用TF-IDF把文本转成稀疏向量。这一步的意义在于后面做大模型问答或内容推荐时需要计算文本间的相似度。from sklearn.feature_extraction.text import TfidfVectorizer # 假设descriptions是清洗后的房源描述列表 vectorizer TfidfVectorizer(max_features5000, stop_wordsenglish) tfidf_matrix vectorizer.fit_transform(descriptions)TF-IDF的细节是max_features不要设太大毕设级项目5000足够了。太大会引入噪声增加计算开销而且解释性变差。第二为类别字段做编码。区域、户型、装修情况都是离散类别直接做One-Hot会让维度爆炸。更合理的做法是用Label Encoding或者按频数编码。比如区域字段北京有十几个区直接数字编码即可。第三构造交叉特征。比如每平方米租金就是价格除以面积这个特征比单独的价格更能反映房源定位。还有房源的发布时间距今的天数可以衡量房源的新旧程度。# 构造单价特征 df[price_per_sqm] df[price] / df[area] # 构造房源年龄特征 df[days_since_publish] (pd.Timestamp.now() - pd.to_datetime(df[publish_time])).dt.days这些交叉特征后面喂给推荐模型时效果会明显好于只用原始字段。答辩时如果被问你是怎么提高推荐精度的特征工程就是最好的回答素材。5. 数据可视化看板让数据自己会说话数据可视化不是画几张图那么简单而是要回答这个数据集的规律是什么哪些信息对用户最有价值这两个问题。我对租房数据看板的设计思路是先让用户看到整体市场情况再提供交互条件进行下钻分析。5.1 看板应该包含哪几类图表根据数据特点和项目目标我设计了一个四区块看板价格分布直方图横轴为价格区间纵轴为房源数量。能直观看出主力租金区间方便回答这个城市租房大概多少钱。区域均价条形图按行政区聚合平均租金以条形图展示。配合地图使用效果更好。户型占比饼图/环形图展示一居室、两居室、三居室等的占比情况。面积与价格散点图横轴面积纵轴价格每个点代表一个房源。能看出面积和价格的相关性还能发现一些异常点价格特别高的可能看是豪宅还是录入错误。这四个图表各有侧重分布、区域、结构、相关性正好能覆盖数据探索的主要维度。5.2 Flask后端JSON接口设计规范后端接口的返回格式要统一这是和前端对接时最容易出问题的地方。我定的规范是{ code: 0, message: success, data: { ... } }Flask路由返回JSON的方式很简单from flask import Flask, jsonify import pymysql app Flask(__name__) def get_db_connection(): return pymysql.connect( hostlocalhost, userroot, password123456, databaserent_db, charsetutf8mb4 ) app.route(/api/price_distribution) def price_distribution(): conn get_db_connection() cursor conn.cursor() cursor.execute( SELECT CASE WHEN price 2000 THEN 0-2000 WHEN price 4000 THEN 2000-4000 WHEN price 6000 THEN 4000-6000 WHEN price 10000 THEN 6000-10000 ELSE 10000 END AS price_range, COUNT(*) AS cnt FROM rent_info GROUP BY price_range ORDER BY price_range ) rows cursor.fetchall() conn.close() data { ranges: [r[0] for r in rows], counts: [r[1] for r in rows] } return jsonify({code: 0, message: success, data: data})这里有一个容易踩的坑数据库连接用完后必须关闭。我见过很多同学每请求一次就创建连接但忘记close跑几个小时看板接口就挂掉。正确做法是使用连接池但毕设层面每次请求关连接就够了只要养成习惯。5.3 前端页面的交互设计细节ECharts图表初始化之后还可以加一些交互功能。我建议必须做的一个交互是点击区域条形图的某个区散点图联动更新为只看该区的数据。实现思路很简单给条形图绑定点击事件触发后重新请求接口并更新散点图。regionChart.on(click, function(params) { const region params.name; fetch(/api/scatter?region${encodeURIComponent(region)}) .then(res res.json()) .then(data { scatterChart.setOption({ series: [{ data: data.data.map(item [item.area, item.price]) }] }); }); });6. 推荐系统从协同过滤到用户行为画像推荐系统是整个平台里最有技术含量的模块也是答辩时的高频提问区。我选择了两条路线结合基于用户的协同过滤做基础推荐再用大模型做语义层面的智能匹配。6.1 基于协同过滤的房源推荐协同过滤的思路是和你相似的人也看了这些房源。不过租房数据天然缺少用户行为因为用户不会在爬虫数据集留下点击记录。所以我用了一个变通方案用房源内容特征模拟用户偏好。具体做法如下根据用户浏览过的房源这个可以在平台内记录组成用户的兴趣房源集合。对用户兴趣集合中的房源特征价格、面积、户型、区域、文本向量求平均构成用户画像向量。对于未浏览的房源计算其画像向量与用户画像向量的余弦相似度取Top-N推荐。这种方式在推荐系统里叫基于内容推荐实现简单还不用处理冷启动问题。from sklearn.metrics.pairwise import cosine_similarity import numpy as np def recommend_by_content(user_interest_indices, feature_matrix, top_n10): # 用户画像向量 兴趣房源特征的平均 user_profile feature_matrix[user_interest_indices].mean(axis0) # 计算与所有房源的相似度 similarities cosine_similarity(user_profile.reshape(1, -1), feature_matrix)[0] # 排除已浏览的 similarity_rank np.argsort(-similarities) results [] for idx in similarity_rank: if idx not in user_interest_indices and len(results) top_n: results.append(idx) return results这段代码的逻辑对于毕设答辩足够了但你要能解释清楚为什么用余弦相似度而不是欧氏距离因为余弦相似度对向量的长度不敏感更适合衡量方向一致性。比如面积是一个绝对数值但用户可能既看50平的也看60平的余弦相似度在处理这种成比例变化时更合理。6.2 大模型接入让用户用自然语言找房大模型接入是项目的亮点也是最容易画蛇添足的地方。我见过很多项目把大模型集成当成聊天机器人展示和业务毫无关联这样答辩时必然翻车。合理的做法是用大模型解析用户自然语言需求转化为结构化查询条件然后回数据表查询。举个例子用户输入朝阳区或者海淀区的两居室预算5000以内要有地铁。我的系统流程是大模型从这句话里提取三个关键要素区域列表、户型、预算上限。校验提取结果抽取出JSON格式条件。JSON条件传给后端查询函数返回匹配房源列表。接入大模型的方式可以直接用API调用也可以本地部署轻量模型。从毕设成本考虑我建议用云API但要在代码里做好prompt设计和返回校验。# 大模型意图解析函数的简化版 def parse_user_need(raw_text): prompt f 根据用户输入的找房需求提取结构化信息。 用户输入{raw_text} 输出JSON格式字段包括 - regions: 期望区域列表 - layouts: 期望户型列表 - max_price: 最大月租金整数 - keywords: 其他关键词列表 response call_llm_api(prompt) # 假设已有封装 return parse_json(response)大模型输出的JSON不一定规范必须有校验环节。我建议使用json.loads尝试解析失败则净化后重试一次再失败就把原始文本交给后端用正则匹配保证系统可用性。6.3 两种推荐逻辑的融合策略协同过滤和内容推荐各有利弊内容推荐没有冷启动问题但需要用户先有浏览记录大模型解析能处理复杂句式但依赖外部服务稳定性。融合策略很简单列表页和详情页推荐用内容推荐搜索和问答场景用大模型解析SQL查询。前者实时性要求高用本地计算后者可以接受一定延迟调API也没问题。7. 系统集成与部署从开发机到能演示的完整平台模块开发完成后我花了整整两天把整个系统串起来包括统一认证、数据库初始化、定时爬虫调度、前端路由跳转等。这里有几个关键点值得单独说。7.1 项目结构规划项目结构要清晰避免把所有代码扔一堆。我使用的结构是这样的rent_platform/ ├── crawler/ # Scrapy爬虫目录 │ ├── spiders/ │ ├── middlewares.py │ ├── items.py │ └── settings.py ├── backend/ # Flask后端 │ ├── app.py # 主入口与路由 │ ├── api/ # 各模块API │ ├── recommender/ # 推荐系统 │ └── models.py # 数据库操作 ├── frontend/ # 静态页面 │ ├── templates/ # Jinja2模板 │ ├── static/ │ ├── css/ │ └── js/ ├── analysis/ # 数据分析notebook脚本 │ ├── clean.py │ ├── features.py │ └── visualize.py └── requirements.txt很多同学把爬虫和Web代码混在一起项目规模一大就会乱。我建议至少按采集-分析-展示三层来划分目录。7.2 定时采集与增量更新的实现方案房源的时效性很强数据应该定期更新。我的方案是用系统定时任务调用Scrapy命令。# 定时执行爬虫任务的cron配置每天凌晨2点 0 2 * * * cd /path/to/rent_platform/crawler scrapy crawl rent_spider /tmp/rent_crawler.log 21在Windows环境可以用计划任务Linux则用crontab。如果不想依赖系统任务也可以在Flask后台线程里用APScheduler定时触发。毕设演示时不需要真实定时但代码里要预留这个接口告诉答辩老师系统是有更新机制的。7.3 答辩演示环境的三个准备要点答辩演示翻车往往不是功能有问题而是环境准备不充分。三个必须提前检查的点数据库数据必须提前灌好不要在答辩现场跑爬虫采集数据时间不可控。大模型API要有降级方案如果现场网络不稳定API调用失败系统要能退回纯SQL查询模式。可视化图表要预生成一份静态快照即使前端动态渲染失败也能展示静态截图。我教给学弟的做法是先打开静态快照页面做完整讲解再演示一次实时刷新看板双保险。8. 踩坑记录真实开发中遇到的问题与解决链路整个开发过程大概持续了四五个星期中间踩了不少坑。我把其中比较有价值的经验列出来希望能帮你少走弯路。8.1 爬虫被反爬的完整排查过程有一段时间爬虫跑到第2000条左右就开始返回验证码页面我当时的第一反应是加代理但问题并没有解决。后来一步步排查才发现UA列表配置有误导致有一半请求用的同一个UA被识别后封禁。请求头中缺少Accept-Encoding服务器认为请求异常返回了压缩验证页面。没有控制爬虫运行时间段深夜爬虫的高频请求更容易触发风控。修复方案很明确补充完整请求头、修正UA池、下载延迟从0.8秒调整到2秒。验证后发现重新爬取时不再触发验证码问题解决。这个排查过程本身就值得写进报告里它能展示你具备定位问题、分步验证的能力。8.2 数据清洗阶段发现房源价格差10倍的真相清洗时发现同一小区同时存在几套房源价格区间跨度极大。一开始我以为是数据错误后来对比经纬度才发现这些域同名的房源实际上是不同的楼盘组团有回迁房也有商品房价格自然差距巨大。这提醒我数据清洗时不只要关注格式还要注意业务逻辑的正确性。如果不去了解业务很容易把正常数据当异常删掉。8.3 推荐结果全线崩坏的原因推荐系统上线后第一次测试发现推荐的房源和用户兴趣毫无关联甚至推荐了和搜索词完全无关的房源。排查后定位到两个问题特征工程阶段文本向量没有做归一化导致价格这类数值特征的权重完全被文本覆盖。用户画像向量计算用了所有浏览房源的平均值但用户浏览了多个不同预算等级的房源平均值不只没有代表性还扭曲了推荐方向。修复方法是先对每个数值特征做归一化再对文本向量做L2归一化最后对用户浏览房源使用衰减加权平均——越近浏览的房源权重越高。修改之后推荐效果明显改善。8.4 大模型API返回格式漂移的兜底策略接入大模型的第三天后发现偶尔会遇到API返回的不是JSON字符串而是夹杂着自然语言描述的Markdown格式文本。这个问题非常隐蔽属于模型的随机性行为。最后的兜底策略是写一个robust_parse_json函数用多种手段尝试提取JSON片段失败时降级到正则匹配。这个函数放在整个系统的最外圈保证任何异常输入都不会让推荐系统崩溃。9. 项目后续还能怎么扩展毕设交付不意味着项目终止如果你之后想继续完善我有几个方向建议接入地图组件把房源经纬度绘制成散点图叠加在地图上形成空间分布热力图视觉效果和分区分析能力都会大幅提升。引入价格预测模型用回归算法预测房源合理租金可以结合面积、户型、区域、周边配套等特征训练一个简单的回归器。构建用户侧的Web端目前的系统还偏向数据管理端如果做一个面向普通租客的搜索界面加上收藏和浏览历史功能推荐系统就能真正跑起来。增加API服务能力把数据采集、分析、推荐都封装成API接入小程序或者App扩展使用场景。这些扩展方向都可以在不改变核心架构的前提下逐步添加也让项目有了持续生长的空间。我个人在带完这个整个开发流程之后的一个体会是技术选型别追求多高级关键是每个模块都能讲清楚为什么这么做。Scrapy能讲出框架设计的好处Pandas清洗能讲出业务理解推荐能讲出算法原理大模型接入能讲出工程兜底答辩和简历基本就稳了。