尧图精选

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

🕒 发布时间:2026/10/1 23:27:47 📁 来源:尧图网络
简介这是一套面向房地产研究者、数据分析学习者与购房决策人群的西安房价分析工具源码采用Python与MATLAB混合编程实现。Python负责网络爬虫与数据预处理MATLAB承担数值计算、可视化与预测建模覆盖新房、二手房、租赁三类房源可按区域、户型、建筑年代、装修状态、朝向等多维度展开分析并借助随机森林、GBR、SVR等模型完成房价预测。资源包共49个文件约49.41MB包含29个CSV数据文件、7个Python脚本、6个MATLAB脚本、3张PNG图表及字典与说明文档目录按爬取、处理、预测、模型结果分层组织结构清晰便于二次开发。目前已有293人学习下载。读者可据此掌握从数据采集、清洗到建模预测的完整流程理解两种语言优势互补的工程实践并参考现成脚本快速复现分析结果适合作为课程设计、毕业项目或数据分析练手案例。1. 西安房价数据怎么抓一份 PythonMATLAB 混合分析源码能跑出什么西安的房价数据散在贝壳、安居客、链家这些平台上单看一个小区、一个月的挂牌价说明不了任何问题。真正做分析的人需要的是把新房、二手房、租赁三类房源按区域、户型、朝向、装修、楼层、建筑年代、挂牌时间等维度拆开再拿去做回归和预测。这份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(pagepage) resp requests.get(url, headersHEADERS, timeout10) 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, headersHEADERS, timeout10) soup BeautifulSoup(resp.text, html.parser) record {} record[title] soup.select_one(.sellDetailHeader .main).get_text(stripTrue) record[total_price] soup.select_one(.price .total).get_text(stripTrue) record[unit_price] soup.select_one(.unitPrice span).get_text(stripTrue) # 区域、户型、朝向、装修、楼层、年代等字段按实际页面结构补充 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, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesall_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)是翻页间隔这两个值不要调太小否则很容易被限流。encodingutf-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, encodingutf-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, indexFalse, encodingutf-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间隔调大到 35 秒加随机抖动必要时轮换 User-Agent。更稳妥的做法是分时段跑不要一次性抓几千页。5.2 CSV 用 Excel 打开乱码现象Python 写出的 CSV 在 Excel 里中文显示为乱码。原因Excel 默认按 GBK 解码而文件是 UTF-8。解决写出时用encodingutf-8-sigBOM 头会让 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 不写结论。这份源码包把验证数据单独留出来说明作者是认真跑过流程的不是只堆脚本。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →