Python数据分析实战:北上广二手房房价全流程解析

发布时间:2026/9/26 5:38:00
Python数据分析实战:北上广二手房房价全流程解析 每年毕业季一到选“Python大数据分析”方向的同学一堆其中又有一半人扎堆做房价分析。导师一句“题目太大、太泛”能把你从第一稿打回到选题列表——但说实话北上广住房数据这个题目不是不能做而是很多人压根没想清楚怎么做。我当年从数据抓取到最终成稿完整跑了一遍最有感触的一点是真正卡住你的从来不是Python语法而是那堆脏到怀疑人生的原始数据以及你能否从中挖出几个能讲圆故事的结论。这篇文章把这个项目的完整流程、踩坑过程和最终方案都盘一遍适合正在纠结毕设题目的在校生也适合想用Python做一次完整数据分析实战的朋友。1. 项目整体设计与思路拆解1.1 为什么选北上广住房数据作为切入点选这个题首先要回答导师最常问的那句“你的创新点在哪”如果只说“我爬了链家房价数据画了几张图”那和别人的区别基本为零。但如果你把题目收敛成“北上广三城二手房挂牌数据对比分析”再加上“基于成交特征的影响因素建模”这个题就立得住。北上广这三个城市的住房数据天然具备几个优势数据体量足够大链家、贝壳、安居客这类平台的二手房挂牌量长期维持在数万到数十万条完全撑得起Pandas的处理规模也不至于大到个人电脑跑不动。维度足够丰富小区名、城区、户型、面积、朝向、装修、楼层、总价、单价、挂牌时间这些字段能做横向城市对比也能做纵向价格分析。结论容易落地不同城市的价格结构差异、面积段偏好、学区属性差异这些都是客观可分析的点论文里也好展开。但这里有个非常关键的提醒千万别一开始就想着“爬全网数据”。有人规划时写“爬取链家、安居客、房天下、贝壳四个平台全量数据”——这在毕设周期里几乎不可能完成光反爬就能耗掉你大半时间。我的做法是选定一个主数据源把字段抓全、抓干净再用手工补充或公开数据集做交叉验证这样数据可控、结论可复现工作量也在合理范围内。1.2 技术栈选型与Python生态的取舍这个项目用到的技术栈我建议按“处理链条”来选而不是按“热门程度”来选。整个链条是数据采集 → 清洗入库 → 探索性分析 → 可视化 → 建模预测 → 结论呈现。我最终敲定的技术组合如下环节工具选择理由环境管理Anaconda Jupyter Notebook省去Python安装和包管理的麻烦Notebook适合分步调试数据采集Requests BeautifulSoup轻量、够用二手房列表页是静态HTML不需要上Scrapy动态页面处理Selenium备用部分平台有JS渲染但能不用就不用性能差太多数据清洗Pandas NumPy绝对核心处理缺失值、异常值、字段规整全靠它数据可视化Matplotlib Seaborn Pyecharts前两个画学术图pyecharts做交互式地图和仪表盘建模分析Scikit-learn线性回归、随机森林、KMeans一套齐全数据库SQLite轻量、单文件毕设答辩演示方便不用配服务这里我想多说一句关于环境配置的坑。很多人第一步就死在“装Python”上下载完官方安装包之后直接在命令行敲pip结果装了包导入时报错然后开始怀疑人生。我给的建议是装Anaconda别装裸Python。Anaconda自带的conda环境管理能让你用一句话创建隔离环境conda create -n house_analysis python3.9 conda activate house_analysis pip install pandas numpy requests beautifulsoup4 matplotlib seaborn pyecharts scikit-learn jupyter用编辑器的话VSCode加Python插件就够了配置Python解释器路径时记得选到Anaconda环境里的那个python.exe而不是系统自带的路径。这一步配置错了后面所有“明明装了包却ImportError”的问题都从这里来。1.3 完整流程规划与时间分配毕设最怕的不是难而是拖延。我建议把项目切成分阶段的里程碑每个阶段交付一个可见结果选型与准备3天确定数据源、完成环境搭建、跑通一个简单页面抓取。数据采集1-2周完成城市、区域、翻页的遍历抓取落库并做好日志。数据清洗1周缺失值处理、字段规整、格式统一产出干净的CSV或SQLite表。探索分析与可视化1-2周做价格分布、区域对比、户型差异等分析产出图表。建模分析1周建立影响房价的关键因素模型输出特征权重或预测指标。论文与答辩整理1-2周把分析过程转化为论文章节准备演示图表。时间分配的核心思想是清洗占三成时间建模最多占一成。很多同学把重心放在跑模型上结果前端数据是脏的模型再花哨也是空中楼阁。后面我会重点讲清洗阶段要注意的细节那是整个项目性价比最高的部分。2. 数据获取爬虫方案与公开数据集怎么选2.1 数据源选择的优先级与合规问题做住房数据分析数据源就那么几个链家贝壳、安居客、房天下、58同城以及Kaggle或国内开源平台上的二手数据集。从毕设角度我的优先级排序是链家贝壳 房天下 公开数据集 安居客。链家是我最终选的源原因很实际结构规整列表页每个房源卡片的信息字段相对统一标题、位置、户型、面积、单价、总价、关注人数都在固定位置。翻页路径清晰URL参数规则简单城市、区域、页码都在URL里可见拼接方便。反爬相对温和比某些平台的验证码地狱友好得多只要控制好请求频率基本能稳定采集。合规方面提醒一句做研究用途抓取公开信息在个人学习、学术研究范围内相对宽松但不要拿去商用也不要做成公开服务供他人批量抓取。爬虫代码和采集逻辑讲清楚是加分项但别把自己绕进合规风险里。论文里建议明确写“数据仅用于学术研究”这个说明在答辩时能省掉不少麻烦。2.2 爬虫脚本的落地实现我用Requests BeautifulSoup搭了一套最简但够稳的采集框架。核心思路是先抓一个列表页解析出房源详情链接再遍历详情页抽取结构化字段。列表页的关键解析逻辑大概长这样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, Referer: https://www.lianjia.com/ } def parse_list_page(url): resp requests.get(url, headersheaders, timeout15) if resp.status_code ! 200: print(f请求失败: {resp.status_code}) return [] soup BeautifulSoup(resp.text, html.parser) items soup.select(.sellListContent li .info) house_list [] for item in items: title item.select_one(.title a).get_text(stripTrue) link item.select_one(.title a).get(href) house_code link.split(/)[-1].replace(.html, ) house_list.append({title: title, code: house_code, link: link}) return house_list详情页解析字段时要特别留意HTML结构的细节。链家的详情页里总价和单价分别在.total和.unitPrice两个标签里但解析出来的字符串长这样total_price soup.select_one(.total).get_text(stripTrue) # 850 unit_price soup.select_one(.unitPrice).get_text(stripTrue) # 63568元/平米注意total标签下面可能还有单位“万”单价字符串里夹着一个空格和“元/平米”这些都得在后面的清洗阶段统一处理。字段抓不全并不可怕怕的是抓下来了却没做格式解析后面全变成脏数据。2.3 反爬应对与数据完整性保障爬虫跑起来之后第一道坎就是反爬。链家不会一上来就封你但如果你用固定User-Agent连续高频请求几百次之后就会开始出现验证码或直接403。我的应对策略有四个实测效果还可以请求头伪装User-Agent用浏览器真实的UAReferer带上网站域名有的页面还会校验这个。请求限速每请求一次随机睡1到3秒。不要用time.sleep(1)这种固定值用random.uniform(1, 3)更自然。失败重试请求异常时重试最多3次重试间隔递增比如5秒、10秒、20秒。断点续采每抓完一个城市片区就把进度记录到一个JSON文件里下次启动时跳过已抓页码。采集完整性的检查方式也很重要。每抓完一个城市我统计一下总记录数和各区域记录数和网站列表页的展示数量对比偏差超过5%就说明中间漏了一批需要补采。这一步看似简单却直接决定了后面分析结论的可信度——漏掉一整片区域的数据画出来的地图上就会缺一块答辩时被导师指着图问“广州番禺呢”你会非常被动。3. 数据处理把脏数据洗成能分析的干净表3.1 缺失值与重复值的处理思路抓回来的原始数据脏的程度往往超出预期。最典型的表现就是字段缺失有的房源没填朝向有的没写装修有的面积是“暂无数据”有的单价字段直接是空字符串。Pandas处理缺失值有几个常用手段但不要无脑填0或删除。我用的策略是按字段重要性区分df pd.read_csv(houses_raw.csv) # 查看缺失情况 missing_ratio df.isnull().mean().sort_values(ascendingFalse) print(missing_ratio) # 面积、总价缺失直接删除 df df.dropna(subset[area, total_price, unit_price]) # 朝向、装修等分类字段缺失用众数填充 for col in [orientation, decoration, floor]: df[col] df[col].fillna(df[col].mode()[0]) # 经纬度缺失根据小区名通过批量地理编码补齐 geocode_cache {} def fill_lnglat(row): key row[community] if key in geocode_cache: return geocode_cache[key] # 调用地理编码接口带回lng/lat ...分数线字段同理单价和总价是后续分析的核心指标缺失值占比超过1%就直接删掉那条记录占比低删起来不影响整体分布。朝向这种分类数据缺失多了反而别用众数填充会导致该分类比例失真更好的办法是单独立一个“未知”类型。3.2 异常值与格式规范化数据清洗里最花时间的其实是格式规范化。链家抓下来的字符串五花八门总价“850” 或 “850万”单价“63568元/平米” 或 “63568元/㎡”面积“89.4平米” 或 “89.4㎡”楼层“低楼层/共6层”建造年份“2008年建”这些字符串不处理成数值型别说建模连求个平均值都是报错。我写了一段统一的清洗函数import re def clean_numeric(value): if isinstance(value, (int, float)): return float(value) if value is None: return float(nan) # 提取数字部分兼容850万、63568元/平米、89.4㎡ nums re.findall(r(\d\.?\d*), str(value)) if not nums: return float(nan) return float(nums[0]) df[total_price] df[total_price].apply(clean_numeric) df[unit_price] df[unit_price].apply(clean_numeric) df[area] df[area].apply(clean_numeric)异常值这块有两个经典陷阱总价 9999 万这是挂牌测试房源业主随便挂的单价可能超过几十万一平。这类数据不一定是录入错误但会严重拉高均值分析前最好按分位数截断比如保留1%到99%分位之间的数据。面积 1000 平米以上别墅、商业性质住宅混入住宅数据对普通住宅分析干扰很大。建议在探索阶段先看一下面积分布直方图结合实际经验确定合理区间。3.3 特征工程从原始字段里挖出可用特征清洗完之后我习惯再生成几个派生特征这些特征在后续分析和建模里非常有用楼龄用当前年份减去建造年份。楼龄对房价影响显著尤其在老城区。每层均价unit_price和total_price / area有时候对不上两者差异超过10%的数据可能是录入异常做一致化校验。区域粒度把行政区再做一次归类例如北京划分为“城六区”和“远郊区县”上海划分为“内环内”“内中环间”“中外环间”“外环外”这比直接用几十个行政区做对比更直观。房型组合把“3室2厅”拆成居室数和厅数两个数值字段后续可以做户型与总价的交叉分析。加完这些派生特征后清理过的数据就可以存成一张干净的houses_clean.csv后面的所有分析和建模都基于这张表。我在这里踩过一次大跟头一开始图省事直接用原始数据建模结果模型跑出来R方只有0.3后来排查发现单价字段里混进了大量“暂无标价”转成的0值导致特征分布完全被污染。清理后再跑R方直接翻倍——数据质量对模型的影响就是这么直观。4. 分析建模与可视化让结论看得见4.1 三城房价分布与区域对比分析清洗后的第一件事我建议先做整体的描述性统计。用Pandas一行代码看核心指标summary df.groupby(city)[unit_price].describe() print(summary)我当时跑出来的结果数据仅供示例大致是这样的规律北京和上海的单价中位数显著高于广州且北京的单价极差最大——中心城区和远郊的差距能达到几倍广州的单价分布相对平缓远郊的中位数也没有低到离谱。这个发现本身就值得写进论文第一章节“一线城市内部的价格梯度差异”。再往下拆一个维度各城市行政区的均价排行。用Seaborn画横向条形图就能清晰看到北京的西城、东城、海淀领跑上海的黄浦、静安、徐汇领跑广州的天河、越秀领跑。这个对比图表拿出来答辩时可以说“看数据是符合常识的说明采集清洗质量可靠”这比任何吹嘘都有说服力。4.2 面积、户型与总价的关联分析这个环节主要回答一个问题“在北上广同样的预算能买到多大面积”拆解方法是按城市分桶统计bins [0, 50, 70, 90, 110, 140, 200, 1000] labels [50㎡以下, 50-70㎡, 70-90㎡, 90-110㎡, 110-140㎡, 140-200㎡, 200㎡以上] df[area_bucket] pd.cut(df[area], binsbins, labelslabels) pivot pd.pivot_table(df, indexarea_bucket, columnscity, valuestotal_price, aggfuncmedian) print(pivot)从这个透视表能看到一个很有意思的规律在70-90㎡这个刚需面积段广州的总价中位数可能是北上的一半以上而在140㎡以上改善段上海的溢价反而比北京更猛。这类结论是由真实数据支撑的写进论文里比泛泛的“北上广房价高”要有力得多。户型和总价的关系可以再用箱线图呈现。拿“室数”作横轴、总价作纵轴每个城市画一张子图能清楚回答“多一个房间要加多少钱”这种接地气的问题。比如三居室的总价中位数比两居室贵多少、四居室是否开始出现明显的分层这些都是可以写成结论的点。4.3 机器学习模型影响房价的关键因素排序说到建模毕设里最常见的错误是“为了建模而建模”。如果数据里变量之间本身就高度共线比如面积和总价线性回归跑出来R方很高但意义不大。我的建议是用随机森林回归模型做特征重要性排序它能处理非线性关系同时给出每个特征的贡献程度。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder feature_cols [area, bedrooms, living_rooms, building_age, decoration_num, floor_num] df[decoration_num] LabelEncoder().fit_transform(df[decoration]) df[floor_num] df[floor].str.extract(r(\d)).astype(float) X df[feature_cols].fillna(0) y df[total_price] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) model RandomForestRegressor(n_estimators200, random_state42) model.fit(X_train, y_train) print(R²:, model.score(X_test, y_test)) importance pd.Series(model.feature_importances_, indexfeature_cols).sort_values(ascendingFalse) print(importance)我跑出来的经验是面积几乎总是排第一楼龄和户型次之纯地理区域信息如果没编码进模型重要度反而体现不出来。所以建议把“行政区”也做LabelEncoder后加进特征集合能更全面展示区位对总价的影响。如果还想加一个更直观的模型结果可以试KMeans聚类对城市区域进行“房价梯度带”划分。比如把北京各区按单价、楼龄、面积聚类成3到4档看哪些区域被划到同一档位这种“数据自动分组”的结果往往比人工划分更有说服力。4.4 可视化图表的设计与呈现这一节内容很重要因为毕设答辩的观感很大程度上取决于图表质量。我的建议是区分两种图静态图Matplotlib/Seaborn用于论文正文交互图Pyecharts用于答辩演示。Pyecharts画城市房价地图是最直观的展示方式。以广州为例from pyecharts.charts import Map from pyecharts import options as opts district_price df[df[city] 广州].groupby(district)[unit_price].median() c ( Map() .add(广州各区均价, [list(z) for z in zip(district_price.index, district_price.values)], 广州) .set_global_opts( title_optsopts.TitleOpts(title广州各区二手房价分布), visualmap_optsopts.VisualMapOpts(min20000, max100000, is_piecewiseTrue), ) ) c.render(guangzhou_price_map.html)这里的color区间要选好我用的是is_piecewiseTrue分段这样地图上颜色区分更明显色阶连续模式在区域跨度大时容易一片红一片绿看不出层次。北上广三个城市都可以生成对应的地图。此外加一个三城价格分布小提琴图比箱线图更能看出多峰分布加一个面积-单价的散点图可以标出面积溢价明显的“学区房”或“老破小”。5. 常见问题与排查技巧实录5.1 爬虫阶段的典型报错与解决整个项目里我被问得最多的就是爬虫跑着跑着突然断了。我把这段时间遇到最多的问题整理成一张表现象常见原因排查与解决方案请求返回403触发反爬限制加UA、加Referer、降低请求频率检查是否多次重试同一URL页面结构解析为空列表请求到了反爬验证页而非正常列表页先打印resp.text的前500个字符看看页面内容别直接解析某几个区域一直抓不到URL参数拼接错误或该区域无该分类房源手动访问该URL确认是否有效检查城市拼音和区域编码参数CSV中文乱码编码格式不统一写入时指定encodingutf-8-sigExcel打开不乱码抓了重复记录翻页过程中重复抓了同一列表用房源详情页URL唯一标识去重按code字段做drop_duplicates()有一个很隐蔽的坑链家有些区域的URL里是区域拼音的简称比如北京“亦庄”在URL里是yizhuangkaifaqu和你从页面上理解的“亦庄”不完全对应。所以我在遍历区域列表时先手动打开平台首页的区域选择器把每个区域的URL前缀记录下来再统一拼接切不要凭直觉去拼拼音。5.2 清洗阶段的数据陷阱清洗阶段最容易出问题的是单价和总价之间的勾稽关系。正常情况下单价 ≈ 总价 / 面积。但实际数据里同一套房源的单价和总价有时并非严格对应可能因为四舍五入或挂牌信息更新延迟。我在清洗时加了这样一步校验df[calc_unit_price] df[total_price] * 10000 / df[area] df[unit_price_diff_ratio] (df[unit_price] - df[calc_unit_price]) / df[calc_unit_price] outliers df[df[unit_price_diff_ratio].abs() 0.1]差异超过10%的记录我统一标记为异常按缺失值处理逻辑删除。这个细节在论文里写出来体现的是你对数据质量的把控能力——比任何花哨的算法都更能让导师信服。另一个很容易忽略的问题是同一小区名字存在变体。比如“珠江帝景”“珠江帝景苑”“珠江帝景(东区)”其实可能是同一小区但从文本上看是三个不同名字。如果后续要做小区维度分析建议先用相似度聚类做一轮名称归并或者直接放弃小区粒度以行政区为最小粒度。5.3 分析建模阶段的误区简单说一下建模阶段的常见误区。第一拿全部变量一股脑丢进线性回归不做共线性检查第二数据集不划分训练测试集直接全量训练然后报告R方第三为了论文好看调参把测试集分数拟合到极限这在答辩时一问就露馅。我的建议是把模型部分定位为“辅助分析工具”而不是“项目核心亮点”。论文核心亮点应该是数据采集的完整可靠 多维度的探索分析 支撑结论的合理解读。模型输出的特征重要性排序和预测误差只是给你关于“哪些因素影响房价权重更大”这个结论做量化背书。6. 写在最后少走弯路的几点经验整个项目从0到1跑通我最想说的经验就是别让代码的复杂度成为项目的负担也别让“炫技”冲淡分析本身的价值。数据抓得到、洗得干净、结论讲得通比模型多加两层神经网络重要得多。实际操作中还有一个容易被忽视的小技巧从一开始就给所有代码文件加上清晰的注释和阶段划分标记。比如01_scrapy.py、02_clean.py、03_analysis.ipynb、04_visualize.py论文写到每一个章节的时候对应打开相应的文件修改和补充都非常省力。否则到后期你看着一团乱麻的代码想复用一段函数都找不到在哪。如果你打算在这个题目的基础上做进一步扩展可以往两个方向走一是加入时间维度做近三到五年的房价走势分析这需要采集历史挂牌数据或借助公开数据补充二是引入租金数据做租售比分析回答“买房还是租房划算”的问题。这两个方向都是当前热度较高且数据相对好获取的切入点也都有足够的分析深度来支撑一篇优秀毕业论文。最后再分享一个答辩演示的小细节把Pyecharts生成的交互地图做成HTML文件存在本地答辩时用浏览器打开现场点几个区域看数据。这个演示效果比念PPT上的静态图片好得多而且基本零成本实现。我当时就是靠这个交互地图把导师对“题目太大”的质疑直接扭转成了“数据挺扎实”的评价。