尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Hadoop+Python+Vue:房价数据分析系统毕业设计全流程详解

Hadoop+Python+Vue:房价数据分析系统毕业设计全流程详解 每年找毕业设计题目的同学里做“数据采集 大数据分析 可视化展示”的比例都不低。这类题目听起来完整但真正动手时会发现难点不在某一个技术点上而在把 Python 爬虫、Hadoop 存储计算、后端接口、Vue 页面这一整条链路串起来。本篇就拿“基于 Hadoop 的房价数据分析系统”作为完整案例围绕爬虫采集、HDFS 存储、MapReduce/Hive 分析、Flask 接口、Vue ECharts 可视化这条主线拆解一套可落地、可讲解、可答辩的毕业设计项目方案。如果你正准备做类似的毕设或者想了解一个“大数据 前端”项目到底怎么组织这篇文章可以直接作为技术参考。1. 项目背景与技术选型1.1 为什么选择房价数据作为毕设切入点房价数据是一个非常典型的数据分析素材。它具备几个明显特点数据量足够大各大房产平台每天都有大量更新字段结构清晰区域、户型、面积、朝向、总价、单价这些信息都能规整成结构化数据分析维度丰富既可以做区域均价对比也可以做户型占比、价格分布、涨跌趋势等统计可视化效果好柱状图、饼图、地图都能很好地展示结果。从毕业设计的评审角度看一个好的选题需要同时体现工程能力、数据分析思路和业务表达。房价数据分析系统恰好能覆盖这三方面工程能力体现在爬虫、Hadoop 集群搭建、前后端开发数据分析思路体现在如何清洗数据、如何设计统计指标、如何从数据中得出结论业务表达体现在前端可视化页面能否直观说明问题。因此“Python 爬虫 Hadoop 存储计算 Vue 可视化分析”这个组合非常适合作为计算机相关专业的毕业设计项目。1.2 系统技术栈与分工本项目的技术栈不是单一语言而是围绕数据全生命周期来组织的。每项技术负责一个环节职责边界需要清晰。先整体看一遍Python负责数据采集和数据清洗。爬虫模块使用 requests 请求网页BeautifulSoup 解析 HTMLpandas 做字段清洗和数据类型转换。Hadoop HDFS负责海量数据文件的分布式存储。爬虫清洗后得到的数据文件上传到 HDFS后续分析任务从 HDFS 读取数据。MapReduce / Hive负责离线统计分析。MapReduce 适合写自定义统计逻辑Hive 适合用 SQL 快速做聚合查询。Flask负责将分析结果封装成接口供前端调用。Flask 轻量灵活适合这种以数据展示为核心的毕设项目。Vue ECharts负责前端可视化页面展示。Vue 管理页面结构和交互ECharts 渲染统计图表。MySQL可选如果希望系统支持用户管理、历史记录等功能可以用 MySQL 存储业务数据。纯粹做展示也可以不引入 MySQL避免加重部署成本。从数据流向来看整个系统的核心链路是Python 爬虫获取房价数据 → 数据清洗后生成 CSV 文件 → 文件上传到 HDFS → MapReduce/Hive 完成区域均价、价格分布等统计 → 统计结果导出为结果文件 → Flask 读取结果文件并提供 JSON 接口 → Vue 页面请求接口并用 ECharts 渲染图表。Python爬虫采集 ↓ 数据清洗/结构化 ↓ 上传 HDFS ↓ MapReduce / Hive 统计分析 ↓ 导出结果文件 ↓ Flask 后端接口 ↓ Vue ECharts 可视化展示1.3 系统模块划分根据毕业设计文档的常见要求系统通常可以拆成五个核心模块数据采集模块负责定时或手动抓取目标房产网站的列表数据和详情数据将原始 HTML 转换为结构化记录。数据清洗与存储模块处理缺失字段、去除重复数据、统一价格单位并把最终数据保存为 CSV 文件或写入数据库。大数据分析模块在 Hadoop 平台上完成数据上传、MapReduce 统计、Hive 查询输出各维度分析结果。后端接口模块提供统一的数据接口隐藏 Hadoop 底层细节让前端开发者不需要关心数据是从哪里来的。前端可视化模块展示城市均价、区域对比、户型分布、价格区间占比等图表支持筛选和联动。这种模块划分方式能直接对应到毕业论文的章节结构后续写文档时每一章都有具体代码和功能可以支撑不会出现“设计写了但没实现”的问题。2. 环境准备与版本说明2.1 开发环境规划这一类的毕设项目不需要过于纠结环境版本核心是能完整跑通数据链路。下面是一个比较常用的环境组合你可以根据自己的电脑系统调整操作系统Windows 10/11、macOS、Ubuntu 都可以。Windows 用户推荐开启 WSL2 来装 Hadoop也可以直接用虚拟机如果不方便单机伪分布式在 Windows 下也能跑。JDKHadoop 3.x 依赖 JDK 8 或 JDK 11具体以你安装的 Hadoop 版本要求为准。Python3.8 以上即可用于爬虫和 Flask 后端。Hadoop建议使用 Hadoop 3.x 稳定版部署为伪分布式模式。Node.js14 以上Vue CLI 或 Vite 构建前端项目需要用到。数据库MySQL 5.7 或 8.x作为可选组件。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。不建议一上来就装最新版因为大数据生态组件之间的兼容关系比较复杂稳定版本反而更容易排查问题。2.2 Python 环境与依赖安装Python 环境建议使用虚拟环境避免多个项目互相污染依赖。先创建一个项目目录然后初始化虚拟环境并安装依赖mkdir housing-analysis cd housing-analysis python -m venv venv # Windows激活 venv\Scripts\activate # macOS/Linux激活 source venv/bin/activate在项目根目录创建requirements.txtrequests beautifulsoup4 lxml pandas flask flask-cors执行安装命令pip install -r requirements.txt这里有一个小建议爬虫解析库可以优先选择 lxml它的解析速度比纯 Python 实现快很多尤其当列表页存在大量重复结构时lxml 能明显提升采集效率。flask-cors用来解决前端开发服务器的跨域问题前后端联调时非常有必要。2.3 Hadoop 伪分布式环境搭建Hadoop 的安装过程不建议在没有任何准备的情况下直接走“下一步式”教程。你要先理解三个核心组件HDFS 负责存储YARN 负责资源调度MapReduce 负责计算。毕业设计中HDFS 是必备的MapReduce 至少要能运行一个统计任务。下载 Hadoop 并解压后需要配置环境变量。在hadoop-env.sh中确认 JAVA_HOME 指向你本机 JDK 安装路径。然后修改两个核心配置文件。etc/hadoop/core-site.xmlconfiguration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configurationetc/hadoop/hdfs-site.xmlconfiguration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/opt/hadoop/namenode/value /property property namedfs.datanode.data.dir/name value/opt/hadoop/datanode/value /property /configuration这里解释几个关键配置项。fs.defaultFS是 NameNode 的地址9000 端口是社区教程里很常见的写法如果你用的是某些发行版或 Docker 镜像端口可能不同需要以实际环境为准。dfs.replication表示副本数伪分布式环境下只有一台机器副本数必须设置为 1否则 DataNode 会一直报副本不足的警告。NameNode 和 DataNode 的目录建议单独指定不要用默认的临时目录否则系统重启可能导致元数据丢失。首次使用还需要格式化 NameNodehdfs namenode -format格式化完成后启动 HDFSsbin/start-dfs.sh启动后浏览器访问http://localhost:9870能看到 HDFS 的 Web 管理界面说明 HDFS 启动成功。2.4 Vue 前端环境准备前端项目可以通过 Vue CLI 创建。如果没有安装先执行npm install -g vue/cli创建项目并安装基础依赖vue create housing-web cd housing-web npm install echarts axios创建好的前端项目结构里src/views放页面组件src/components放复用组件src/api放接口请求封装。后面实现可视化页面时我们会按照这个结构组织代码。2.5 项目整体目录结构完整的项目目录建议按下述方式组织这样后续写论文和代码讲解时都能清晰对应housing-analysis/ ├── datasource/ # 数据采集与清洗 │ ├── spider.py # 爬虫主程序 │ ├── clean.py # 数据清洗脚本 │ └── output/ │ └── house_price.csv # 清洗后的结构化数据 ├── hadoop/ │ ├── mapreduce/ │ │ ├── AvgPriceMapper.java │ │ ├── AvgPriceReducer.java │ │ └── AvgPriceDriver.java │ └── hive/ │ └── analysis.sql # Hive 分析 SQL ├── server/ │ └── app.py # Flask 后端接口 └── web/ # Vue 前端项目 └── src/ ├── views/ │ └── Dashboard.vue └── api/ └── index.js这种目录划分既符合数据工程的模块化思想也方便在答辩时解释每个文件的职责。3. 数据采集Python 爬虫模块设计3.1 爬虫目标与字段设计在做爬虫之前先想清楚要抓哪些字段这决定了后面的数据清洗、Hadoop 存储和分析指标。房价数据一般包含以下核心字段城市所属城市名称例如“北京”“上海”“广州”。区域行政区或商圈例如“朝阳区”“浦东新区”“天河区”。小区名称房源所在小区。户型例如“3室2厅”用于户型分布分析。面积房屋建筑面积单位平方米。朝向例如“南北”“南”“东”。装修例如“精装”“简装”。楼层中楼层、高楼层、低楼层等。总价房屋总价单位万元。单价每平方米价格单位元/平方米。字段设计得越规范后续分析就能做得越丰富。需要注意的是不同网站的字段名称可能不同在爬虫阶段最好统一映射成上述字段名这样数据清洗就不用反复做字段重命名。3.2 requests BeautifulSoup 基础爬取示例爬虫的基本流程是构造请求 → 发送请求 → 解析 HTML → 提取字段 → 生成结构化数据。下面以解析一个包含房源列表的页面为例演示核心代码逻辑。# 文件路径datasource/spider.py import time import requests 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.0.0 Safari/537.36 } def fetch_page(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding return resp.text def parse_list_page(html): soup BeautifulSoup(html, lxml) items soup.select(.house-item) result [] for item in items: title_node item.select_one(.title) price_node item.select_one(.price) area_node item.select_one(.area) if title_node is None: continue result.append({ title: title_node.text.strip(), price: price_node.text.strip() if price_node else , area: area_node.text.strip() if area_node else }) return result def crawl(start_url, pages5): all_data [] for page in range(1, pages 1): url f{start_url}?page{page} html fetch_page(url) data parse_list_page(html) all_data.extend(data) print(f第 {page} 页抓取完成共 {len(data)} 条记录) time.sleep(2) # 控制访问频率 return all_data if __name__ __main__: seed_url https://example.com/house # 替换为实际数据源 result crawl(seed_url, pages3) print(f总计抓取 {len(result)} 条数据)这段代码里有几个细节需要重点理解。第一resp.encoding resp.apparent_encoding这一步很关键。很多网站的页面编码是 GBK 或 GB2312如果不手动设置编码requests 会根据响应头猜测容易导致中文乱码。apparent_encoding是从页面内容中检测出的编码准确性更高。第二item.select_one(.title)使用 CSS 选择器定位节点如果节点不存在会返回 None所以要加判空处理。实际开发中每个网站的结构不同这一步需要根据目标页面调整选择器。建议先用浏览器开发者工具在页面中找到房源的列表项容器再写对应的 select 表达式。第三控制抓取频率非常重要。time.sleep(2)表示每抓取一页暂停 2 秒这是对目标网站的起码尊重。如果目标站点有反爬机制频繁请求可能导致 IP 被封。3.3 反爬应对思路与合规提醒在毕业设计场景下我建议不要花太多精力去挑战高难度反爬更不要绕过登录态、验证码等安全机制。合理的做法是为请求配置完整的 User-Agent模拟浏览器访问。设置请求超时时间并捕获 requests 异常。减少并发使用串行或低并发采集。对抓取结果做本地缓存避免重复请求。如果目标网站要求登录才能查看完整信息优先更换数据源而不是尝试破解。合规方面需要特别注意在正式开发前查看目标网站的 robots.txt 文件和用户协议确认是否允许自动采集采集频率要保持在合理范围采集到的数据仅用于课程学习和毕业设计演示不要用于商业用途不要采集个人敏感信息。这些内容在毕业论文的“系统伦理与合规性”章节中也可以作为亮点写进去。3.4 数据清洗与结构化存储爬虫拿到的原始数据通常有各种问题比如价格字段带有“万”“元/平”等文字面积字段混入“平米”单位有些记录缺字段。数据清洗就是把这些脏数据整理成规范格式。# 文件路径datasource/clean.py import pandas as pd def clean_data(df): # 去掉完全重复的记录 df df.drop_duplicates(subset[title, area]) # 提取数字处理单位 df[total_price] df[total_price].str.replace(万, ).str.strip() df[total_price] pd.to_numeric(df[total_price], errorscoerce) df[unit_price] ( df[unit_price] .str.replace(元/平, ) .str.replace(,, ) .str.strip() ) df[unit_price] pd.to_numeric(df[unit_price], errorscoerce) # 面积转数字 df[area] df[area].str.replace(平米, ).str.strip() df[area] pd.to_numeric(df[area], errorscoerce) # 删除关键字段为空的行 df df.dropna(subset[region, unit_price, area]) # 过滤异常数据 df df[(df[unit_price] 1000) (df[unit_price] 200000)] return df if __name__ __main__: raw_df pd.read_csv(output/house_price_raw.csv) clean_df clean_data(raw_df) clean_df.to_csv(output/house_price.csv, indexFalse, encodingutf-8-sig) print(f清洗完成剩余 {len(clean_df)} 条有效数据)清洗逻辑当中pd.to_numeric(..., errorscoerce)会把无法转换为数字的值变成 NaN这样后续可以用dropna统一删除。单价过滤条件要根据你抓取的城市实际情况调整如果目标城市均价较低可以适当放宽下限。最终保存时使用utf-8-sig编码是为了让 Excel 打开 CSV 时不会出现中文乱码。4. Hadoop 数据存储与离线分析4.1 数据上传 HDFS清洗后的house_price.csv需要上传到 HDFS才能被 Hadoop 生态中的分析组件读取。先创建目录再执行上传。hdfs dfs -mkdir -p /housing/input hdfs dfs -put output/house_price.csv /housing/input/ hdfs dfs -ls /housing/input/上传成功后可以通过 HDFS Web 界面确认文件状态。需要注意上传过程中如果文件路径写错可能不小心把文件覆盖到错误目录所以建议固定一个项目专用目录比如/housing/input。4.2 基于 MapReduce 的区域均价统计MapReduce 是 Hadoop 的核心计算模型。虽然现在很多生产环境使用 Spark、Flink 等更高效的计算框架但毕业设计使用 MapReduce 能更好地体现你对 Hadoop 基础原理的理解。下面实现一个统计“各区域平均单价”的任务。MapReduce 的思路是Mapper 阶段从 CSV 的每一行中解析出区域和单价Reducer 阶段对同一区域的所有单价求和并计算平均值。// 文件路径hadoop/mapreduce/AvgPriceMapper.java import java.io.IOException; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class AvgPriceMapper extends MapperLongWritable, Text, Text, DoubleWritable { Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line value.toString(); // 如果 CSV 有表头可以判断第一行并跳过 if (line.contains(region) line.contains(unit_price)) { return; } String[] fields line.split(,); // 字段顺序city, region, community, layout, area, total_price, unit_price if (fields.length 7) { return; } String region fields[1].trim(); try { double unitPrice Double.parseDouble(fields[6].trim()); context.write(new Text(region), new DoubleWritable(unitPrice)); } catch (NumberFormatException e) { // 价格字段异常时跳过该行 } } }// 文件路径hadoop/mapreduce/AvgPriceReducer.java import java.io.IOException; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class AvgPriceReducer extends ReducerText, DoubleWritable, Text, DoubleWritable { Override protected void reduce(Text key, IterableDoubleWritable values, Context context) throws IOException, InterruptedException { double sum 0.0; int count 0; for (DoubleWritable value : values) { sum value.get(); count; } double avg sum / count; context.write(key, new DoubleWritable(Math.round(avg * 100) / 100.0)); } }// 文件路径hadoop/mapreduce/AvgPriceDriver.java import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class AvgPriceDriver { public static void main(String[] args) throws Exception { if (args.length ! 2) { System.err.println(Usage: AvgPriceDriver input output); System.exit(-1); } Configuration conf new Configuration(); Job job Job.getInstance(conf, avg price by region); job.setJarByClass(AvgPriceDriver.class); job.setMapperClass(AvgPriceMapper.class); job.setReducerClass(AvgPriceReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(DoubleWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } }打包运行前需要确认 Hadoop 的 classpath 能被正确识别。通常使用hadoop classpath命令获取依赖路径。将三个 Java 文件编译并打包为 jar然后执行hadoop jar avg-price.jar AvgPriceDriver /housing/input /housing/output/avg_price任务执行完成后查看结果hdfs dfs -cat /housing/output/avg_price/part-r-00000输出结果大致如下朝阳区 78256.0 天河区 65423.0 浦东新区 72110.0需要特别说明两点。一是 Mapper 中的fields[6]对应单价字段这完全取决于你的 CSV 字段顺序如果清洗后字段顺序不同这里必须同步修改。二是 MapReduce 的 Java 版本、Hadoop 依赖 jar 路径需要与本地环境保持一致最稳妥的方式是在项目里使用 Maven 管理依赖并把 Hadoop 相关依赖的 scope 设置为provided实际运行时使用集群自带 jar。如果你觉得写 Java 代码比较麻烦也可以用 Hadoop Streaming 方式用 Python 写 Mapper 和 Reducer。这样代码量更小对 Python 技术栈的同学更友好。Hadoop Streaming 通过标准输入输出传递数据下面是 Mapper 的 Python 写法python3 bin/hadoop_streaming.py \ -input /housing/input \ -output /housing/output/avg_price_py \ -mapper python3 map.py \ -reducer python3 reduce.py# map.py import sys for line in sys.stdin: fields line.strip().split(,) if len(fields) 7: continue print(f{fields[1].strip()}\t{fields[6].strip()})# reduce.py import sys current_region None current_sum 0 current_count 0 for line in sys.stdin: parts line.strip().split(\t) if len(parts) ! 2: continue region, price parts try: price float(price) except ValueError: continue if current_region and region ! current_region: avg current_sum / current_count print(f{current_region}\t{avg:.2f}) current_sum 0 current_count 0 current_region region current_sum price current_count 1 if current_region: avg current_sum / current_count print(f{current_region}\t{avg:.2f})Hadoop Streaming 的优点是任务逻辑直观答辩时容易被评委理解缺点是需要额外确认 Python 脚本的可执行权限和 Hadoop 集群中的 Python 环境。没有把握时优先选择 Java 版 MapReduce 更稳妥。4.3 使用 Hive 进行 SQL 分析如果不想为每个统计指标都写一遍 MapReduce可以引入 Hive。Hive 会把 SQL 翻译成 MapReduce 任务对开发者更友好。先创建外部表-- 文件路径hadoop/hive/analysis.sql CREATE EXTERNAL TABLE IF NOT EXISTS house_price ( city STRING, region STRING, community STRING, layout STRING, area DOUBLE, total_price DOUBLE, unit_price DOUBLE ) ROW FORMAT DELIMITED FIELDS TERMINATED BY , STORED AS TEXTFILE LOCATION /housing/input;这里有一个前提数据文件不能包含表头。如果 CSV 文件第一行是字段名建表后查询时会把这行当成正常数据处理。建议在清洗阶段直接去掉表头或者用sed -i 1d house_price.csv删除首行后再上传 HDFS。建表完成后可以直接用 SQL 做多维分析-- 各区域平均单价 SELECT region, AVG(unit_price) AS avg_price FROM house_price GROUP BY region ORDER BY avg_price DESC; -- 户型占比 SELECT layout, COUNT(*) AS cnt FROM house_price GROUP BY layout ORDER BY cnt DESC; -- 各区域房源数量 SELECT region, COUNT(*) AS cnt FROM house_price GROUP BY region;这些分析结果可以直接导出到本地文件INSERT OVERWRITE LOCAL DIRECTORY /tmp/hive_result ROW FORMAT DELIMITED FIELDS TERMINATED BY , SELECT region, AVG(unit_price) FROM house_price GROUP BY region;导出后的结果文件会存放在/tmp/hive_result下文件名类似000000_0。文件名不固定在脚本中处理时需要通配匹配。Hive 和 MapReduce 的选择建议是如果选题是“基于 Hadoop 的数据分析系统”建议两种技术都展示。MapReduce 体现底层原理掌握Hive 体现工程效率。答辩时既能讲清楚 MapReduce 的 shuffle 过程又能展示 SQL 分析效率技术深度会更完整。4.4 分析结果存储与导出分析完成后的结果文件一般在 HDFS 中前端接口并不能直接读取 HDFS。常见的做法是使用hdfs dfs -get将结果下载到后端服务器本地目录。或者将结果插入 MySQL后端接口查询数据库。或者直接用 Hive 将分析结果写入 MySQL 表通过 Sqoop 或手动导出。最简做法是第一种。对于毕设级别的数据量结果文件通常只有几百 KB下载到本地完全没有性能问题。后端 Flask 启动时读取这些结果文件按接口分类返回即可。hdfs dfs -get /housing/output/avg_price/part-r-00000 server/data/avg_price.csv5. 后端接口服务设计5.1 接口模块的作用很多同学会问既然前端可以访问 HDFS为什么不直接让 Vue 去读 HDFS 文件答案很简单HDFS 提供的是 Hadoop RPC 和 WebHDFS 接口不是为浏览器前端设计的。直接暴露 HDFS 不仅不安全还会让前端代码严重耦合大数据组件。比较好的做法是增加一个轻量级后端服务把分析结果封装成前端友好的 JSON 接口。5.2 Flask 接口实现后端使用 Flask因为它轻量、学习成本低适合作为毕设项目的接口层。核心代码如下# 文件路径server/app.py import pandas as pd from flask import Flask, jsonify from flask_cors import CORS app Flask(__name__) CORS(app) DATA_DIR data def load_result(filename): df pd.read_csv(f{DATA_DIR}/{filename}) return df.to_dict(orientrecords) app.route(/api/overview, methods[GET]) def overview(): 返回系统概览数据 总记录数、城市列表、最高单价、最低单价等 df pd.read_csv(f{DATA_DIR}/house_price.csv) total len(df) max_price df[unit_price].max() min_price df[unit_price].min() cities df[city].unique().tolist() data { total: total, max_price: max_price, min_price: min_price, cities: cities } return jsonify({code: 0, data: data}) app.route(/api/avg_price, methods[GET]) def avg_price(): 返回各区域平均单价 data load_result(avg_price.csv) return jsonify({code: 0, data: data}) app.route(/api/type_ratio, methods[GET]) def type_ratio(): 返回户型占比 data load_result(type_ratio.csv) return jsonify({code: 0, data: data}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)在编写接口时建议统一返回格式例如都包含code和data两个字段。前端可以根据code判断请求是否成功后续扩展错误提示时也方便。load_result函数对分析结果文件做了统一封装后续新增接口时只需要提供一个文件名。5.3 跨域处理与接口测试启动 Flask 接口服务后访问http://localhost:5000/api/avg_price应该能直接看到 JSON 数据。但 Vue 开发服务器运行在http://localhost:8080两个服务端口不同会产生跨域问题。解决方案有两种。一是后端使用 Flask-CORS这已经在代码中配置了二是前端使用 Vue 的代理功能。推荐同时配置两者兼容不同场景。前端代理配置写在vue.config.js中// 文件路径web/vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:5000, changeOrigin: true } } } };配置好代理后前端请求/api/avg_price时开发服务器会将其转发到后端的http://localhost:5000/api/avg_price。这样浏览器侧的请求没有跨域前端代码中也只需要写相对路径后续部署到 nginx 时进行反向代理配置即可。6. Vue ECharts 可视化分析页面6.1 页面规划可视化页面是整个系统的展示核心。常见布局如下顶部展示系统标题和总体指标卡片房源总数、最高单价、平均单价、覆盖城市数。中部左侧展示区域均价柱状图直观对比各区域价格水平。中部右侧展示价格区间分布饼图观察房源价格结构。底部展示户型占比条形图或 TOP10 小区排行。可选城市筛选器切换不同城市后所有图表联动更新。这种布局采用“指标卡 图表”的大屏风格在答辩演示时视觉效果较好。为了不让页面过于复杂先实现一个包含两个核心图表的仪表盘页面。6.2 Vue 组件实现创建一个Dashboard.vue页面代码结构如下template div classdashboard h1房价数据分析可视化平台/h1 div classcharts div classchart-card h3各区域平均单价/h3 div refbarChart classchart/div /div div classchart-card h3价格区间分布/h3 div refpieChart classchart/div /div /div /div /template script import * as echarts from echarts; import axios from axios; export default { name: Dashboard, data() { return { avgPriceData: [], priceRangeData: [] }; }, mounted() { this.fetchData(); window.addEventListener(resize, this.resizeCharts); }, beforeUnmount() { window.removeEventListener(resize, this.resizeCharts); }, methods: { async fetchData() { const [avgRes, rangeRes] await Promise.all([ axios.get(/api/avg_price), axios.get(/api/price_range) ]); this.avgPriceData avgRes.data.data; this.priceRangeData rangeRes.data.data; this.renderCharts(); }, renderCharts() { this.renderBarChart(); this.renderPieChart(); }, renderBarChart() { const chart echarts.init(this.$refs.barChart); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: this.avgPriceData.map((item) item.region) }, yAxis: { type: value }, series: [ { type: bar, data: this.avgPriceData.map((item) item.avg_price), itemStyle: { color: #409eff } } ] }); }, renderPieChart() { const chart echarts.init(this.$refs.pieChart); chart.setOption({ tooltip: { trigger: item }, legend: { bottom: 0% }, series: [ { type: pie, radius: [30%, 60%], data: this.priceRangeData } ] }); }, resizeCharts() { // 简单处理重新渲染图表之前需要先调用 resize 方法 const charts [ echarts.getInstanceByDom(this.$refs.barChart), echarts.getInstanceByDom(this.$refs.pieChart) ]; charts.forEach((chart) { if (chart) chart.resize(); }); } } }; /script style scoped .dashboard { padding: 24px; } .charts { display: flex; gap: 24px; flex-wrap: wrap; } .chart-card { width: 48%; min-width: 400px; border: 1px solid #eee; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } .chart { width: 100%; height: 400px; } /style这段代码需要注意几个地方。第一mounted钩子中先调用fetchData接口数据返回后才渲染图表。因为 ECharts 的setOption需要基于真实数据生成配置项如果数据还没返回就渲染图表会是空的。第二页面销毁时移除resize事件监听避免组件卸载后继续触发回调导致内存泄漏。虽然这是小细节但在答辩讲解时可以体现你对前端工程化的理解。第三ECharts 的饼图数据格式为数组对象每个对象需要包含name和value字段例如[{ name: 3万以下, value: 20 }, { name: 3万-5万, value: 35 }]。后端返回数据时需要按这个格式组装否则饼图渲染不出来。6.3 大屏视觉优化建议如果想让页面更有“大数据可视化”的感觉可以围绕三方面优化深色主题将图表背景设置为深蓝或黑色系配合霓虹色文字这是数据分析大屏的常见风格。指标卡在页面顶部用四个卡片展示关键指标每个卡片包含数字标题和说明文字使用 CSS 渐变背景提升质感。动态效果ECharts 的animationDuration配置项可以控制图表动画时长适当增加动画会让演示时更有节奏感。这些优化纯粹是前端效果不改变系统架构但能在答辩时给评委留下更直接的视觉印象。如果你的前后端基础一般建议先把功能跑通再考虑视觉美化不要一开始就在样式上花太多时间。6.4 前后端联调联调阶段需要保证后端接口服务、前端开发服务器、HDFS 环境都处于运行状态。启动顺序建议是先确认 HDFS 正常再启动 Flask 后端最后启动 Vue 开发服务器。# 终端1启动 Flask cd server python app.py # 终端2启动 Vue cd web npm run serve浏览器访问http://localhost:8080如果页面能正常显示图表并且打开浏览器开发者工具的 Network 面板能看到/api/avg_price请求返回 200说明整条链路已经打通。7. 系统运行与验证方式7.1 完整启动流程为了确保项目在答辩演示时不会出问题建议制定一套固定的启动脚本。核心流程如下表步骤操作验证方式1启动 HDFS访问http://localhost:9870确认 NameNode、DataNode 状态正常2上传数据确认hdfs dfs -ls /housing/input能看到 CSV 文件3运行分析任务确认 HDFS 输出目录中存在part-r-00000结果文件4下载结果到 server/data确认本地结果文件时间戳是最新的5启动 Flask 后端使用 curl 或浏览器访问接口确认返回 JSON6启动 Vue 开发服务器浏览器访问首页确认图表渲染正常这套流程可以整理成一份README.md放在项目根目录中。README 不需要写得很复杂但一定要把启动命令按顺序列清楚。毕业设计提交源码时评审老师第一眼看到的就是 README一个清晰的项目文档会让整体印象好很多。7.2 验证数据分析结果分析结果是否合理需要结合实际业务常识来判断。例如北京、上海、深圳的房价整体高于其他城市如果统计结果中出现某二线城市均价突然高于北京就需要检查数据清洗逻辑或者采集范围是否有问题。一个常见的坑是价格单位不统一。比如有的页面总价用“万”表示单价用“元/平”表示有的页面可能直接给出总价 350 万、单价 45000 元/平。清洗时必须统一转换为可计算的数值否则聚合结果会偏差很大。我通常会额外做一个“数据质量检查”脚本单独输出每个字段的最大值、最小值和空值数量排查速度会明显提高。8. 常见问题与排查思路8.1 环境与部署问题问题现象常见原因解决思路HDFS 启动后 NameNode 一直不在线格式化失败或端口冲突检查core-site.xml端口配置重新格式化 NameNode运行 MapReduce 报 Java 版本错误集群 JDK 与编译 JDK 不一致统一使用 Hadoop 支持的 JDK 版本重新编译HDFS 文件上传失败目录不存在或权限不足先执行hdfs dfs -mkdir -p创建目录Flask 接口启动端口被占用5000 端口被其他程序占用修改app.run中的端口参数前端页面报跨域错误未配置代理或 CORS检查vue.config.js代理配置NameNode 未启动是初学者最容易遇到的问题。排查时先执行jps查看 Java 进程确认是否有NameNode、DataNode进程。如果没有打开 Hadoop 的日志文件查看具体报错信息日志路径一般在logs/目录下文件名类似hadoop-hadoop-namenode-*.log。日志会明确指出配置错误、端口冲突或目录权限问题。8.2 爬虫与中文乱码中文乱码是爬虫项目的高频问题发生原因通常是编码判断错误。解决方法已经在爬虫代码中通过resp.apparent_encoding做了处理。如果还是乱码可以手动指定页面编码例如某些网站明确使用GB2312可以直接赋值resp.encoding gb2312。爬虫拿不到数据的另一种常见情况是页面结构变化。房产网站的页面节点 class 名称不是固定不变的过一段时间可能调整。如果解析不到字段先用浏览器开发者工具查看当前页面的实际 DOM 结构更新选择器。8.3 Hadoop 输出结果为空如果 MapReduce 任务成功执行但输出文件为空大概率是输入路径中没有文件或者 Mapper 解析逻辑把所有行都跳过了。排查步骤很简单先用hdfs dfs -cat /housing/input/house_price.csv查看文件内容是否正常再检查 Mapper 中字段分割符和字段下标是否与 CSV 实际结构一致最后确认 CSV 文件没有表头或者 Mapper 正确跳过了表头。9. 毕业设计与文档报告组织9.1 毕业设计文档怎么写很多同学的代码已经跑通了但毕业论文报告迟迟写不出来。原因在于没有把论文结构和代码模块对应起来。建议按以下目录组织毕业设计文档需求分析描述系统要解决什么问题用户角色是谁需要哪些功能。系统设计画系统架构图、模块图、数据库设计定义接口规范。系统实现按数据采集、大数据分析、后端接口、前端可视化四个模块分别描述实现方案。系统测试编写测试用例给出关键功能的测试截图。总结与展望总结项目完成的指标提出可以继续改进的方向。写代码过程中注意保存设计草稿和截图。比如 HDFS 浏览器中显示的文件列表截图、MapReduce 任务执行成功截图、ECharts 图表的展示截图这些内容放在论文中非常加分。9.2 答辩现场演示脚本答辩演示不要临时打开页面乱点最好准备一个固定脚本展示系统首页说明数据来源和分析主题。展示爬虫模块运行一次爬虫抓取小批量数据演示数据清洗过程。展示 HDFS 文件说明数据存储位置。运行一个 MapReduce 或 Hive 统计任务展示执行过程和结果。展示前端图表结合业务背景解释图表说明了什么。演示时重点讲清楚数据流数据从网页到 HDFS再到分析结果最后到前端图表的完整链路。评委最关心的不是你用了多新的技术而是你是否真正理解系统里每个组件的职责和它们之间的协作关系。9.3 后续可扩展方向如果你的时间和精力允许可以考虑在现有基础上加入以下能力增加项目深度用 Spark SQL 替代部分 MapReduce 任务对比两种引擎的运行效率。在可视化页面增加地图展示通过 ECharts 的 geo 地图组件显示各区域房价热力图。加入时间维度爬虫按周定时采集数据分析房价走势。使用 Docker 将 Hadoop、后端、前端打包成容器方便环境迁移。对价格做简单的线性回归预测形成“现状分析 趋势
返回列表