☰
Python+Flask租房数据分析系统:从爬虫采集到可视化展示
2026/9/27 23:55:21 网站建设 项目流程

简介:面向计算机相关专业毕业设计场景,这份基于Python+Flask的租房数据分析系统完整源码包,适合需要快速落地前后端分离项目、掌握数据分析与管理系统开发流程的学生参考,系统分为后台管理端与前台展示端,覆盖房屋信息管理、用户管理、租房数据统计、房屋资讯与个人中心等模块,并附带运行教程、数据库脚本与逻辑讲解文档。包内共573个文件、约23.51MB,主要类型包括49个Python源码、93个Vue前端页面、SQL数据库文件、SVG图标与配置批处理,其中Py文件对应业务逻辑、Vue文件构成界面,bat脚本便于安装和启动,整体目录清晰便于检索,已有70人学习下载。读者可获得完整项目源码、数据库备份、运行教程、逻辑讲解等资料,能对照理解用户管理、房屋筛选、热门区域与租金分布分析等实现方式,也可依据开发说明进行二次扩展,是毕业设计答辩准备与项目实战训练的有效素材。

1. 租房数据分析系统:毕业设计里最不容易翻车的完整闭环

每年毕设季都能看到大量选题撞车,但“基于Python+Flask的租房数据分析系统”这个题目之所以值得做,是因为它把毕业设计最看重的几件事全占齐了:有爬虫或数据集处理,有MySQL这类持久化存储,有Flask提供的后端API与网页交互,还有数据分析与可视化展示。一个系统做完,技术栈从数据获取一直铺到前端呈现,不是只写个CRUD交差那种。它解决的核心问题也很具体:把链家、贝壳这类平台上的租房数据抓下来,清洗后存进数据库,再按区域、户型、租金等维度做统计,最后用图表在网页上直观展示,附带对租房趋势的简单结论。适合的人群很明确:计算机相关专业、需要一份能讲清楚“为什么这么设计”的毕设项目,或者想用最短路径熟悉Python Web开发全流程的初学者。

2. 从零搭起开发环境:Python、Flask和MySQL的选型与安装细节

2.1 为什么这套系统选Flask而不是Django或Spring Boot

租房数据分析系统属于典型的中小型Web应用,数据量在几万到几十万条这个量级,Flask的轻量特性正好匹配。Flask不像Django自带ORM、Admin后台和模板系统那么重,但它给开发者保留了足够的控制权:路由自己定义,数据库用SQLAlchemy或原生SQL都行,模板用Jinja2,扩展按需安装。对毕设答辩来说,你反而更容易把每个组件讲清楚——因为Django帮你做的太多,被问到“这个功能怎么实现的”时容易卡壳。

另一个实际理由是学习曲线。Flask的Hello World只需要几行代码,新手能快速看到网页输出,这种即时反馈对建立信心很重要。相比Spring Boot要配Maven依赖、理解IoC容器,Flask的“请求进来→路由匹配→视图函数返回→模板渲染”这条链路非常直观。而且这个项目里数据分析是核心,Flask与Pandas、Matplotlib、Plotly这些Python数据分析库天然同处一个生态,不需要像Java那样跨语言传数据。

MySQL作为数据库的理由也简单:毕设答辩环境里MySQL最常见,面试问起来也能顺势说到索引、事务、SQL调优。SQLite虽然零配置更省事,但“租房数据分析系统”强调数据量级和分析查询,MySQL在几十万行数据下做GROUP BY和JOIN的稳定性更好,且安装配置教程多,出了问题容易搜到答案。

2.2 本地开发环境安装:Python、虚拟环境与MySQL的完整命令

以Windows环境为例。先确认Python版本,推荐3.9或3.10,这两个版本对Flask 2.x和Pandas 1.5.x的兼容性最稳定。打开命令行执行:

python --version pip --version

如果没有Python,去官网下载安装包,勾选“Add Python to PATH”。这里有一个经常让人栽跟头的点:同时装过Anaconda和官方Python的机器上,python命令指向的可能是旧版本。所以装完后先跑一遍上面的版本确认,再继续。

接下来创建虚拟环境。毕设项目最忌讳把依赖装进全局Python,因为不同项目对Flask版本的冲突会逐渐失控。用venv隔离:

mkdir rent_analysis cd rent_analysis python -m venv venv venv\Scripts\activate

命令行出现(venv)前缀说明虚拟环境已激活。然后安装核心依赖:

pip install flask==2.3.3 flask-sqlalchemy pandas mysqlclient

注意mysqlclient在Windows下经常装不上,因为需要编译环境。如果报错,改用PyMySQL:

pip install flask==2.3.3 flask-sqlalchemy pandas pymysql

PyMySQL和mysqlclient的差异在于一个是纯Python实现一个是C扩展,项目里用PyMySQL需要在数据库连接URL里额外指定端口和字符集,稍后配置数据库时会看到。

MySQL安装建议用MySQL 8.0社区版。安装时记住root密码,建议直接设为root,毕设环境不用纠结安全性,方便自己连接即可。安装完成后确认服务已启动:

mysql -u root -p

能进入MySQL命令行就说明服务正常。这个步骤值得截图留证,答辩时“环境搭建”这一页可以直接用。

2.3 数据库初始化:建库建表与连接字符串的配置

进入MySQL后建库,字符集一定要指定utf8mb4,否则后面存中文房源描述和小区名时会乱码:

CREATE DATABASE rent_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

然后在Flask项目的config.py里配置SQLAlchemy连接:

# config.py import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = 'your-secret-key' SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:root@localhost:3306/rent_db?charset=utf8mb4' SQLALCHEMY_TRACK_MODIFICATIONS = False

这里mysql+pymysql中的+pymysql是告诉SQLAlchemy用PyMySQL驱动连接MySQL。localhost:3306是默认地址和端口,charset=utf8mb4保证中文写入不乱码。SQLALCHEMY_TRACK_MODIFICATIONS设为False是为了关闭对象变更监控,省内存也去掉警告。

这个配置文件的参数是后期最容易查错的地方:密码不对会报Access denied,端口没改会报Can't connect,字符集漏了会出现中文乱码。答辩时问到数据库配置,顺手把这三项指出来讲,比背概念更有说服力。

3. 数据从哪来:爬虫采集与数据清洗的落地操作

3.1 爬取租房平台数据:Requests请求与解析的边界问题

租房数据有两个来源:一是直接用公开数据集,二是自己写爬虫抓取。字节跳动和Kaggle上有一些城市租房数据集,但数据时效性差且字段不全。自己做爬虫更可控,也能把“数据获取”这块写进毕设的创新点。以链家租房页面为例,常见的抓取方式是:

import requests from bs4 import BeautifulSoup headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } def fetch_page(city='sh', page=1): url = f'https://{city}.zu.lianjia.com/zufang/pg{page}/' resp = requests.get(url, headers=headers, timeout=10) resp.encoding = 'utf-8' soup = BeautifulSoup(resp.text, 'html.parser') items = soup.select('.content__list--item') return items

这里有一个大多数教程不会明说的问题:链家的页面结构会改版,.content__list--item这个选择器只在特定版本下有效。写脚本前先在浏览器按F12确认当前页面的列表项class名,直接把选择器写死到代码里,过一段时间页面改版脚本就废了。所以正确的做法是加个解析状态校验:如果解析出来的条数少于预期,立刻记录日志而不是静默跳过。

对于毕设项目,数据量控制在5000到10000条就足够。抓太多既耗费时间,也容易触发反爬。反爬的常见特征包括:请求频率过高被封IP、需要验证码、返回内容为空壳。所以抓取时要控制访问间隔:

import time import random for page in range(1, 30): items = fetch_page(page=page) if not items: print(f'第{page}页解析为空,可能被反爬,停止抓取') break process_items(items) time.sleep(random.uniform(2, 5))

每个页面间隔2到5秒,30个页面大概需要两分钟。这个随机延时对反爬有实际效果,固定间隔反而容易被识别。把延时参数和页数上限独立配置,后期调整时不用翻代码。

3.2 数据清洗与结构化:处理缺失值、价格异常和文本噪声

抓下来的是HTML结构,需要转换成结构化的字段:标题、区域、户型、面积、朝向、租金、发布时间、所在小区。这一步是数据分析质量的分水岭,清洗得干净,后面的图表才有说服力。先做一个数据提取函数:

def parse_item(item): data = {} title_el = item.select_one('.content__list--item-title') data['title'] = title_el.get_text(strip=True) if title_el else '' desc_el = item.select_one('.content__list--item-des') desc_parts = [p.get_text(strip=True) for p in desc_el.select('p')] if desc_el else [] # desc_parts格式典型为 ['3室1厅 主卧', '南 北', '25.6㎡', '整租', '5/6层'] if len(desc_parts) >= 3: data['house_type'] = desc_parts[0] # 户型 data['orientation'] = desc_parts[1] # 朝向 try: data['area'] = float(desc_parts[2].replace('㎡', '').strip()) except ValueError: data['area'] = None price_el = item.select_one('.content__list--item-price') if price_el: price_text = price_el.get_text(strip=True) data['price'] = extract_price(price_text) return data

这个函数里最关键的是面积字段的异常处理。租房广告里“25㎡”常被写成“25.6㎡”或“25平”,直接转float会抛异常。所以用try-except把不能解析的面积置为None,保证程序不中断。价格提取同理,字符串里可能带“元/月”前缀,要用正则把数字剥出来。

清洗完成后做一次有效性校验,重点检查三类异常:

def clean_data(df): df = df.drop_duplicates(subset=['title', 'price', 'area']) df = df[df['price'] > 0] df = df[df['area'] > 3] # 过小面积视为无效数据 df['price_per_sqm'] = df['price'] / df['area'] return df

drop_duplicates按标题、价格、面积三重条件去重,避免同一条房源在多个页面重复出现污染统计。price > 0和area > 3是物理约束,面积小于3平米的房子在正常租房市场几乎不存在,所以直接排除。最后生成price_per_sqm(每平米租金)字段,这是后续分析的一个重要指标,用来做区域租金对比时比总价更公平。

4. Flask后端与数据分析可视化:从数据库查询到图表展示的完整链路

4.1 用Flask-SQLAlchemy建模与设计API接口

数据清洗完成后,将结构化数据写入MySQL。先用Flask-SQLAlchemy定义房源表模型:

from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class House(db.Model): __tablename__ = 'house' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200)) district = db.Column(db.String(50)) # 区域,如朝阳、浦东 house_type = db.Column(db.String(50)) # 户型 orientation = db.Column(db.String(20)) # 朝向 area = db.Column(db.Float) price = db.Column(db.Integer) price_per_sqm = db.Column(db.Float) publish_date = db.Column(db.Date) source_url = db.Column(db.String(500)) created_at = db.Column(db.DateTime, default=datetime.now)

模型定义字段类型时,price用Integer就够了,租金不会出现小数点;area用Float是因为中介常发布带小数的面积。publish_date建议直接和抓取页面的发布时间字符串做解析,不要存成字符串,否则后面按时间趋势分析时日期解析会很痛苦。如果你只想快速跑通,可以从created_at处简化,直接不存发布时间字段,会让整个系统少一个重要分析维度。

然后写API,前后端分离的思路是后端只返回JSON数据,前端用Ajax请求拉取渲染。这个设计在答辩中很好讲,也方便后续切换前端框架。以一个区域租金统计接口为例:

from flask import Blueprint, jsonify, request from sqlalchemy import func api_bp = Blueprint('api', __name__, url_prefix='/api') @api_bp.route('/rent/region') def rent_region(): district = request.args.get('district') query = db.session.query( House.district, func.avg(House.price).label('avg_price'), func.avg(House.price_per_sqm).label('avg_price_per_sqm'), func.count(House.id).label('house_count') ) if district: query = query.filter(House.district == district) query = query.group_by(House.district).order_by(func.avg(House.price).desc()) rows = query.all() result = [ {'district': r.district, 'avg_price': round(r.avg_price, 2), 'avg_price_per_sqm': round(r.avg_price_per_sqm, 2), 'count': r.house_count} for r in rows ] return jsonify({'code': 0, 'data': result})

这里把聚合逻辑放进SQL而不是全查出来用Pandas算,原因是在上万条数据下SQL的GROUP BY比Python层循环高效一个量级。返回时用round处理浮点数精度,JSON序列化时不会出现一长串小数。接口里的district参数是可选的,加判断是为了支持首页加载全部区域、点击单个区域时加载该区域两种场景。

4.2 可视化方案选型:用ECharts还是Plotly给毕业设计加分

租房数据分析系统最终呈现的数据分析结果,主要是一些图表。常见的可视化方案有两种:ECharts纯前端渲染,后端只提供数据接口;或用Plotly在后端生成HTML片段直接嵌入模板。对Flask项目,我更推荐ECharts方案。原因是它可以通过CDN或者本地静态文件引入,不需要后端额外依赖,而且图表交互体验比静态图片好得多——鼠标悬浮能看到具体数值、点击柱状图能联动筛选。

先下载ECharts的min.js放入static/目录,然后在模板里写一个柱状图渲染区域租金:

// 基于准备好的dom,初始化echarts实例 const chartDom = document.getElementById('rentChart'); const myChart = echarts.init(chartDom); fetch('/api/rent/region') .then(resp => resp.json()) .then(json => { const data = json.data; myChart.setOption({ title: { text: '各区域平均租金对比' }, tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: data.map(d => d.district) }, yAxis: { type: 'value', name: '元/月' }, series: [ { name: '平均租金', type: 'bar', data: data.map(d => d.avg_price) }, { name: '每平方米租金', type: 'line', data: data.map(d => d.avg_price_per_sqm) } ] }); });

这个图展示了两个维度的租金:总租金和单位面积租金。你一眼能看出哪个区域总价高但每平米单价不高,这在分析中很有价值。比如总价最高的区域可能是大户型为主,而单位面积租金最贵的才是真正的核心地段。答辩时可以指着这两条数据线讲分析结论,比单纯说“朝阳区平均租金最高”要有深度。

4.3 数据洞察:围绕租房人群的分析维度设计

系统不只要把数据展示出来,还要回答一个问题——“这些图表对用户有什么实际决策意义”。所以分析维度的设计非常关键。我建议至少覆盖这三个核心场景:

第一个是区域租金排行榜,直接告诉用户哪个区租房成本最高,用柱状图加表格呈现。第二个是户型分布饼图,用房源数量的比例来展示市场供给结构——一居室、两居室、三居室各占多少,让用户了解要选择的户型是否充足。第三个是面积与租金的关系散点图,横轴是面积、纵轴是租金,用散点阵列揭示租金随面积增长的规律,同时也能识别出异常值(比如面积很小但租金很高的特殊情况)。

在实现面积与租金散点图接口时,要注意前端一次加载过多数据点会导致渲染卡顿。所以后端做适度采样或聚合:

@api_bp.route('/rent/scatter') def rent_scatter(): limit = request.args.get('limit', default=500, type=int) rows = db.session.query(House.area, House.price).filter( House.area.isnot(None), House.price.isnot(None) ).limit(limit).all() result = [{'area': r[0], 'price': r[1]} for r in rows] return jsonify({'code': 0, 'data': result})

limit默认只取500条,在渲染上万条租房数据时保证页面不卡。这类性能细节不在常规课程设计里出现,一旦讲了,答辩老师会认为你对真实场景有考虑。

5. 高频避坑指南:租房数据分析系统的五个疑难杂症

5.1 MySQL连接报错:Can't connect to MySQL server,服务没启动还是配置写错

现象:程序启动时抛错sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'localhost'")。

原因:90%的情况是MySQL服务根本没启动,Windows下安装MySQL后服务默认不是自动启动的,需要去“服务”里手动开启。剩下10%是连接字符串里的端口或主机写错。

解决:先确认服务状态,在命令行执行net start mysql或到服务管理器里启动MySQL服务。确认启动后仍然报错,检查config.py里的SQLALCHEMY_DATABASE_URI是否写成了IP地址如127.0.0.1。另外MySQL 8.0默认的认证插件是caching_sha2_password,PyMySQL最新版本已经支持,但如果装的PyMySQL版本较老,需要升级:

pip install --upgrade pymysql

5.2 爬虫抓到的页面是空壳:页面结构变了还是被反爬了

现象:运行爬虫脚本后返回的列表项数量为0,但浏览器打开同一个URL却正常显示房源。

原因:第一可能是页面结构改版,select_one('.content__list--item')选择器匹配不到内容;第二可能是服务器识别到异常请求头,返回了验证页或空白页。

解决:先打印返回的HTML内容长度,如果长度明显小于正常页面,大概率是反爬拦截。优先检查headers是否完整,尤其是User-Agent,不要用默认的python-requests。其次检查页面结构,用开发者工具确认真实的选择器名称。还有一个常见手段是给请求加Referer头,模拟从链家首页跳转过来的浏览器行为。

5.3 中文乱码:从数据库到页面全是问号或乱码

现象:MySQL里存进去的中文显示为???,网页渲染出来也是乱码。

原因:典型的三处遗漏叠加——建库时没指定utf8mb4、连接URL没带charset=utf8mb4、HTML页面没设置<meta charset="utf-8">。

解决:第一,在MySQL里执行ALTER DATABASE rent_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;对现有库做修改。第二,在SQLALCHEMY_DATABASE_URI末尾加上?charset=utf8mb4。第三,在Flask模板的<head>里加上字符集声明。这三项都做过,乱码问题基本消失。

5.4 Flask调试模式开着,改代码就自动重启,有时会报错

现象:开着app.run(debug=True)写代码,保存时服务自动重启,偶尔页面刷出来一个带红色报错的大堆栈。

原因:debug模式下Flask会监听文件变化,一旦Python文件有语法错误或暂时不完整,重启就会失败。这不是系统出bug,是开发中间态。

解决:平时用debug=True方便热加载,但当文件处于编辑中间态时看一眼终端错误信息等改完再保存就行。如果嫌重启频繁,可以直接改成debug=False,改完代码手动重启服务。

5.5 数据分析结果不符合常识:数据没清洗干净导致图形失真

现象:区域租金排行榜里出现一个均价远超正常水平的区域,比如某区域均价5万/月,点进去只有一条300平米的别墅房源。

原因:样本量太少的区域,均值会被极端值拉偏。这是数据分析中的经典问题——小样本+异常值=误导结论。

解决:聚合查询时加一个最低样本量约束,比如HAVING count(House.id) >= 5,只展示房源数量不少于5套的区域。如果还想去掉异常值,可以在清洗时按百分位数截断,比如只保留price在5%到95%区间的数据。这个处理逻辑讲出来,比“我爬了数据就直接展示”有技术含量得多。

6. 进阶玩法:给系统加上推荐筛选和本地部署验证

最后一个值得做的加分项,是在基础统计分析之上加一个“租房推荐筛选”功能。这个功能不复杂但很好用:让用户输入预算上限、想要区域、户型要求,系统从数据库里筛选符合条件的房源,并按每平米租金从低到高排序,输出Top列表。这其实是把价格敏感用户的真实决策过程程序化,毕设答辩中演示这个功能时,比单纯展示图表更直观。

实现时需要新增一个支持多条件组合查询的接口:

@api_bp.route('/house/filter') def house_filter(): max_price = request.args.get('max_price', type=int) district = request.args.get('district') house_type = request.args.get('house_type') query = House.query if max_price: query = query.filter(House.price <= max_price) if district: query = query.filter(House.district == district) if house_type: query = query.filter(House.house_type.like(f'%{house_type}%')) query = query.order_by(House.price_per_sqm.asc()) rows = query.limit(20).all() return jsonify({'code': 0, 'data': [r.to_dict() for r in rows]})

这里用house_type.like做模糊匹配很关键,因为同一户型在真实数据里可能是“2室1厅”“2室2厅”,只用完全匹配会漏掉很多房源。排序用price_per_sqm升序,目的是优先推荐单位面积性价比最高的房子。最后用limit(20)避免返回太多冗余数据。

一条经验是:组合筛选接口一定要给前端一个“清空条件”的按钮,否则用户试了几次条件后想回到默认列表,只能刷新页面。这种交互细节不一定在毕设要求里,但做了就显得产品思维完整。

项目在本地跑通后,验证工作不止是打开浏览器点几个页面。建议写一个简单的功能检查清单,按顺序过一遍:数据库连接能通、爬虫能抓到数据并入库、清洗后的数据量符合预期、首页图表能正常加载、筛选接口返回内容正确、项目能通过python app.py一次启动。检查时留意日志输出,Flask终端里如果出现异常堆栈就要顺手修掉,不要带着警告提交。

本人习惯是在项目根目录放一个requirements.txt,每次装依赖后用pip freeze > requirements.txt更新,这样无论换电脑还是答辩现场的演示环境,都能用pip install -r requirements.txt一键还原环境。这个习惯救过我不少次——答辩前换过一台电脑,靠这份文件五分钟恢复了全部依赖,没有因为环境问题耽误演示。希望这个细节帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询