news 2026/10/1 23:27:40

Python+MATLAB混合实战:西安房价数据爬取与预测分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+MATLAB混合实战:西安房价数据爬取与预测分析

简介:这是一套面向房地产研究者、数据分析学习者与购房决策人群的西安房价分析工具源码,采用Python与MATLAB混合编程实现。Python负责网络爬虫与数据预处理,MATLAB承担数值计算、可视化与预测建模,覆盖新房、二手房、租赁三类房源,可按区域、户型、建筑年代、装修状态、朝向等多维度展开分析,并借助随机森林、GBR、SVR等模型完成房价预测。资源包共49个文件,约49.41MB,包含29个CSV数据文件、7个Python脚本、6个MATLAB脚本、3张PNG图表及字典与说明文档,目录按爬取、处理、预测、模型结果分层组织,结构清晰便于二次开发。目前已有293人学习下载。读者可据此掌握从数据采集、清洗到建模预测的完整流程,理解两种语言优势互补的工程实践,并参考现成脚本快速复现分析结果,适合作为课程设计、毕业项目或数据分析练手案例。

1. 西安房价数据怎么抓:一份 Python+MATLAB 混合分析源码能跑出什么

西安的房价数据,散在贝壳、安居客、链家这些平台上,单看一个小区、一个月的挂牌价,说明不了任何问题。真正做分析的人需要的是:把新房、二手房、租赁三类房源按区域、户型、朝向、装修、楼层、建筑年代、挂牌时间等维度拆开,再拿去做回归和预测。这份house_crawler源码包,就是围绕这个需求搭起来的一套完整流程——Python 负责爬取和清洗,MATLAB 负责建模和出图,中间用 CSV 文件做数据交换。

它适合三类人:一是想学 Python 爬虫但不想只爬豆瓣 Top250 的,二是做房地产研究或中介后台分析、需要一套可改可扩的数据管线的,三是正在学 MATLAB 统计与机器学习工具箱、想找个真实数据集练手的。源码包一共 49 个文件,其中 29 个 CSV 数据文件、7 个 Python 脚本、6 个 MATLAB 脚本、3 张 PNG 图表和 1 个 readme。下面按「数据怎么来 → 怎么清洗 → 怎么建模 → 坑在哪」的顺序拆开讲。

2. 爬虫脚本拆解:rented_house 与 old_house 两条抓取链路

2.1 三类房源对应三个爬虫入口

源码包里 Python 脚本的命名很直白,rented_house_info_crawler.py抓租赁,old_house_info_crawler.py抓二手房,new_house_info_crawler.py抓新房。三个脚本结构相似,核心逻辑都是「构造列表页 URL → 解析详情页字段 → 写入 CSV」。以二手房为例,抓取字段包括区域、面积、户型、朝向、装修状态、楼层、建筑年代、挂牌中介、挂牌时间。这些字段不是随便定的,它们直接对应后面 MATLAB 建模时的自变量。

我一般会先跑租赁那条链路,因为租赁数据字段少、页面结构简单,适合验证解析逻辑是否跑通。跑通之后再改二手房和新房,避免一上来就被反爬拦住、分不清是代码问题还是网络问题。

2.2 列表页翻页与详情页字段提取

爬虫的主体逻辑分两层:列表页负责拿到详情页链接和翻页,详情页负责提取具体字段。下面这段是二手房爬虫的骨架,实际脚本里字段名和选择器会因目标站点不同而调整,但结构一致:

import requests from bs4 import BeautifulSoup import csv import time BASE_URL = "https://example.com/ershoufang/xian/pg{page}/" 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 parse_list_page(page): """解析列表页,返回当前页所有详情页链接""" url = BASE_URL.format(page=page) resp = requests.get(url, headers=HEADERS, timeout=10) soup = BeautifulSoup(resp.text, "html.parser") links = [] for item in soup.select(".sellListContent li .title a"): href = item.get("href") if href: links.append(href) return links def parse_detail_page(url): """解析详情页,提取单个房源字段""" resp = requests.get(url, headers=HEADERS, timeout=10) soup = BeautifulSoup(resp.text, "html.parser") record = {} record["title"] = soup.select_one(".sellDetailHeader .main").get_text(strip=True) record["total_price"] = soup.select_one(".price .total").get_text(strip=True) record["unit_price"] = soup.select_one(".unitPrice span").get_text(strip=True) # 区域、户型、朝向、装修、楼层、年代等字段按实际页面结构补充 return record def crawl(start_page, end_page, out_csv): """主流程:翻页抓取并写入 CSV""" all_records = [] for page in range(start_page, end_page + 1): links = parse_list_page(page) for link in links: try: all_records.append(parse_detail_page(link)) except Exception as e: print(f"解析失败 {link}: {e}") time.sleep(1) # 控制请求频率,避免触发风控 time.sleep(2) with open(out_csv, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=all_records[0].keys()) writer.writeheader() writer.writerows(all_records) if __name__ == "__main__": crawl(1, 50, "old_house_info_by_area.csv")

这段代码里几个参数值得说清楚。time.sleep(1)是详情页之间的间隔,time.sleep(2)是翻页间隔,这两个值不要调太小,否则很容易被限流。encoding="utf-8-sig"是为了让 Excel 打开 CSV 时不乱码,Windows 上做数据分析的人应该都踩过这个坑。fieldnames取自第一条记录,所以如果某条记录缺字段,写出来的 CSV 列会对不齐——稳妥做法是预先定义好字段列表。

2.3 抓下来的 CSV 长什么样

源码包里 CSV 文件的命名本身就是一份数据字典。old_house_info_by_area.csv是按区域聚合的二手房数据,old_house_info_by_face_direction.csv是按朝向,old_house_info_by_decoration_status.csv是按装修状态,old_house_info_by_build_time.csv是按建筑年代,old_house_info_by_property_agency.csv是按中介机构,old_house_info_by_storey.csv是按楼层。租赁那边有rented_house_info_by_rent.csv和rented_house_info_by_house_type.csv。新房有house_info_by_building_type.csv、house_info_by_sale_status.csv、house_info_by_open_time.csv等。

这种「一个维度一个文件」的组织方式,好处是 MATLAB 那边读起来直接对应分析目标,坏处是文件多、容易搞混。我一般会在data_processing.py里统一做一次合并,生成house_info_data_final.csv作为建模输入。

3. 数据清洗与预处理:data_processing.py 里该做什么

3.1 缺失值、异常值与单位统一

爬下来的原始数据几乎不可能直接进模型。常见问题有三类:价格字段带「万」「元/平米」等单位,需要剥离成数值;面积字段可能写成「89.5平米」或「89.5㎡」,要统一;楼层、年代这类字段经常出现「暂无数据」或空字符串。data_processing.py的职责就是把这些处理干净。

处理策略上,价格和面积用正则提取数字,缺失值按字段重要性分别处理——建模关键字段(单价、面积、区域)缺失的直接丢弃,非关键字段(装修、朝向)缺失的填「未知」。下面是一个可复用的清洗片段:

import pandas as pd import re def clean_price(val): """把 '125万' '89元/平米' 这类字符串转成浮点数""" if pd.isna(val): return None num = re.findall(r"[\d.]+", str(val)) return float(num[0]) if num else None def clean_area(val): """统一面积单位,去掉 '平米' '㎡' 等后缀""" if pd.isna(val): return None num = re.findall(r"[\d.]+", str(val)) return float(num[0]) if num else None def preprocess(input_csv, output_csv): df = pd.read_csv(input_csv, encoding="utf-8-sig") df["total_price"] = df["total_price"].apply(clean_price) df["area"] = df["area"].apply(clean_area) # 丢弃关键字段缺失的行 df = df.dropna(subset=["total_price", "area"]) # 非关键字段填默认值 df["decoration"] = df["decoration"].fillna("未知") df["face_direction"] = df["face_direction"].fillna("未知") # 计算单价,作为后续建模的目标变量之一 df["unit_price"] = df["total_price"] * 10000 / df["area"] df.to_csv(output_csv, index=False, encoding="utf-8-sig") return df if __name__ == "__main__": preprocess("old_house_info_by_area.csv", "house_info_data_final.csv")

clean_price和clean_area都用正则提取第一个数字,这对大多数中文房源标题和字段是够用的。unit_price的计算假设total_price单位是万元、area单位是平方米,如果你的数据源单位不同,这个系数要改。dropna的 subset 只保留建模必需字段,避免因为一个无关字段缺失丢掉整条记录。

3.2 类别字段编码与建模输入准备

MATLAB 的回归模型对类别变量支持不如 Python 的 sklearn 直接,所以类别字段(区域、朝向、装修、楼层区间)在 Python 阶段就要做编码。常见做法是 one-hot 编码,或者按目标变量均值做 target encoding。源码包里house_price_prediction_by_factors.py和area_house_price_prediction.py就是干这个的。

我一般会保留一份原始类别列和一份编码后的数值列,前者用于出图时做标签,后者用于喂模型。这样在 MATLAB 里画「各区域均价对比」时不用再回头查映射表。

提示:CSV 文件用utf-8-sig编码写出,MATLAB 的readtable默认按系统编码读,Windows 上可能乱码。稳妥做法是在 MATLAB 里显式指定'Encoding', 'UTF-8'。

4. MATLAB 建模与预测:随机森林、GBR、SVR 三条路线怎么选

4.1 三种模型的适用场景与参数入口

源码包里house_price_prediction目录下有random_forest_result、gbr_result、svr_result三组结果,对应随机森林、梯度提升回归、支持向量回归三种模型。models目录存放训练好的模型文件,prediction_results存放预测输出,images存放 3 张 PNG 图表。

选型上,随机森林对特征尺度不敏感、能输出特征重要性,适合做第一版基线;GBR 在中小数据集上通常精度更高,但对异常值敏感;SVR 适合样本量不大、特征维度适中的场景,核函数和惩罚系数 C 需要调。源码包把三条路线都跑了一遍,方便对比。

MATLAB 里读数据、划分训练测试集、训练随机森林的骨架大致如下:

% 读取清洗后的建模数据 opts = detectImportOptions('house_info_data_final.csv', 'Encoding', 'UTF-8'); opts.VariableNamingRule = 'preserve'; data = readtable('house_info_data_final.csv', opts); % 选取特征列和目标列 features = data(:, {'area', 'room_num', 'hall_num', 'build_year', 'unit_price'}); target = data.total_price; % 划分训练集和测试集,固定随机种子保证可复现 rng(42); cv = cvpartition(height(data), 'HoldOut', 0.2); X_train = features(training(cv), :); y_train = target(training(cv), :); X_test = features(test(cv), :); y_test = target(test(cv), :); % 训练随机森林回归模型 rf_model = fitrensemble(X_train, y_train, 'Method', 'Bag', ... 'NumLearningCycles', 200, 'Learners', 'Tree'); % 预测并评估 y_pred = predict(rf_model, X_test); rmse = sqrt(mean((y_test - y_pred).^2)); fprintf('随机森林 RMSE: %.2f\n', rmse); % 保存模型 save('models/random_forest_model.mat', 'rf_model');

rng(42)固定随机种子,保证每次划分一致,否则调参时结果波动会让你怀疑人生。NumLearningCycles是树的数量,200 是常见起点,数据量大可以加到 500。Method设为'Bag'是 bagging 集成,对应随机森林;如果换成'LSBoost'就是 GBR 路线。Learners设为'Tree'表示基学习器是决策树。

4.2 按区域和按因素两条预测链路

源码包里预测脚本分两条:area_house_price_prediction.py按区域做预测,house_price_prediction_by_factors.py按多因素做预测。前者适合回答「曲江新区下季度均价大概多少」,后者适合回答「同样 90 平米、朝南、精装,在雁塔和高新差多少」。

按区域预测时,通常先把数据按district分组,每组单独训练一个模型,或者把区域作为类别特征加入全局模型。前者样本量小、容易过拟合,后者需要足够的区域样本。我一般先看各区域样本量,少于 100 条的区域合并成「其他」,再统一建模。

2020_ground_truth.csv和ground_truth.csv是留出来做验证的真实数据,用预测结果和它对一下,能直观看出模型在高价区域是否系统性偏低。这个对比步骤不要省,否则模型上线后偏差会很难解释。

4.3 结果文件与图表怎么读

house_price_of_different_districts.csv是各区域房价汇总,prediction_results目录下是模型预测输出,images里 3 张 PNG 分别是趋势图、对比图和特征重要性图。读这些结果时重点看两件事:一是高价区域(高新、曲江)的预测误差是否明显大于低价区域,二是特征重要性里area和build_year的排序是否符合常识。如果build_year重要性异常高,可能是数据里年代字段和价格存在伪相关,需要检查样本分布。

5. 避坑与常见问题:爬虫、编码、模型三条线上的翻车记录

5.1 爬虫被限流,返回空列表或验证码页

现象:跑了几十页之后,parse_list_page返回空列表,或者resp.text里出现验证码页面。原因:请求频率过高,触发了目标站点的风控。解决:把time.sleep间隔调大到 3~5 秒,加随机抖动,必要时轮换 User-Agent。更稳妥的做法是分时段跑,不要一次性抓几千页。

5.2 CSV 用 Excel 打开乱码

现象:Python 写出的 CSV 在 Excel 里中文显示为乱码。原因:Excel 默认按 GBK 解码,而文件是 UTF-8。解决:写出时用encoding="utf-8-sig",BOM 头会让 Excel 正确识别编码。MATLAB 读取时显式指定'Encoding', 'UTF-8'。

5.3 MATLAB 读取 CSV 后数值列变成 cell 数组

现象:readtable读进来后,价格列是 cell 而不是 double,无法直接参与运算。原因:CSV 里该列混有非数值字符(如「暂无」),MATLAB 自动降级为 cell。解决:在 Python 清洗阶段就把非数值行处理掉,或者 MATLAB 里用str2double逐列转换。根本办法是保证house_info_data_final.csv里建模字段全是纯数值。

5.4 随机森林 RMSE 忽高忽低

现象:每次跑模型 RMSE 差很多。原因:没有固定随机种子,训练测试集划分和树的自助采样都在变。解决:rng(42)固定种子,cvpartition也受种子控制。调参对比时尤其要固定,否则无法判断是参数起作用还是随机波动。

5.5 预测结果在高价区域系统性偏低

现象:模型对高新、曲江的预测价普遍低于实际。原因:高价样本少,模型被低价样本主导,回归向均值收缩。解决:对目标变量取对数后再建模,或者按区域分层建模,也可以给高价样本加权。验证时用ground_truth.csv对照,别只看整体 RMSE。

6. 进阶用法:把 2020_ground_truth.csv 用成回归验证的标尺

2020_ground_truth.csv这份文件容易被忽略,但它其实是整个项目里最有价值的验证工具。做法是:用爬取的历史数据训练模型,预测 2020 年的价格,再和 ground truth 逐区域对比,算 MAPE(平均绝对百分比误差)。这比单纯看 RMSE 更能说明模型在真实场景下的可用性。

具体操作上,我会在 MATLAB 里加一段对比脚本:

% 读取预测结果和真实值 pred = readtable('prediction_results/area_prediction.csv', opts); truth = readtable('2020_ground_truth.csv', opts); % 按区域对齐后计算 MAPE joined = innerjoin(pred, truth, 'Keys', 'district'); mape = mean(abs(joined.predicted_price - joined.actual_price) ./ joined.actual_price) * 100; fprintf('各区域 MAPE: %.2f%%\n', mape); % 找出偏差最大的三个区域 [~, idx] = sort(abs(joined.predicted_price - joined.actual_price) ./ joined.actual_price, 'descend'); worst = joined(idx(1:3), :); disp(worst(:, {'district', 'predicted_price', 'actual_price'}));

innerjoin按区域对齐两份数据,mape算的是百分比误差,比 RMSE 更直观。sort那几行是找出偏差最大的区域,通常会是样本量少或者价格波动大的区。找到之后回头检查该区域的样本量和特征分布,往往能发现数据问题。

从那以后我每次做完预测,都强制走一遍 ground truth 对比,不看 MAPE 不写结论。这份源码包把验证数据单独留出来,说明作者是认真跑过流程的,不是只堆脚本。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 23:26:51

PyTorch + AMD ROCm:零修改迁移实战指南

1. 这不是“换显卡”,而是PyTorch开发工作流的静默升级很多AI开发者还在为NVIDIA显卡价格发愁,一边盯着3090二手价跳水,一边在Colab里抢T4配额;另一边,手头那张7900 XTX静静躺在机箱里,被当成“游戏卡”闲置…

作者头像 李华
网站建设 2026/10/1 23:25:54

骑士加油商业需求文档落地指南:从PPT到技术方案与MVP验证

简介:这份《骑士加油商业需求文档》PPT面向互联网油站赛道的创业者、产品经理与商业分析学习者,系统梳理了智慧油站解决方案的完整商业逻辑。内容围绕市场分析、商业模式、产品规划、收益与成本、风险及对策五大模块展开,涵盖石油行业3万亿年…

作者头像 李华
网站建设 2026/10/1 23:23:54

Wiki长知识,RAG找答案:团队知识库精准问答实践

团队内部的 Wiki 已经写了三百多篇,覆盖了从技术方案、故障复盘到新人手册的所有内容。但每次有人问起“之前那个服务到底是怎么搭的”,最先回答的往往不是文档本身,而是“你去 Wiki 搜一下”。然后呢?要么搜不到,要么…

作者头像 李华
网站建设 2026/10/1 23:23:27

异步加载与前端性能优化:从关键渲染路径到工程落地

页面白屏了整整四秒,用户在群里直接开骂,这是三年前我刚接手一个中型电商项目时的真实状态。后来花了两周时间把整个前端加载链路重做了一遍,FCP从3.2秒压到1.4秒,LCP从5.8秒压到2.1秒,核心手段就是异步加载与性能优化…

作者头像 李华
网站建设 2026/10/1 23:21:01

Simulink AUTOSAR冗余类型顽固生成:根因分析与清理指南

做 AUTOSAR 适配的工程师一定都有过这种经历:Simulink 模型里信号、Bus 对象都删干净了,但生成完代码一打开Rte_Type.h,里面仍然顽固地保留着几个早已不用的结构体 typedef。我前阵子就遇到一个典型的Simulink AUTOSAR 冗余数据类型顽固生成问…

作者头像 李华
网站建设 2026/10/1 23:19:36

基于SpringBoot+Vue3的汽车租赁管理系统设计与实现

做这套汽车租赁管理系统的时候,我其实是拿它当“新手到进阶”的过渡项目来打磨的。团队里几个想走 Java 后端路线的年轻人,需要的是能完整走通“数据库设计→后端接口→前端页面→部署上线”全流程的项目,但又不想用那些烂大街的电商秒杀系统…

作者头像 李华