ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python二手房数据分析:爬虫、清洗、可视化与建模全流程

Python二手房数据分析:爬虫、清洗、可视化与建模全流程 简介基于Python的二手房数据分析完整项目专为毕业设计、期末大作业和课程设计场景打造。项目从数据采集清洗到可视化展示形成闭环18个Python源码文件覆盖数据读取、清洗、建模和可视化等环节18个CSV数据集包含原始房源表与多版本清洗结果15个HTML页面和11个JS脚本搭建交互式展示界面另配文档说明和PPT汇报材料适合不同基础的学习者快速上手。源码关键位置附有详细注释整体逻辑清晰简单部署即可运行65张PNG截图和可视化图表直观呈现分析过程与结论便于答辩演示与成果汇报。整个资源包共157个文件压缩后48.05MB结构紧凑、分类明确CSV文件还兼顾不同编码格式便于在多种环境下直接读取。已有127人学习/下载对于需要快速完成高质量数据分析类课程设计、期末大作业或毕业设计的同学是一份经过调试、可直接参考和复用的高分范本。1. 二手房照样能做出让人眼前一亮的数据分析项目同学毕设选题十个里有六七个卡在“不知道做什么数据、跑完怎么讲”。但 Python 二手房数据分析这题反而是最不容易翻车的一类数据量大、字段结构稳定、结论可解释性天然强。标题里的“完整源码文档说明PPT资料”指向的是一份能直接复现、能应对答辩追问的交付物而这条链路并不复杂——爬虫抓列表页、清洗字段、做特征、画图、建模、最后把结果整理成文档和PPT。对刚入门 Python、想完整走一遍数据分析流程的人来说它比纯练习题更能建立全局观。接下来我就按这个顺序把每一步的代码、参数和坑摊开讲新手能照做熟手能直接抄边界条件。2. 房源数据从哪来爬虫采集与字段设计2.1 结构化列表页为什么是数据源首选二手房数据分析项目最容易被低估的一步是数据获取。有人一上来就想到平台开放 API但这几年开放接口收紧个人申请基本拿不到也有人想通过搜索引擎聚合网页再解析解析成本高、字段还不对齐。我一般直接选链家、贝壳这类结构化列表页原因很朴素一个小区列表页里的每套房标题、户型、面积、朝向、楼层、总价、单价都在固定的 HTML 节点里只需要一个解析函数就能稳定抽出几百条记录。这类页面的另一个好处是翻页规则非常规整ershoufang/pg1、pg2一路往后翻就行不用处理复杂的异步加载和签名参数。爬下来的是城市维度的挂牌数据不是成交数据用来研究房价分布和影响因素足够了如果论文或课设需要成交价再去找对应城市的存量成交明细但那种数据通常需要额外授权建议在项目文档里写清楚用的是挂牌数据。提示爬虫只做学习用途控制频率、不要并发抓取也不要抓取需要登录才能看的访客数据。这个项目重在链路完整不是抓得多才高分把前几页数据跑通胜过后台挂着抓三天。2.2 一个最小可运行爬虫请求头、解析与字段提取常见做法是用 requests 拉页面BeautifulSoup 配合 lxml 解析。下面这段代码是完整的抓取保存逻辑运行在 Python 3.8 环境依赖 requests、beautifulsoup4、lxml 三个库装好就能直接跑import requests from bs4 import BeautifulSoup import csv import time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(city_spider: str, page: int) - list: url fhttps://{city_spider}.lianjia.com/ershoufang/pg{page}/ resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, lxml) items [] for li in soup.select(.sellListContent li .info): item {} a li.select_one(.title a) item[title] a.text.strip() if a else info li.select_one(.houseInfo) item[house_info] info.text.strip() if info else p li.select_one(.totalPrice span) item[total_price] p.text.strip() if p else u li.select_one(.unitPrice span) item[unit_price] u.text.strip() if u else items.append(item) return items if __name__ __main__: all_data [] for pg in range(1, 6): # 前5页验证流程够用 all_data.extend(fetch_page(bj, pg)) time.sleep(1) # 控制抓取频率 with open(houses.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, house_info, total_price, unit_price]) writer.writeheader() writer.writerows(all_data) print(saved, len(all_data), rows)这段代码的逻辑分三层requests.get负责把页面拉回来BeautifulSoup负责解析 DOM最后的csv.DictWriter把结果落盘。需要注意 HEADERS 里最关键的是User-Agent不带或不标准很容易触发拦截Accept-Language保证返回的中文页面避免解析到英文或乱码节点这在国内站点上时常出现。几个参数的实用取法fetch_page里每次只解析当前页因为sleep(1)必须保留把 5 页变成 50 页还不加 sleep基本几十页后就会被限流。CSV 保存用utf-8-sig是为了让 Excel 打开不出现中文乱码普通utf-8在 Windows 上打开会乱。跑完后确认保存条数是否约等于 5 页乘每页 30 条如果差太多先检查select选择器是否匹配当前页面结构。2.3 数据落库与增量更新CSV 和 SQLite 怎么选项目前期用 CSV 完全够用结构简单、肉眼可查、Excel 能直接打开。但如果你打算抓多个城市、连续多天抓取或者后面要按天对比挂牌价变化我建议早点换成 SQLite。理由有两条一是重复运行脚本不会产生重复数据二是后面做可视化查询时写 SQL 比在 DataFrame 里反复去重省事得多。import sqlite3 import pandas as pd df pd.read_csv(houses.csv) conn sqlite3.connect(houses.db) df.to_sql(house, conn, if_existsappend, indexFalse) # 幂等去重避免下次重复导入产生脏行 conn.execute( DELETE FROM house WHERE rowid NOT IN ( SELECT MIN(rowid) FROM house GROUP BY title, house_info, total_price, unit_price ) ) conn.commit() print(conn.execute(SELECT COUNT(*) FROM house).fetchone()[0])逻辑说明先把 CSV 追加导入 SQLite再用GROUP BY找出重复组合里最早的那条删掉其余重复行。title、house_info、total_price、unit_price四个字段组合起来基本能唯一识别一套房后面如果加了小区和街道字段组合可以更精确。参数上注意if_existsappend是追加语义不是覆盖重复跑脚本时配合去重 SQL 才能保持数据干净。这套流程跑通后手里就有一张结构化的房源表。接下来进入整个项目最花时间的环节把原始字段变成能算、能画、能建模的干净数据。3. 数据清洗与特征工程房价分析的底气都在这里3.1 四类脏数据先做标准清洗原始数据长什么样上一章的输出已经能看出来——house_info是一个包含小区、户型、面积、朝向、装修、楼层、楼龄的拼接字符串total_price是整数unit_price是62345元/平这种带单位的文本。先处理最常见的四类脏数据空值、单位、拼接文本、异常值。只要 Python 基础语法过关这部分处理起来其实很机械但漏掉任何一类后面建模都会出问题。import pandas as pd df pd.read_csv(houses.csv) df df.dropna(subset[title, total_price, unit_price]) # 拆 house_info示例格式 小区名 2室1厅 89.5平米 南 简装 中楼层 2012年建 parts df[house_info].str.split( , expandTrue) df[layout] parts[1].fillna(未知) df[area] pd.to_numeric(parts[2].str.replace(平米, ), errorscoerce) df[orientation] parts[3].fillna(未知) df[floor] parts[4].fillna(未知) df[build_year] parts[5].str.replace(年建, ) df[total_price] df[total_price].astype(float) # 单位万 df[unit_price_clean] ( df[unit_price].str.replace(元/平, ).str.replace(,, ).astype(float) ) df[price_checked] df[total_price] * 10000 / df[area] # 核单价 df df[(df[area] 5) (df[area] 500)] df df[(df[total_price] 5) (df[total_price] 5000)] df.to_csv(houses_clean.csv, indexFalse, encodingutf-8-sig)参数说明split( , expandTrue)的展开列数取决于页面字段如果小区名带空格整体列位置会后移建议先打印parts.head()确认列位置再继续。area字段如果是89.5㎡这种写法replace后直接astype(float)会抛错用pd.to_numeric(..., errorscoerce)更稳无法转换的会变成 NaN后续统一处理。price_checked是核单价用来校对页面给的单价是否合理两边差距超过 20% 的行标记为可疑不急着删。很多初学项目在第一步就翻车不是代码不会写而是没意识到爬下来的东西根本不是表。上面这套清洗做完得到的是可以进模型的 DataFrame后面所有分析都建立在它上面所以这一步宁可多花半小时核对也别急着往后跑。3.2 特征工程把字符串变成模型能用的数字清洗只是把数据变干净特征工程才是提分的关键。二手房分析里最常用的几个衍生特征房龄当前年份减建成年份、单价总价乘 10000 除以面积、朝向分组、楼层分组、商圈编码。import numpy as np from sklearn.preprocessing import LabelEncoder df pd.read_csv(houses_clean.csv) now_year 2024 df[house_age] now_year - pd.to_numeric(df[build_year], errorscoerce) def group_orientation(ori): if ori in [南, 东南, 西南, 南北, 南东, 南西]: return south if ori in [东, 西, 东西]: return ew if ori in [北, 东北, 西北]: return north return unknown df[ori_group] df[orientation].apply(group_orientation) df[is_south] (df[ori_group] south).astype(int) le LabelEncoder() df[floor_code] le.fit_transform(df[floor].fillna(未知))说明house_age在build_year缺失时相减会变成 NaN建模前要么填中位数要么直接删不要留着让 sklearn 报错。朝向分组里“南、东南、西南、南北”归为south是基于北方城市“朝南最好卖”的经验如果是南方城市西晒问题会让西南朝向口碑分化这个阈值要按城市调整。LabelEncoder的编码结果是 0、1、2……本身有大小关系这在树模型里问题不大但线性回归里会诱导模型以为“编码越大的楼层对价格影响越大”如果坚持用线性回归建议用 one-hot 替代。3.3 切分数据前的质量检查三个硬指标特征做完了先别急着建模做一次“三查”。第一查空值率逐列打印缺失比例第二查重复率按核心字段集合统计重复行数第三查离群看面积、单价、总价的分布有没有明显异常。print(df.isnull().mean().sort_values(ascendingFalse)) print(dup:, df.duplicated(subset[title, area, total_price]).sum()) print(df[[area, total_price, unit_price_clean]].describe(percentiles[.01, .05, .95, .99]))这三个指标决定后面模型的天花板空值率超过 20% 的列建议直接砍重复率过高说明爬虫去重逻辑有问题离群值不一定删但要标记。percentiles是这段代码里唯一需要调的参数数据量小的时候看 1% 和 99% 分位就够数据量大时加 0.5% 和 99.5% 能更早发现极端值。把这些检查结果写进项目文档答辩时是很加分的“数据质量说明”部分——它证明你不是拿到数据就直接开跑而是对数据做过系统的体检。4. 可视化分析与模型验证把数据变成能讲结论的图4.1 静态结论图seaborn 一把梭出核心洞察数据干净了先出最直观的结论图。二手房分析里永远三个图最有用单价分布直方图、面积-总价散点图、商圈均价 Top 条形图。这三个图覆盖了“整体行情、一房一价关系、区域差异”三个问题也是文档和 PPT 里最常被引用的素材。import matplotlib.pyplot as plt import seaborn as sns sns.set_theme(stylewhitegrid) df pd.read_csv(houses_clean.csv) fig, axes plt.subplots(1, 3, figsize(16, 4)) sns.histplot(df[unit_price_clean] / 10000, bins40, axaxes[0]) axes[0].set_title(单价分布万元/平) sns.scatterplot(datadf, xarea, ytotal_price, alpha0.6, axaxes[1]) axes[1].set_title(面积-总价关系) top df.groupby(district)[unit_price_clean].mean().sort_values(ascendingFalse).head(10) sns.barplot(xtop.values, ytop.index, axaxes[2]) axes[2].set_title(商圈均价Top10) plt.tight_layout() plt.savefig(insights.png, dpi150)参数上bins40是直方图分箱数样本量在 300 条以内时 40 箱会显得碎改 30 更干净dpi150是导出清晰度进 PPT 够用要打印再提到 200。groupby(district)依赖数据里有商圈字段如果爬虫里没抓用小区名前缀做粗粒度分组也可以。这里去掉 hue 参数样本几百条时按朝向分色块区分不明显反而干扰“面积涨总价涨”这个主结论。图保存用savefig而不是plt.show()避免在无图形界面环境里卡住远程跑项目时这个习惯很省事。4.2 可视化界面FastAPI ECharts 做一个能交互的报告静态图只够放在文档和 PPT 里。如果你想做一个能现场点选的“python 爬虫可视化界面”最省事的方案是 FastAPI 做后端、ECharts 做前端图表。后端提供一个 JSON 接口前端一个下拉框切换区域图表跟着变演示效果比静态图强一个档次。# app.py from fastapi import FastAPI from fastapi.responses import HTMLResponse import pandas as pd app FastAPI() df pd.read_csv(houses_clean.csv) app.get(/api/price/{district}) def price_dist(district: str): sub df[df[district] district] bins pd.cut(sub[unit_price_clean], 20) hist sub.groupby(bins, observedFalse).size() return {district: district, bins: [str(b) for b in hist.index], counts: hist.tolist()} app.get(/, response_classHTMLResponse) def index(): return !DOCTYPE html htmlheadmeta charsetutf-8title二手房单价分析/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script/head body h3选择区域/h3 select idsel option朝阳/optionoption海淀/optionoption丰台/optionoption通州/option /select div idchart stylewidth:800px;height:400px;/div script const chart echarts.init(document.getElementById(chart)); async function load(){ const d document.getElementById(sel).value; const res await fetch(/api/price/ d); const data await res.json(); chart.setOption({xAxis:{data:data.bins}, yAxis:{}, series:[{type:bar, data:data.counts}]}); } document.getElementById(sel).onchange load; load(); /script/body/html逻辑说明/api/price/{district}是核心接口district决定过滤哪个区域返回单价分箱后的频数分布交给 ECharts 画柱状图前端就一个下拉框加一个 div复杂度很低。pd.cut的分箱数 20 可以按数据量调只有 200 条数据时 20 个箱子会很碎减到 10 更合适。跑起来用uvicorn app:app --port 8000浏览器打开127.0.0.1:8000就能看。这个界面的价值不只是好看它让项目从“我写了分析代码”变成“我做了一个可交互的数据分析工具”。答辩现场换一个区域让图表变化比放十张静态图更有说服力也是标题里“高分项目”最容易出彩的地方。4.3 模型验证回归跑出来不是终点能解释才是可视化回答了“什么因素和房价相关”模型用来回答“这些因素组合起来能预测到什么程度”。二手房价格预测最常见的做法是 log1p 变换后做回归。总价分布一般右偏严重直接回归会让模型把注意力全放在高价房上取对数后分布接近正态误差评估也更合理。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score import numpy as np features [area, house_age, is_south, floor_code] X df[features].copy() X[total_price_log] np.log1p(df[total_price]) y X.pop(total_price_log) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) lr LinearRegression() lr.fit(X_train, y_train) y_pred_lr lr.predict(X_test) print(Linear RMSE:, np.sqrt(mean_squared_error(y_test, y_pred_lr)), R2:, r2_score(y_test, y_pred_lr)) rf RandomForestRegressor(n_estimators200, max_depth8, random_state42) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(RF RMSE:, np.sqrt(mean_squared_error(y_test, y_pred_rf)), R2:, r2_score(y_test, y_pred_rf))参数说明random_state42是为了让结果可复现答辩时两次跑的结果不一样会很尴尬test_size0.2在几百条数据下合理数据更少时改 0.3随机森林的max_depth8是为了控制过拟合小样本上树太深时 R² 会虚高。评估指标统一看 log 尺度下的 RMSE不要只报 R²R² 在样本量小时参考意义有限RMSE 才能反映“预测一套 400 万的房平均误差多少万”。模型部分到这里足够支撑一个高分项目有基准模型线性回归、有提升模型随机森林、有指标比较。真正让项目区分度变高的是把“哪些特征重要、为什么重要”讲清楚——这部分放进文档比放进代码更值钱。5. 避坑与常见问题爬虫、清洗、评估三个环节最容易翻车的 5 个点避坑这一章我单独拎出来写原因很简单这个项目里代码本身不难真正消耗时间的是各种环境、结构和数据上的意外。我见过不少做 Python 数据分析课设的同学翻车点高度集中在这五个地方每个都能让人白折腾一个晚上。5.1 反爬连续抓取几十页后全部超时现象脚本前几十页抓得很快到一定页数后 requests 开始抛超时异常或者返回的 HTML 里找不到任何.sellListContent节点看起来像页面改版了。原因列表页有基于 IP 的访问频控短时间内请求数超过阈值就进入临时限制。很多脚本没加 sleep甚至用了多线程通常几百个请求就会触发。解决每次请求之间至少time.sleep(1)数据量少时 2 秒更稳分页循环里加一个简单的重试逻辑连续失败三次就退出而不是无限重试一次只抓一个城市的前 20 到 30 页完全够分析用了。不推荐也不需要用任何绕过限流的方案学习项目里把频率降下来比什么技巧都管用。5.2 页面结构一改字段全错位现象昨天跑得好好的脚本今天抓下来的house_info里面积变成了朝向朝向变成了楼龄。原因目标页面的 DOM 结构调整了字段顺序或 class 名称变了。这是爬虫类项目最常见的“结构漂移”问题平台改版频率比想象中高。解决把页面解析逻辑收敛到一个函数里页面变化时只改一处每次跑完检查字段取值分布面积列如果出现非数值立刻停下来抓取时间精确到天记录在数据文件里方便追溯。还有一个实用习惯把原始 HTML 存一份到本地字段错乱时打开看结构比反复请求页面高效得多。5.3 假房源与离群点比缺数据更麻烦现象清洗后画散点图发现左下角有一批总价 3 万、面积 200 平的点模型拟合时 R² 一直上不去。原因挂牌平台存在中介用低价噱头吸引点击的虚假房源这部分数据不是真实市场规律属于被刻意制造出来的离群点。解决先按分位数做标记而不是直接删。比如总价低于 1% 分位或单价低于同商圈均价 50% 的行加一列suspicious1建模时跑一个不含噪声的版本做对比如果 R² 明显上升就在文档的数据质量说明里写明处理方式。答辩时主动交代这批数据怎么处理的比被提问时支支吾吾强得多。5.4 总价不取对数直接回归结果惨不忍睹现象线性回归跑完RMSE 大得离谱预测出来甚至出现负的总价。原因总价分布严重右偏不满足线性回归对误差正态性的假设模型被少部分高价房主导普通房源的误差反而很大。解决目标值从一开始就用np.log1p取对数误差评估都在对数空间做展示结果时再用np.expm1还原成真实价格单位。另一个细节是评估指标不要只报 R²log 尺度下的 RMSE 要一起报两个指标结合才能看出模型在普通价位段表现如何。这不算奇技淫巧几乎所有房价预测项目都是标准操作。5.5 Windows 下中文路径和编码问题环境一换就全崩现象在 Mac 上跑得好好的项目拷到 Windows 一运行就报UnicodeDecodeErrorCSV 用 Excel 打开一片乱码。原因Windows 默认编码是 GBK读取没指定 encoding 的 utf-8 文件直接报错保存时用了 utf-8 而不是 utf-8-sigExcel 不认缺失 BOM 的 utf-8。解决所有pd.read_csv和open都显式传encodingutf-8-sig或utf-8保存统一用utf-8-sig项目根目录准备requirements.txt并写清 Python 版本用 PyCharm 或 VSCode 打开项目时把文件编码统一设成 UTF-8避免中文注释变成乱码。Python 安装本身不难难的是装完之后这套环境和编码的坑。还有一个最容易忽略的项目路径不要带中文和空格Windows 下一旦路径里有中文某些库会报一些玄学级错误。这五条不是每个项目都会全遇到但反爬和页面结构两条出现的概率最高。遇到时先别急着改代码把现象截图、把报错信息贴到文档里再对照原因逐条排查大多数都能在半小时内定位。6. 文档、PPT 与答辩把分析结果做成一份高分交付项目代码跑通只是完成了一半另一半是文档和 PPT。README 第一屏要写清楚三件事数据来源和抓取时间、运行环境与依赖、一键复现的步骤。数据字典单独用表格列出字段说明和类型这是评审最想看的部分——它证明你知道每个字段在讲什么而不是只会调库。PPT 的思路是一页讲一个洞察第一页放单价分布讲这个城市挂牌价的集中区间第二页放面积-总价散点讲刚需面积段和价格弹性第三页放商圈对比讲区域差异第四页放模型结果讲哪些特征最影响价格、误差多大。每页配一句话结论就够别堆代码截图。答辩演示有个我自己的血泪习惯先准备好“已知缺陷”的表述。比如数据是挂牌价不是成交价、样本量只有几百条、模型没有做时序验证。主动讲出这些边界比被评委问出来要体面得多也说明你是真的理解项目而不是只会跑代码。文档里再加一节“改进方向”写未来可以接入爬虫定时更新、加入经纬度做空间聚类、用成交数据替换挂牌数据——这些不一定要实现但展示了分析的延伸思考能力。这个方向值不值得投入如果你需要的是一个能讲清完整数据流、能现场演示交互界面的 Python 数据分析项目二手房这个选题的性价比确实高数据获取门槛低、字段容易解释、可视化效果好模型也不用很复杂就能出结论。希望这些经验能帮你在复现这套项目的过程中少走几步弯路把时间花在真正提分的文档和答辩准备上。本文还有配套的精品资源点击获取
返回列表