Python全栈租房数据采集分析与推荐平台:从爬虫到大模型的全链路实践
发布时间:2026/10/6 13:36:41 作者:尧图编辑部 阅读量:1,286

我接触过不少毕业设计选题如果要找出一个能把爬虫、数据分析、推荐算法、可视化、大模型全部串联起来的题目“Python全栈租房数据采集分析与推荐平台”绝对排得上前三。它表面上看是一个跟租房相关的Web平台实际上是一套完整的数据流水线Scrapy爬虫负责把房源信息采下来经过清洗和存储进入数据库后端基于内容相似度给用户推荐房源前端用可视化大屏把市场行情展示得一目了然最后还能用大模型做房源摘要和智能问答。这篇文章就是沿着这条流水线从爬虫工程化、数据治理、推荐系统设计到可视化大屏、大模型落地和答辩前要踩的坑一步步聊很多细节都是做了几轮之后才发现的希望能让你少走点弯路。1. 选题拆解与技术选型为什么是“租房全栈”1.1 租房数据的天然优势无论毕设还是竞赛数据题材选得好后面会省很多事。租房数据有个其他领域比不了的优势字段天然丰富且业务含义强。价格、面积、户型、朝向、楼层、小区、城区、地铁距离、发布时间这些字段随便拿出来几个就能做出一套像样的分析报告。而且租房网站的页面结构相对固定公开页面多对爬虫新手非常友好不用处理过于复杂的权限验证。更关键的一点租房数据天然适合做推荐系统。推荐系统本质上就是猜用户喜欢什么物品而房源恰恰有明确的物品特征向量。一个用户喜欢“地铁附近、两室一厅、月租5000以内”这个偏好可以用结构化条件表示也可以从文本描述里抽取出来。相比电影、新闻这种长尾内容房源推荐的解释性和可评估性都强得多答辩时讲清楚推荐逻辑也不难。1.2 技术栈选择的三个理由我先说说语言选型。这个项目用Python几乎是必然的理由不复杂Scrapy是现成的爬虫框架自带异步并发、去重、中间件和Pipeline机制直接用requestsBeautifulSoup去拼的工程化程度完全不是一个量级。再往后的pandas做清洗、sklearn做推荐、pyecharts做可视化全部在同一套语言生态里完成不需要跨语言调来调去。如果你的目标是展示“全栈”能力Python全家桶的天然衔接能力就是最大优势。前端展示不一定要上VueReact用Flask或FastAPI做一个轻量服务端页面里嵌入pyecharts生成的图表HTML整个交互链就通了。做毕设不需要为了难度盲目引入微服务、容器编排这些偏生产环境的组件但数据采集、存储、分析、推荐、展示这条主链路必须完整。1.3 整体架构怎么串整个平台可以拆成四条主线采集层、存储层、分析推荐层、展示层。采集层用Scrapy写爬虫把房源列表页和详情页的数据全部抓下来存储层用MySQL存结构化字段中间做好去重和清洗分析推荐层用pandas做统计用scikit-learn算相似度、跑推荐展示层做一个可视化大屏加推荐结果页顺便把统计报告输出成图表。这四条线各自独立、前后衔接最大的好处是每一层都能单独验收。爬虫先交一版数据清洗再交一版干净数据推荐系统基于干净数据跑出结果可视化基于推荐和统计结果出图。答辩的时候如果某个环节卡住了不至于整个项目全崩——这个分层设计的价值在紧张环境里特别明显。2. 数据源头Scrapy爬虫从“能跑到能扛”2.1 别把爬虫写成一次性脚本爬虫模块最常见的翻车方式是写一个spider抓到一个页面就交差然后把数据直接print到控制台。这种做法在毕设级别的项目里完全不够用。Scrapy的工程化价值体现在几个关键组件上Items定义结构化字段、Pipelines负责清洗和入库、Middlewares处理请求头轮换和异常重试、Settings统一控制并发数和下载延迟。把这几个组件都配上再动手写解析逻辑爬虫才真正具备“可持续运转”的能力。以房源字段为例我在Items里定义了二十多个字段包括标题、小区、城区、租金、面积、户型、朝向、楼层、总层数、装修、发布时间、经纬度、房源链接、唯一编号等。这些字段不是拍脑袋定的而是根据后续分析需求反推的。区域要画地图热力图所以必须拿经纬度价格趋势要做时间维度所以发布时间必填户型描述要做推荐特征所以户型和面积一个都不能少。先想清楚“数据要用来做什么”再定字段比爬完再补字段效率高得多。2.2 核心代码骨架从列表页到详情页这里给一个能直接用的爬虫骨架。先定义字段的Itemsimport scrapy class HouseItem(scrapy.Item): house_id scrapy.Field() # 房源唯一编号 title scrapy.Field() # 标题 district scrapy.Field() # 城区 community scrapy.Field() # 小区名称 price scrapy.Field() # 月租金/月 area scrapy.Field() # 面积/㎡ layout scrapy.Field() # 户型如2室1厅 orientation scrapy.Field() # 朝向 floor scrapy.Field() # 所在楼层 total_floor scrapy.Field() # 总楼层 decoration scrapy.Field() # 装修情况 publish_time scrapy.Field() # 发布时间 lng scrapy.Field() # 经度 lat scrapy.Field() # 纬度 detail_url scrapy.Field() # 详情页链接然后是Spider的主体逻辑。主流程是先请求列表页从列表页解析出每个房源的详情页URL再请求详情页最后在详情页Parser里提取完整字段。列表页结构稳定用CSS选择器足够不需要上Seleniumimport scrapy from ..items import HouseItem class RentSpider(scrapy.Spider): name rent allowed_domains [example.com] start_urls [https://www.example.com/zufang/pg1/] def parse(self, response): # 提取详情页链接 for href in response.css(.house-title a::attr(href)).getall(): yield response.follow(href, callbackself.parse_detail) # 提取下一页链接实现翻页 next_page response.css(.page-next::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse) def parse_detail(self, response): item HouseItem() item[house_id] response.url.split(/)[-2] item[title] response.css(h1::text).get() item[price] response.css(.price::text).get() item[area] response.css(.area::text).get() # 其他字段同理逻辑清晰逐项提取 yield item这里有两个细节容易被忽略。第一列表页和详情页的请求要分开写别在一个函数里全部解析否则后续想单独调试详情页Parser时会很难受。第二翻页判断不能只写“如果下一页存在就继续”还要设定一个最大页数上限防止程序异常时无限翻下去也方便控制总爬取量——毕设阶段爬5000到10000条就足够支撑后续分析和推荐了。2.3 反爬应对与采集效率的平衡租房网站的反爬不算严格但完全裸奔还是会被限制。我常用的三板斧是UA轮换、下载延迟和失败重试。UA轮换直接用fake_useragent这个库Scrapy里写一个Downloader Middleware每次请求随机换一个UA代码很短from fake_useragent import UserAgent class RandomUserAgentMiddleware: def __init__(self): self.ua UserAgent() def process_request(self, request, spider): request.headers[User-Agent] self.ua.random下载延迟我一般设0.3到0.5秒配合Scrapy的AUTO_THROTTLE自动限速功能。AUTO_THROTTLE会根据服务器响应时间动态调整请求速度响应慢了就自动放慢响应快就适度加快实测比固定延迟更稳。另外一定要开启重试机制把重试次数调到3次网络波动和临时限制导致的请求失败大部分能靠重试解决。如果遇到数据写在页面里、必须执行JavaScript才能拿到的字段我建议用ScrapySelenium或ScrapyPlaywright的组合方案。通常做法是仅在详情页需要执行JS时才启动浏览器渲染其他请求走普通下载器。千万别把每个列表请求都过一遍浏览器性能和稳定性都会崩。我的一个简单判断标准是优先检查页面里是否存在隐藏的XHR接口如果接口能直接拿到JSON数据就优先解析接口比渲染页面省太多事。2.4 数据字段的去重与增量更新去重是爬虫模块很容易被忽略的环节。Scrapy自带了RFPDupeFilter默认会对请求URL做指纹去重能防止同一个详情页被重复爬。但这还不够因为同一套房源可能在列表页多次出现也可能在不同条件下返回不同URL所以我在Pipeline里加了一道自定义去重以house_id为唯一键查询数据库存在就跳过不存在才插入。这个简单判断大大减轻了数据清洗的压力。增量更新方面我会在采集完成后跑一个定时任务只抓最近24小时内新发布的房源。实现思路是列表页解析时判断每条房源的发布时间超过24小时就停止翻页。这个逻辑让系统看起来有“持续运行”的能力答辩时演示完首次全量采集再演示增量采集整个数据链路就完整了。3. 数据清洗与存储最不起眼却最耗时的环节3.1 脏数据长什么样、怎么处理爬下来的数据大概率是没法直接用的。我踩过的典型坑包括价格字段带着“元/月”这种单位、面积字段为空、“2室1厅”和“二室一厅”混写、同一个小区名称有多个写法、经纬度缺失、假房源标价极低或极高。这些问题如果不在入库前处理干净后面推荐和可视化全都会出问题。我的清洗流程分四步。第一步去重以house_id判断去掉重复房源第二步格式清洗把“4500元/月”这类字符串转成数值把“2室1厅”统一成标准格式第三步缺失值处理经纬度缺失的调用逆地理编码补齐面积缺失的按同户型平均值填充第四步异常值过滤把租金低于200元/月、面积小于5平方米或大于200平方米的房源标记为疑似异常并剔除出推荐候选集。这里有一个细节值得记录不要一上来就删数据。先把异常数据单独放一张表或一个CSV文件保留现场。因为你不确定是爬虫解析错了还是平台本身就有虚假房源。保留原始数据方便回溯答辩时也拿得出“我是怎么发现异常、怎么处理异常”的故事。处理代码可以写得非常简短用pandas几行就搞定import pandas as pd df pd.read_sql(SELECT * FROM house, engine) # 格式化把4500元/月这类字符串提取数字 df[price] df[price].str.extract(r(\d)).astype(float) # 剔除明显异常的房源 df df[(df[price] 300) (df[price] 100000)] df df[(df[area] 5) (df[area] 300)] # 户型统一两室一厅、二室一厅 等写法做正则替换 df[layout] df[layout].replace({两室一厅: 2室1厅, 二室一厅: 2室1厅})3.2 存储选型与表结构设计存储层我推荐MySQL不是因为它比MongoDB厉害而是因为租房数据的字段结构非常固定用关系型模型管理最省心。一张房源表、一张用户表如果有注册登录功能、一张用户行为表收藏、浏览记录再加一张推荐结果表四张表足够撑起整个平台。MongoDB的优势在于文档结构灵活但毕设项目对灵活性要求不高反而对“SQL查询聚合”的需求不少——比如统计各城区均价一条GROUP BY就能搞定。房源表的核心结构和索引设计如下CREATE TABLE house ( house_id VARCHAR(32) PRIMARY KEY, title VARCHAR(255), district VARCHAR(50), community VARCHAR(100), price DECIMAL(10,2), area DECIMAL(10,2), layout VARCHAR(20), orientation VARCHAR(20), lng DECIMAL(10,6), lat DECIMAL(10,6), publish_time DATETIME, create_time DATETIME, INDEX idx_district_price (district, price), INDEX idx_publish_time (publish_time) );主键是house_id业务索引建在district城区和price租金两个字段上因为绝大多数查询场景是“按城区筛、按价格排序”。如果数据量在十万条以内建好这两个索引后查询性能完全够用。给publish_time加一个索引是因为增量更新和趋势分析都要按时间筛选。3.3 数据质量校验与可视化联动数据入库之后别急着写推荐和可视化先做一轮质量校验。我会写一个简单的统计脚本分别统计房源总量、各字段非空率、各城区房源数量、价格和面积的极值、挂牌量最高的小区Top10。这些数字一方面能验证清洗效果另一方面本身就是可视化大屏的一部分数据。比如房源总量、平均租金、各城区房源数这些概览数字就是从质量校验脚本里顺手输出的一举两得。我有一段时间踩过数据入库后才发现大屏图表全是空值的坑后来养成了习惯每次数据更新完先跑校验脚本再刷新大屏。校验脚本通过的标准非常朴素——各字段非空率不低于90%各城区房源数量不至于为零价格分布覆盖合理区间。4. 推荐系统让平台学会猜心思4.1 推荐场景分析与算法选型推荐系统在这个项目里要解决的真实问题是什么是新用户没有可用的历史行为数据时的冷启动问题。真实的租房场景中新用户打开平台不可能已经有一堆浏览记录和收藏记录所以纯协同过滤算法在这种场景下会失效。最稳妥的组合是基于内容的推荐作为主线协同过滤作为增强。算法选型对比起来很简单协同过滤UserCF、ItemCF依赖用户行为矩阵数据稀疏时效果很差基于内容的推荐完全不依赖用户行为只要有房源特征就能算相似度但泛化性差容易推荐同类房源。毕设项目的数据量有限用户行为几乎为零所以我的方案是“特征相似度规则热榜”双路召回最终结果用简单加权排序合并。4.2 基于内容的推荐怎么实现基于内容的推荐核心是构造房源特征向量并计算相似度。房源特征分两类数值特征和类别特征。数值特征是价格、面积、楼层类别特征是地段、户型、朝向、装修情况。类别特征不能直接用裸数值要做One-Hot编码或者先用标签编码再参与计算。实际操作我建议先做特征离散化。比如价格不直接用“4500”这个数值参与距离计算而是划分成价格区间“3000-5000”这样推荐结果更稳定不会因为个别极端值把相似度拉偏。面积同理按小户型、中户型、大户型分桶。然后把全部特征拼成一个文本向量用余弦相似度计算房源之间的距离。Cosine距离对稀疏向量友好类别特征和数值特征都能参与计算from sklearn.feature_extraction.text import CountVectorizer from sklearn.metrics.pairwise import cosine_similarity # features是每个房源的特征拼接文本例如 # 价格区间3000-5000 面积区间小户型 城区朝阳区 户型两室一厅 vec CountVectorizer().fit_transform(features) similarity_matrix cosine_similarity(vec, vec)这里的features可以理解为每套房源的“特征一句话”。用CountVectorizer把文本转成向量计算出的矩阵就是所有房源之间的相似度矩阵。推荐时给用户一个种子房源比如用户点击查看的某一套从相似度矩阵里取TopN再结合热门规则做重排就是一套可解释的推荐流程。4.3 冷启动问题怎么破冷启动还有一层意思是“没有任何种子房源的初始推荐”。我的做法是做一个偏好引导问卷让用户选择预算区间、心仪城区、户型偏好然后把这些偏好转成跟房源特征同样的编码格式直接跟房源向量算相似度。这个方案逻辑简单、演示效果直接答辩时也能讲清楚“我是从交互设计上解决了冷启动”。如果用户连问卷也没填就落到兜底推荐上按城区热门指数排序结合平台内被收藏次数最多的房源推“热门房源榜”。当推荐结果不够精准的时候一个有说服力的热榜比一个失败的自适应算法强得多。4.4 推荐结果怎么评估由于没有真实用户做在线测试离线评估是一个必然选择。我的评估指标很简单留一套测试集每个用户或模拟用户只保留一个正样本房源用推荐算法从其余房源中召回Top10如果正样本在Top10里就算命中最后统计命中率。命中率做到50%以上答辩就有底气说“推荐效果可靠”。真正运营起来会发现离线命中率再高也不代表用户体验好。所以还要做一层业务校验推荐出来的房源必须满足基本规则约束比如同城区优先、价格浮动不超过种子房源正负30%、排除虚假房源。这些规则在推荐重排阶段统一执行优先级比算法分数更高并且全部用结构化字段在重排时直接过滤不依赖任何黑盒。5. 可视化大屏答辩时最能吸引目光的部分5.1 大屏设计的核心思路可视化模块的定位不是堆图表而是把一个开放性问题讲清楚这个城市的租房市场到底什么情况。大屏布局我建议分成四个区域顶部放核心指标房源总量、平均租金、最热门城区、数据更新时间中间放地图热力展示房源分布和租金高低左侧放各城区均价排行榜右侧放户型占比和租金价格段分布。整个页面从上到下、从左到右是一个完整的阅读动线。不要为了炫技堆十五个图表五个以内、每个图表都有明确业务结论才是正确姿势。图表选型也非常固定地图用热力图区域对比用柱状图结构占比用饼图环图趋势变化用折线图描述性关键词用词云。不要再额外引入复杂的可视化方案毕设的时间线经不起折腾。5.2 pyecharts Flask的落地方式可视化大屏我推荐用pyecharts它生成的图表是纯HTML和JavaScript不需要额外部署前端框架。实现方式分三步第一步写一个Python脚本定时读取MySQL数据输出JSON格式的统计数据第二步用pyecharts把这些JSON数据渲染成独立HTML图表第三步用一个Flask应用把图表页面整合到一个大屏里用Ajax请求动态刷新。pyecharts的代码非常简洁拿最常用的柱状图举例from pyecharts import options as opts from pyecharts.charts import Bar bar ( Bar() .add_xaxis([朝阳区, 海淀区, 丰台区, 通州区, 昌平区]) .add_yaxis(平均租金, [6500, 7200, 4300, 3800, 3300]) .set_global_opts(title_optsopts.TitleOpts(title各城区平均租金)) ) bar.render(district_avg_price.html)图表文件渲染出来之后在Flask里只需要一个路由把这些HTML文件嵌入Jinja2模板所有图表就能合成一个页面。页面的刷新建议做成定时Ajax轮询每5到10秒请求一次最新统计接口大屏就会自动更新不需要手动刷网页。5.3 地图可视化的坑地图可视化是整个项目里最容易出问题的地方。第一个坑是经纬度获取。爬虫拿到的通常是“XX小区”这种文本地址需要调用地理编码服务转换成经纬度。实测经验是直接调用国内主流地图服务的逆地理编码API最省事但要注意每天调用次数限制建议在入库时一次性把经纬度补齐并缓存不要实时调用。第二个坑是坐标系偏移。国内地图服务返回的都是GCJ-02坐标系如果拿通用地图底图去渲染会出现偏移。我建议直接用高德、百度这类国内地图服务提供的底图JS库坐标系天然匹配不用自己做转换。第三个坑是小区重名的歧义同一个小区名可能在多个城区存在。处理办法是地理编码时优先结合地址里的城区关键字限定范围宁可缺失经纬度也不要给错坐标。6. 大模型在毕设里的落地姿势6.1 大模型能解决什么问题把大模型放进项目里不是为了给答辩加分而生硬堆砌而是要解决真实问题。这个项目里大模型最合适的三个落地点是房源描述摘要、智能搜索问答、偏好理解。房源描述一般很长用户浏览列表页时需要一个简洁摘要大模型可以对房源描述做压缩提炼提取核心卖点。智能问答则让用户直接用自然语言提问比如“西二旗附近两室一厅预算6000以内的房源有哪些”系统返回推荐结果。这三个场景本质上都依赖大模型的语言理解能力不依赖多复杂的推理能力所以实现成本低、可控性强。千万别一上来就做大模型自动写推荐文案这种确定性不高的活儿容易在答辩现场出洋相。6.2 一个可落地的文本增强案例我做的第一个大模型增强功能是“房源描述摘要生成”。输入是原始房源描述文本输出是200字以内的核心卖点摘要。实现思路非常直接用LangChain或直接调用本地部署的开源模型API把提示词设计成“请提取以下房源描述中的核心卖点输出不超过200字”然后把模型输出存回数据库大屏上展示出来。实际代码可以精简成这样一个骨架def summarize_house_desc(raw_desc: str) - str: prompt ( 请提取以下房源描述中的核心卖点 输出不超过200字不要添加原文没有的信息。\n f房源描述{raw_desc} ) result call_local_llm(prompt) # 替换成你自己部署的模型服务 # 规则校验只保留在原文中出现过的关键词 safe_keywords [kw for kw in [地铁, 精装, 朝南] if kw in raw_desc] return validate_result(result, safe_keywords)如果觉得调用大模型API成本高或者不稳定可以换一个思路用NLP规则做一层兜底比如从描述中抽取关键词和句式跟大模型输出做融合。实际使用中我发现模型输出偶尔会出现房源描述里根本没有的信息比如凭空多出来的楼层或装修描述这种幻觉信息对租房场景完全不可接受。所以加规则校验是必需的只保留模型输出中包含原始描述关键词的句子其他部分丢弃。6.3 别让大模型成为黑盒在毕设项目中用到任何新技术原则都是可解释、可维护、可演示。大模型模块如果不加控制可能会出现两个问题响应延迟过长或者输出结果无法解释。响应延迟问题的解决办法很简单把大模型功能放在独立服务里异步执行不阻塞主链路前端先返回基础房源数据等大模型摘要生成后通过WebSocket推送补充。可维护性方面建议把所有大模型的提示词集中放到一个配置文件中不要把提示词散落在代码里。答辩时如果评委追问“你们的提示词是什么”你直接把配置亮出来逻辑清清楚楚。另一个小技巧是将大模型调用结果单独存一张表有就直接读缓存没有才去请求模型。台下演示时数据从缓存读速度快且稳定。7. 常见问题与排查技巧实录7.1 常见问题速查表整理了一份我在整个开发周期里遇到过的坑按模块分类方便你对照排查模块常见问题主要原因排查与解决方案Scrapy爬虫爬取数据全部为空选择器写错、页面改版、未开启JS渲染用Scrapy shell实时测试选择器先用单条URL调试Scrapy爬虫爬一会儿就被限制请求频率过高、未做UA轮换打开AUTO_THROTTLE下载延迟调大重试次数设为3数据清洗租金字段统计错乱字符串混单位“押一付三”混入数值入库前统一转成数值做类型强校验推荐系统相似度计算结果全是0特征向量太稀疏数值特征未归一化对数值特征做区间离散化类别特征加One-Hot可视化地图坐标偏移坐标系不匹配使用高德/百度底图确保地理编码接口返回的坐标与底图一致大模型响应时长太长每次请求都实时调用增加缓存表启动预热异步更新整体链路页面加载很慢图表文件太大、SQL查询无索引将图表数据预计算成JSON缓存检查SQL执行计划7.2 答辩前必须检查的几件事答辩前的检查清单我按优先级排个序。第一爬虫工程目录必须规范spider、items、pipelines、middlewares、settings各归各的别把逻辑全塞到一个文件里第二数据量要足够至少爬5000条以上太少的话推荐系统没有意义大屏也空得难看第三亲手跑通一次“从爬虫到可视化”的完整流程别在演示时掉链子第四准备一份README写明环境依赖、启动步骤、模块说明第五演示录一份视频存着防止现场网络或系统崩溃。还有一个被很多人忽略的小细节答辩演示前先重启一下数据库和服务清掉临时缓存。很多现场demo崩溃的原因不是代码逻辑问题而是进程跑久了、端口被占用、数据库连接数打满了。程序能稳定跑30分钟不崩比功能花里胡哨要重要得多。7.3 时间投入与项目迭代建议如果你的时间只有三个月我建议这样分配优先级第一个月主攻爬虫和数据清洗把数据量做足第二个月做存储、分析和推荐保证核心算法能跑通第三个月主要留给可视化大屏、大模型增强和整体联调。别把大量时间花在过度优化上毕设展示的是完整的工程链路不是单点性能。后续如果还想扩展可以考虑把房源多空对比功能做成图表或者把推荐系统升级为基于多兴趣向量的序列推荐。比较大的方向是引入用户收藏行为和浏览轨迹将协同过滤纳入推荐流程形成“内容推荐协同过滤规则约束”的融合推荐架构。顺着这个方向扩展写论文时也更有深度可讲。最后分享一个我自己的体会。这个项目最核心的价值不在于每个技术点有多难而在于它把爬虫、数据治理、推荐算法、可视化、大模型这几个独立的知识点串成了一条完整的链路。做完它你对“数据从哪来、怎么处理、如何产生价值”会有非常具体的体感。如果让我重新做一次我会先把5条核心数据链路用最简版本跑通再逐步加厚而不是一开始就追求每个模块都完美。先通后精毕业设计这个阶段最实用。如果你也在写这个题目建议收藏保存按着章节一步步推进遇到卡住的地方可以留言交流。