Python与Hadoop实现租房数据分析系统:从爬虫到可视化全流程 简介这是一份面向计算机相关专业毕业生的租房数据分析系统毕业设计论文基于PythonHadoopFlaskVue技术栈完整呈现从需求分析、系统架构设计、功能模块开发到数据分析应用的毕业设计过程。压缩包内仅含1个docx格式文档大小6.34MB即论文正文内容包括中英文摘要、目录、绪论、关键技术介绍、系统设计、系统实现等完整章节并详细展示了管理员端与前台端各功能模块的设计思路。目前已有153人学习下载适合正在开展类似课题或需要参考完整论文结构的学生。论文重点阐述了Hadoop分布式平台下的租房数据存储与处理方案、Django后端逻辑实现、Vue前端交互设计以及MySQL数据库的应用同时梳理了系统首页、个人中心、用户管理、房屋信息管理、租房数据管理、房屋资讯等核心模块的划分与实现细节。读者可通过该论文快速理清毕业设计写作脉络获得技术选型与系统设计参考为论文撰写和毕业答辩提供实用支撑。1. 租房数据分析系统毕业设计技术栈的选型逻辑租房数据分析这类毕业设计难点通常不在算法而在“数据能否跑通全链路”。只靠 Python 做爬虫加 pandas 统计能在本地快速出结果但和“大数据”方向的课程要求贴合不够硬上 Hadoop 又容易让采集、展示变成摆设。常见做法是用 Python 解决数据获取与清洗把清洗结果落到 HDFS由 Hadoop 承担分布式存储和离线计算再用 Flask 把统计结果暴露成接口最后用 Vue 做可视化页面。这套技术栈里的每一环都有明确职责也正好对应论文里“数据层—计算层—服务层—展示层”的章节结构。下面按这条链路把可复现的做法拆开讲适合正在做同类系统、需要尽快拿出能演示能答辩的完整项目的读者。2. Python 爬虫与数据清洗租房数据进 HDFS 的最小链路2.1 用 requests 与 BeautifulSoup 抓取租房列表页租房数据来源不固定链家、贝壳、58 同城都有列表页和详情页反爬策略也各不相同。毕设场景下不需要追求高并发用 requests 加 BeautifulSoup 就能支撑几万条数据量级。下面是一个最小可跑的抓取示例目标是从列表页提取标题、租金、面积、户型、城区和朝向。import requests import time import json from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_page(city_code, page): # city_code 是城市在目标网站的拼音或数字编码page 从 1 开始 url fhttps://example-rental.com/{city_code}/rent/pg{page}/ resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding utf-8 return resp.text def parse_list(html): soup BeautifulSoup(html, html.parser) items [] for li in soup.select(div.rental-item): title li.select_one(a.title).text.strip() rent li.select_one(span.rent).text.strip() area li.select_one(span.area).text.strip() layout li.select_one(span.layout).text.strip() district li.select_one(span.district).text.strip() items.append({ title: title, rent: rent, area: area, layout: layout, district: district, source: example-rental }) return items if __name__ __main__: result [] for p in range(1, 6): html fetch_page(bj, p) result.extend(parse_list(html)) time.sleep(1) # 对目标站点做基础限速避免请求过密 with open(rent_raw.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(f共抓取 {len(result)} 条)这段代码里值得注意的参数是timeout10和time.sleep(1)。前者防止某个页面长时间挂起拖死整个爬虫进程后者是对目标站点的基本礼貌也能降低被识别为爬虫的概率。resp.encoding utf-8必须显式指定部分租房网站页面头里没有 charsetrequests 默认会用 ISO-8859-1 去猜导致中文乱码。选择器.rental-item这类 class 名称要按实际页面结构调整但“每条租房信息对应一个容器节点”的解析思路是通用的。2.2 清洗规则与字段设计租金和面积是后续统计的地基爬下来的字段往往是字符串直接进 Hadoop 再算会造成大量脏数据。我的习惯是在 Python 侧先把字段清洗成规整的 CSV再批量上传到 HDFS。清洗重点有三个租金是否包含“元/月”后缀面积是否混入“平米”字样朝向是否有缺失。用 pandas 处理这类半结构化文本最顺手import pandas as pd import re df pd.read_json(rent_raw.json, encodingutf-8) # 租金统一为整数月租去除元/月后转 int失败置为 -1 df[rent] df[rent].apply(lambda x: int(re.sub(r[^0-9], , str(x))) if re.search(r\d, str(x)) else -1) # 面积统一为浮点数去除平米等汉字空值填充 0.0 df[area] df[area].apply(lambda x: float(re.search(r\d(\.\d)?, str(x)).group()) if re.search(r\d(\.\d)?, str(x)) else 0.0) # 朝向缺失统一填未知方便后续分组统计 df[orientation] df[orientation].fillna(未知) # 过滤租金或面积明显异常的数据租金小于 100 或面积小于 5 基本是噪声 df df[(df[rent] 100) (df[area] 5)] # 生成清洗后 CSV采UTF-8编码保证 Hive/MapReduce 读取不易乱码 df.to_csv(rent_clean.csv, indexFalse, encodingutf-8)清洗字段的取舍会直接影响后面的统计分析口径。租金字段如果保留字符串Hadoop 的 MapReduce 里还得再转一次类型增加错误概率面积字段混入“整租”“合租”这类说明文字统计均值时会被过滤掉。所以我的原则是凡是参与统计的字段都在清洗阶段转成数值类型凡是只做展示的字段比如标题里的“南北通透”“近地铁”保留原文即可。2.3 把清洗结果上传 HDFS先建目录再 put清洗后的 rent_clean.csv 还在本地需要进入 Hadoop 的 HDFS 才能被后续离线分析读取。上传前先在 HDFS 上建立按日期分区的目录结构这样可以避免后续数据全堆在一个文件里也方便论文里写“数据按天增量更新”。hdfs dfs -mkdir -p /rental/data/2025/05/01 hdfs dfs -put rent_clean.csv /rental/data/2025/05/01/ hdfs dfs -ls /rental/data/2025/05/01/参数说明-mkdir -p表示多级目录一次创建如果目录已存在也不会报错-put的本地路径必须在当前用户有读权限的目录下HDFS 目标路径不能带file://前缀。上传完成后用-ls检查文件是否落位输出里能看到rent_clean.csv的副本数、块大小和文件大小。副本数默认是 3伪分布式环境下只有 1 个 DataNode副本数 3 也不会真正复制但不会影响读取。这里还有一个更工程化的做法用 Python 的hdfs库直接写 HDFS但毕设环境往往没有配 Kerberos用命令行 put 反而更直观也方便在论文的操作截图中展示。下表是两种方式的适用场景对比。方式依赖适合场景主要缺点hdfs dfs -putHadoop 客户端一次性批量上传演示直观无法在 Python 代码里动态控制hdfsPython 库pip install hdfs爬虫与上传一体化需要额外配置 HDFS HTTP 端口pyarrow.fspyarrow与后续 Parquet 分析打通版本敏感部署麻烦3. Hadoop 伪分布式搭建与离线分析Streaming 方式跑通租房统计3.1 伪分布式还是集群毕设场景的选型与取舍毕业设计环境里常见有伪分布式、Docker 单节点集群、多机集群三种选择。多机集群需要至少三台服务器或虚拟机适合网络工程类课题Docker 单节点能模拟多进程但内存占用大伪分布式是在一台机器上同时跑 NameNode、DataNode、ResourceManager、NodeManager 四个进程配置最少数据量在百万行以内完全够用。我的建议是直接用伪分布式理由是论文里只需要证明“系统具备 Hadoop 分布式计算能力”而不需要证明“系统能扛多少并发”。伪分布式能完成 HDFS 文件上传、MapReduce 任务提交、YARN 日志查看这条完整链路且排错范围小。唯一需要注意是伪分布式环境下 DataNode 和 NameNode 的端口不能冲突默认8020和9000都有可能被占用建议统一改到9820。3.2 三份核心配置文件与启动顺序Hadoop 的配置集中在etc/hadoop/目录下伪分布式不必动workers或slaves文件重点改三份。core-site.xml指明 NameNode 地址hdfs-site.xml设置副本数和 NameNode 目录yarn-site.xml配置资源调度器。!-- core-site.xml -- configuration property namefs.defaultFS/name valuehdfs://localhost:9820/value /property /configuration!-- hdfs-site.xml -- configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/home/hadoop/data/name/value /property property namedfs.datanode.data.dir/name value/home/hadoop/data/data/value /property /configuration!-- yarn-site.xml -- configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property /configuration配置参数里最容易被忽略的是dfs.replication。如果虚拟机只有单节点默认副本数 3 会让 DataNode 报 “There are X datanode(s) running and X node(s) are excluded” 之类的警告虽然不阻塞任务但在答辩现场会显得不专业。yarn.nodemanager.resource.memory-mb要根据虚拟机内存调整机器只有 8G 内存时设 4096 比较稳妥设太大会把系统内存吃光。配置完成后按顺序格式化并启动进程hdfs namenode -format start-dfs.sh start-yarn.sh jpsjps输出里出现NameNode、DataNode、ResourceManager、NodeManager四个进程才算启动成功。如果缺少 DataNode常见原因是多次执行了namenode -format导致 NameNode 的 clusterId 与 DataNode 不一致解决办法是删除dfs.namenode.name.dir和dfs.datanode.data.dir下的数据后重新格式化和启动。3.3 用 Hadoop Streaming 跑 Python MapReduce统计各城区平均租金很多人以为用 Hadoop 就必须写 Java对 Python 栈不友好。实际上 Hadoop 自带 Streaming 工具允许把任意可执行文件作为 Mapper 和 ReducerPython 脚本也能直接跑在 YARN 上。统计“各城区平均租金”只需要一个 Mapper 输出城区和租金Reducer 做累加和计数。# mapper.py import sys for line in sys.stdin: line line.strip() if not line or line.startswith(district): continue fields line.split(,) if len(fields) 6: continue district fields[3].strip() rent fields[4].strip() try: rent_val int(float(rent)) except ValueError: continue # 输出 key\tvalueHadoop Streaming 默认按 tab 分隔 print(f{district}\t{rent_val}\t1)# reducer.py import sys current_district None total_rent 0 total_count 0 for line in sys.stdin: line line.strip() district, rent, count line.split(\t) if district ! current_district: if current_district is not None: avg total_rent / total_count if total_count else 0 print(f{current_district}\t{avg:.2f}) current_district district total_rent int(rent) total_count int(count) else: total_rent int(rent) total_count int(count) if current_district is not None: avg total_rent / total_count if total_count else 0 print(f{current_district}\t{avg:.2f})提交命令hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \ -input /rental/data/2025/05/01/rent_clean.csv \ -output /rental/output/district_avg_rent \ -mapper python3 mapper.py \ -reducer python3 reducer.py \ -file mapper.py \ -file reducer.py参数说明-input和-output都是 HDFS 上的路径output 目录必须不存在否则任务会直接报错-mapper和-reducer后面跟的是实际执行命令伪分布式环境建议显式写python3避免在某些节点上默认 python 指向 Python 2。-file会把本地脚本分发到所有容器这一步最容易漏漏了会报 “File does not exist: mapper.py” 或者 “Permission denied”。任务跑完后用hdfs dfs -cat /rental/output/district_avg_rent/part-00000查看结果。MapReduce 默认输出很多个 part 文件数据量小时只有一个论文截图里展示这个文件的内容正好能说明“计算完成且结果落盘”。3.4 失败排查与参数记忆点Hadoop Streaming 任务失败时第一件事是看 YARN 日志不要盲目改代码。执行yarn logs -applicationId appId能拿到 stdout 和 stderr。常见的exit code 1原因是 Mapper 里某个字段不是预期的类型导致抛出未捕获异常。我的习惯是在每个可能出错的字段取用处都加try/except或长度校验宁可跳过异常行也不让整个 task 失败。还有一个容易被忽略的坑是 CSV 里的逗号和空格。如果抓取数据里小区名包含逗号或英文引号字段切分就会错位。对这类数据要么预处理时把字段里的逗号替换成中文逗号要么在 Mapper 里写一个简单 CSV 解析函数。使用正则“整体上与字段边界不一致”导致 Mapper 无法反而丢弃数据是常见情况。数据量小时让 Mapper 输出带 index 的调试信息到 stderr辅助定位更快。4. Flask 数据接口与 Vue 可视化把 HDFS 分析结果送到可演示页面4.1 离线分析结果先落成 JSON 中间表MapReduce 输出的 part 文件虽然能直接 cat但前端渲染不能依赖 HDFS 文件读取。常见做法是写一个 Python 脚本把 part 文件拉回本地解析成 JSON 再交给 Flask。这里有一个细节不要每次前端请求都去 HDFS 读文件应该在 Flask 启动时或定时任务中把结果加载到内存或 SQLite接口层面只做查询。# load_hdfs_result.py import json import subprocess def fetch_result(hdfs_path, local_path): # 拉取 HDFS 输出到本地临时文件 subprocess.run([hdfs, dfs, -getmerge, hdfs_path, local_path], checkTrue) data {} with open(local_path, r, encodingutf-8) as f: for line in f: district, avg_rent line.strip().split(\t) data[district] float(avg_rent) return {district_avg_rent: data} if __name__ __main__: result fetch_result( hdfs_path/rental/output/district_avg_rent, local_path/tmp/district_avg_rent.txt ) with open(static_result.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2)hdfs dfs -getmerge的作用是把 HDFS 目录下所有 part 文件合并成一个本地文件比-get更适合 MapReduce 输出。checkTrue表示子进程失败时直接抛异常避免生成半截 JSON。4.2 Flask 接口设计与参数定义Flask 在这里只承担轻量数据服务不需要蓝图、不需要 SQLAlchemy最多加一个 Flask-CORS 解决跨域。接口路径按业务语义命名参数通过 query string 传入。from flask import Flask, jsonify, request import json app Flask(__name__) CORS(app) # 允许 Vue 开发服务器跨域访问 with open(static_result.json, r, encodingutf-8) as f: result json.load(f) app.route(/api/rent/district, methods[GET]) def district_avg_rent(): # 支持可选参数 min_count过滤样本量过少的区域 min_count request.args.get(min_count, default0, typeint) data result.get(district_avg_rent, {}) if min_count 0: data {k: v for k, v in data.items() if counts.get(k, 0) min_count} return jsonify({code: 0, data: data}) app.route(/api/health, methods[GET]) def health(): return jsonify({status: ok}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)jsonify会自动设置 Content-Type 为 application/json这是前端 axios 能不能直接拿res.data的关键。host0.0.0.0表示监听所有网卡如果你用虚拟机跑 Flask、宿主机浏览器访问 Vue 页面就必须这么写只写 127.0.0.1 会导致外部访问失败。接口路径加入/api前缀是为了后续如果换成 Spring Boot 或 Node.js前端代码不用改业务逻辑。4.3 Vue 组件与 ECharts 的联动Vue 侧的核心是把接口数据映射成图表配置。下面是一个常用的RentBar.vue组件用axios请求 Flask 接口拿到数据后交给 ECharts 渲染。template div refchartRef stylewidth: 100%; height: 400px/div /template script setup import { ref, onMounted, watch } from vue import axios from axios import * as echarts from echarts const props defineProps({ minCount: { type: Number, default: 0 } }) const chartRef ref(null) let chartInstance null function renderChart(data) { if (!chartInstance) { chartInstance echarts.init(chartRef.value) } const districts Object.keys(data) const rents Object.values(data) chartInstance.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: districts }, yAxis: { type: value, name: 元/月 }, series: [{ type: bar, data: rents }] }) } onMounted(async () { const res await axios.get(http://localhost:5000/api/rent/district, { params: { min_count: props.minCount } }) renderChart(res.data.data) }) watch(() props.minCount, async (val) { const res await axios.get(http://localhost:5000/api/rent/district, { params: { min_count: val } }) renderChart(res.data.data) }) /scriptref和watch是 Vue 3 组合式 API 的核心chartRef绑定到 DOM 节点ECharts 初始化时需要真实 DOM所以必须在onMounted里执行。二次渲染时不要重复echarts.init复用chartInstance并调用setOption即可否则页面会出现内存泄漏的警告。如果请求频率较高建议在setOption前调用chartInstance.clear()清除旧配置。4.4 联调阶段的两个常见卡点前后端联调时第一个卡点是跨域。Vue 开发服务器默认跑在 5173Flask 跑在 5000浏览器会拦截跨域请求。解决方案有两种后端加 Flask-CORS或前端 vite.config.js 里配置代理。我的建议是开发期用 Flask-CORS最简单部署时再改成 Nginx 同源反向代理避免把服务端口直接暴露。第二个卡点是 ECharts 图表在 Tab 切换或路由跳转后宽高变成 0。原因是图表容器在隐藏状态下初始化时拿不到真实尺寸。解决方案是在组件激活时调用chartInstance.resize()或者给容器一个固定的最小高度比如上面的height: 400px样式。表格 4.2 的接口参数在演示时也方便用来动态展示“样本量过滤”的效果可玩性比静态图高。接口参数返回值演示用途/api/rent/districtmin_count各城区平均租金过滤小样本后趋势更平滑/api/rent/area_trendarea_bin面积区间与租金中位数展示面积与租金的关系/api/health无服务状态答辩开场快速确认服务存活5. 答辩前最值得做的 3 件事数据对账、接口压测与一键复现5.1 数据对账HDFS 行数与清洗后 CSV 的一致性校验答辩被问到最多的问题就是“你保证数据没丢吗”。这是允许验证的。想回答清楚只需两组数字清洗前多少条清洗后多少条。用以下脚本对账# 统计 HDFS 上的 CSV 行数减去表头 hdfs dfs -cat /rental/data/2025/05/01/rent_clean.csv | wc -l # 统计本地清洗文件行数 wc -l rent_clean.csv# reconcile.py import pandas as pd hdfs_count 13428 # 替换为上述命令的输出 df pd.read_csv(rent_clean.csv) clean_count df.shape[0] assert clean_count hdfs_count - 1, f数量不一致: HDFS{hdfs_count}, 本地{clean_count} print(f对账通过: {clean_count} 条)这个脚本建议放在论文“系统测试”章节的截图里配合清洗前后的数据量对比表能直观说明爬虫、清洗、上传三个环节都没有丢数据。5.2 接口压测用 ab 验证 Flask 接口能扛住演示现场演示时可能同时有几十个页面在刷接口如果卡顿会非常尴尬。用 Apache ab 做一次简单压测能提前发现接口瓶颈ab -n 200 -c 20 http://localhost:5000/api/rent/district如果 Requests per second 低于 50优先检查 Flask 是否开启了 debug 模式debugFalse是必须的其次看接口里是否每次都重新读 JSON 文件正确做法是在模块加载时读取一次并缓存。压测结果写入论文“性能测试”一节只需要放吞吐率和平均响应时间两个指标即可。5.3 一键复现用编排脚本把启动顺序写清楚答辩演示最忌讳手动敲一串命令对方一紧张顺序就错。把一个run_demo.sh脚本放进项目根目录按顺序执行 Hadoop 启动、结果拉取、Flask 启动、前端构建能大幅提高演示流畅度。#!/bin/bash start-dfs.sh start-yarn.sh hdfs dfs -test -d /rental/data/2025/05/01 || echo 数据目录不存在先执行上传脚本 python3 load_hdfs_result.py flask --app app run --host0.0.0.0 --port5000 # 后台启动 cd frontend npm run dev脚本里的hdfs dfs -test -d参数值得解释一下如果目录不存在执行后面的||分支用echo给出明确提示避免上游数据缺失时后面所有步骤都失败却看不到原因。把这段脚本放到论文附录能说明系统具备“一键复现”能力这也是评审老师比较看重的工程素养。本文还有配套的精品资源点击获取