尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python商品房数据采集预测系统:爬虫+机器学习+Flask实战

Python商品房数据采集预测系统:爬虫+机器学习+Flask实战 又是一年毕业设计季我后台已经收到不少同学问同一个问题毕设到底选什么题才能既容易过审、又能在答辩时讲出东西来。我给的建议通常很朴素——把一个常见业务场景完整跑通比跟风追新概念更稳。比如这套《Python商品房数据采集预测系统》单看名字不算花哨可当你把Requests爬虫、数据分析、Scikit-learn机器学习、Flask可视化这条链路从头到尾走一遍后你会发现它其实已经覆盖了计算机专业毕业设计最拿分的几个环节工程能力、算法落地、软件工程的完整闭环。这套系统的定位很清晰抓取真实的商品房挂牌数据清洗整理后做特征分析再用机器学习模型对房价做出预测最后通过Flask搭建Web界面把结果可视化展示出来。它能解决的问题也很实在——对购房者来说是“这个区域的房子大概值多少钱”的参考对学生来说是“我有没有能力独立完成一个可供答辩演示的数据类项目”。这篇文章会把选题思路、系统架构、关键代码、踩坑实录全部拆开讲一遍无论你是零基础起步还是已经学过Python想做点综合项目都可以直接照着搭。1. 选题思路与系统架构怎么定1.1 为什么说“房价预测”是毕设的稳牌很多同学选题时容易犯一个毛病就是把题目定得又大又空比如“基于人工智能的智慧城市系统”听上去很唬人可落实到代码上根本不知道第一步该写什么。房价预测这个题目好就好在它业务边界清晰、数据容易获取、技术栈也完全能对上课程要求。从课程考核角度看数据结构课讲了列表和字典数据库课讲了SQL机器学习课讲了回归和集成学习Web开发课讲了MVC——这套系统正好把这些知识点全部串起来。从答辩角度看评委最关心的是“你有没有真的动手做”而这类数据采集预测系统天然能看到实物有爬虫抓取的数据、有可视化的图表、有预测结果的误差评估每一环都可以现场演示比空谈理论强得多。还有一个特别实际的好处房价数据每天都在更新你做的不是死数据你可以随时重新爬取、重新训练答辩时就算评委问“如果数据换成别的城市怎么办”你也可以直接现场换参数演示。这种“活”的系统很加分。1.2 技术栈选型背后的取舍技术选型是我想重点聊的部分。很多初学者会问“为什么不用Scrapy为什么不用Django为什么不用TensorFlow”这些问题背后其实是对工具边界不理解。以毕设场景来看选择标准只有三条你讲得清楚原理、你驾驭得住代码、你三天内能调通。爬虫层我用的是Requests而不是Scrapy。原因很直接Scrapy虽然性能更高、扩展性更强但它的异步框架和Item Pipeline机制对新手来说太抽象了。Requests配合BeautifulSoup解析HTML代码是直线式的读起来人挑人答辩时解释起来也顺。Web框架选了Flask而不是Django。Django自带的Admin后台、ORM和中间件体系很强大但一个项目里往往有大量你并不熟悉的“自动配置”出了问题你很难排查。Flask的请求-响应模型非常直观可以一个路由一页代码、一个函数一个接口地讲清楚部署到云服务器还很简单。机器学习部分用Scikit-learn而不是深度学习框架这是因为房价预测是表格型数据的回归任务随机森林或梯度提升这类传统模型在中小样本上的表现和可解释性都要优于神经网络。而且Sklearn接口统一fit和predict就是两个核心调用答辩时不会卡壳。在前端方面ECharts几乎是可视化场景的最优解图表类型全配置项文档友好动态交互效果也很容易出彩不需要额外学习React或Vue那一套工具链。2. 系统核心功能模块拆解2.1 数据采集模块先理解网页再写爬虫我以为写爬虫最大的坎不是代码本身而是你怎么看待“页面数据”。很多同学一上来就翻审查元素里的XHR试图找接口、拼参数结果绕了一大圈什么都抓到。更稳妥的方式是先用最简单的方式把网页抓下来看看结构再说。以链家、安居客这类房产信息站为例数据通常有两种存在方式一种是服务端渲染的HTML数据直接嵌在网页标签里另一种是前端异步加载数据藏在XHR接口返回的JSON里。判断方式很简单你用Requests抓一下页面源码搜索楼盘名称、面积、单价这些关键词如果搜得到就直接用BeautifulSoup解析搜不到再去看Network面板里的接口。具体到系统里爬虫模块我拆成了四个小步骤构造请求带上合适的请求头、发送GET请求拿到HTML、用BeautifulSoup定位标签提取字段、把结构化结果写入数据库。这四步逻辑清晰、职责单一写代码时好调试写毕业设计文档时也好拆章节。这里要特别提醒抓取公开数据时一定要控制频率和规模把请求间隔设在1到3秒不要对目标服务带来压力不要绕过登录和访问控制也不要用抓下来数据做商业用途。如果目标网站明确不允许爬虫就换一个公开的数据源来练习。2.2 数据清洗模块脏数据才是真正的敌人我在第一次做这个项目时也天真地以为能把数据爬下来就万事大吉了。结果一看数据表挂牌标题里有各种无用的“急售”“捡漏”面积字段有些是“88平米”有些是“88㎡”单价还有缺省值楼层字段格式混乱。清洗是整个项目里最无聊又最关键的一环。用Pandas做得很顺先去除重复记录再把字符串类型的面积用正则提取数字部分并转成浮点数单价字段也做同样处理最后用中位数填补缺失值少的列。对于诸如朝向、装修这样的分类特征则用pd.get_dummies做OneHot编码让机器学习模型能够处理。很多同学会在这一步偷懒直接把原始数据扔给模型训练结果发现R2只有零点几却找不到原因。后来逐一检查特征才发现总价字段单位不统一从“万”变成“万元”导致模型上下颠倒。数据质量决定了模型性能的上限算法只是逼近这个上限这句话我自己做项目时感受很深。2.3 预测模块模型选择与特征工程的关系房价预测本质上是一个回归任务目标值是一个连续变量——每平方米单价。在Scikit-learn里面最常用的两个模型是线性回归和随机森林回归。线性回归的优势是可解释性极强它给出来的系数可以直接说明“面积每增加一平房价大约上涨多少”随机森林的优势则是能够捕获非线性关系比如同一个区域里小户型单价明显高于大户型这种情况。我建议毕设里至少训练两个模型做对比这不只是为了让论文内容好看更重要的是让你真正理解不同算法的差异。参数方面随机森林里最值得做的是调n_estimators树的数量和max_depth树的最大深度这两个参数不宜过大建议在50到200之间搜索过大反而会过拟合。用train_test_split把数据划分成80%训练集和20%测试集再用R2、MAE两个指标评估模型效果。特征工程的细节也影响很大。户型里“3室2厅”这种字符串不能直接进模型要拆成“室数”和“厅数”两个数值特征区域要用经纬度或行政区划编码表示房龄越老、楼层越高的房屋对单价的影响往往都不是线性的这些放到模型里跑一跑才知道。2.4 展示模块Flask让整个系统有了“脸面”命令行里打印一堆预测数字没人觉得你做的是个系统。Flask展示模块存在的意义就是让你的项目变成一个“打开浏览器就能操作”的产品。我的做法是用Flask提供三个核心路由首页展示房源总览和区域均价柱状图分析页展示面积/单价散点图和户型分布饼图预测页提供一个表单用户输入面积、户型、楼层、朝向等特征点击提交后后台调用sklearn模型返回预测价格。这些图表全部用ECharts渲染Flask这边只负责把数据组装成JSON格式返回给前端。这种“前后端数据分离”的做法你在本科阶段可能不觉得有多高级但它确实是以后工作中最常用的模式。而且答辩时你可以现场演示“我输入一个100平方米的3室2厅系统预测出单价是2.3万元/平”这种演示的冲击力比读PPT强得多。3. 实操落地手把手复现关键代码3.1 Flask项目骨架怎么搭项目结构建议按功能模块划分这样后期维护和写文档都省心house_price_system/ ├── app.py # Flask入口 ├── config.py # 配置文件 ├── models/ # 数据模型与数据库定义 ├── spiders/ # 爬虫模块 ├── analysis/ # 数据清洗与特征工程 ├── ml_models/ # 机器学习训练与预测 ├── static/ # 前端静态文件js/css └── templates/ # HTML模板先创建虚拟环境再安装依赖这是很多新手最容易跳过的一步直接在全局环境pip install等到项目多了依赖就会互相冲突。我的习惯是# Python 3.8以上版本均可 pip install flask requests beautifulsoup4 pandas scikit-learn安装完成后写一个最简单的app.py先跑通Flask的hello world再往里面加功能。宁可把基础流程拆成最小可用版本也不要一把梭直接写几万行。3.2 Requests爬虫核心配置与代码写爬虫第一件事是准备一个“像正常人”的请求头。很多网站反爬的第一道防线就是检查User-Agent如果请求头写着Python-requests直接被拦下来毫不奇怪。我整理了一个请求头模板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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, }把住房页面的HTML抓下来后重点是用BeautifulSoup解析列表项。这里有一个很实用的技巧先用开发者工具检查元素定位到房源的列表容器找到一个小区块的HTML作为样例再编写解析逻辑。链家这类网站的房源卡片里标题、面积、单价、位置都在不同的class层级提取时要先用select定位外层再逐层找字段import requests from bs4 import BeautifulSoup url https://example.com/house/sale/ response requests.get(url, headersheaders, timeout10) response.encoding utf-8 soup BeautifulSoup(response.text, html.parser) house_list soup.select(.houseList .houseItem) for item in house_list[:20]: title item.select_one(.title a).text.strip() if item.select_one(.title a) else area item.select_one(.area).text.strip() if item.select_one(.area) else price item.select_one(.totalPrice).text.strip() if item.select_one(.totalPrice) else print(title, area, price)这个示例中我用了虚拟的选择器名称实际编码时你要根据自己选定的数据源来替换。一手数据抓下来后你会发现title里混着“急售”“满五唯一”等各种杂质area字段写着“98平米”而不是数字这正好对接下一阶段的清洗。爬虫里面有个细节一定要给请求设置Timeout。我一开始没设置某天目标网站响应慢了进程就一直挂着最后整个爬虫像死了一样。加一个timeout10再结合retry机制稳得多。3.3 数据分析与特征工程代码数据清洗阶段的工作量主要集中在Pandas操作上。我写了一个clean_data函数把字符串清理和类型转换集中处理import pandas as pd import re def clean_data(df): df df.drop_duplicates(subset[title, area, price]) df[area] df[area].apply(lambda x: float(re.search(r(\d(\.\d)?), str(x)).group(1)) if re.search(r(\d(\.\d)?), str(x)) else None) df[price] df[price].apply(lambda x: float(re.search(r(\d(\.\d)?), str(x)).group(1)) if re.search(r(\d(\.\d)?), str(x)) else None) df[unit_price] df[price] / df[area] df df.copy() df[floor_level] df[floor].apply(lambda x: high if 高 in str(x) else (low if 低 in str(x) else mid)) df pd.get_dummies(df, columns[floor_level, orientation, decoration]) numeric_cols df.select_dtypes(include[float64, int64]).columns for col in numeric_cols: df[col] df[col].fillna(df[col].median()) return df这个函数逻辑并不复杂但它体现了一个很重要的经验数据清洗没有标准答案你需要先看数据长什么样再写对应的处理规则。每一步清洗都应该有依据这也是毕业设计文档里最好写出来的内容因为你可以把“原始数据长什么样、清洗规则是什么、清洗后的结果差异在哪儿”全部对比展示。另外做特征工程时一定要保留一份原始数据备份。我吃过一次亏清洗处理过了头把楼层字段直接丢掉了后面想重新做特征分析只能重跑爬虫白白浪费了一个多小时。清洗代码和数据备份分开管理后面会省很多功夫。3.4 Scikit-learn训练与预测训练模型的核心代码并不长难的是你要理解每一步在干什么。先把特征列和目标列划分开特征列用X表示目标列unit_price单平方米单价用y表示from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import r2_score, mean_absolute_error features [col for col in df.columns if col not in [title, price, unit_price]] X df[features] y df[unit_price] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) model RandomForestRegressor(n_estimators100, max_depth10, random_state42) model.fit(X_train, y_train) y_pred model.predict(X_test) r2 r2_score(y_test, y_pred) mae mean_absolute_error(y_test, y_pred) print(fR2: {r2:.3f}, MAE: {mae:.3f})我在跑这个模型时一个明显的体验是random_state这个参数千万别小看。如果你不固定它每次运行得到的结果都不一样答辩时评委让你重新跑一遍出来一个完全不同的评估结果场面会很尴尬。Random_state设置为42不仅为了可复现也是给自己留一条“按固定结果讲方案”的后路。还有一点n_estimators并不是“越大越好”。我试过把它从100调到500模型训练时间翻了将近三倍但R2几乎没有提升甚至还有轻微下降。树的数量够用就行这个指标在回归问题上的边际收益递减得非常快。3.5 Flask路由与可视化接口Flask后端和机器学习模块之间需要做一个接口对接——当用户在页面表单里输入参数后后端程序要构造一条特征向量调用模型做预测再把结果返回给前端展示。这个环节要特别注意训练时特征列的顺序和预测时输入特征列的顺序必须完全一致。否则会报特征名对齐错误或者更隐蔽地预测结果全部错乱。from flask import Flask, render_template, request, jsonify import joblib app Flask(__name__) model joblib.load(model.pkl) app.route(/) def index(): return render_template(index.html) app.route(/predict, methods[POST]) def predict(): data request.get_json() area float(data[area]) rooms int(data[rooms]) input_df create_input_df(area, rooms) # 构造与训练一致的特征向量 result model.predict(input_df)[0] return jsonify({unit_price: round(result, 2)}) if __name__ __main__: app.run(debugTrue)这里有一个很小的关键点训练好的模型不要每次预测时重新训练用joblib.dump(model, model.pkl)把模型保存下来Flask启动时直接load进内存。这样程序的启动速度快得多也更符合实际生产部署的思路。前端ECharts部分则是从Flask提供一个数据接口开始比如使用/api/avg_price_by_district接口返回各区域的均价列表前端用axios或fetch获取后交给ECharts渲染。这种模式非常通用以后你做任何数据可视化项目都能复用。4. 实战踩坑高频问题与排查清单4.1 429 Too Many Requests爬虫最常撞的墙如果你跑爬虫时看到“exceeded retry limit, last status: 429 too many requests”这种报错说明你的请求频率太高被网站限流了。429是HTTP协议里的状态码含义是“你在短时间内发出了太多请求”。我在做这个项目的头两天就是被这个报错反复折腾。解决思路有几个层次。最低成本的做法是每次请求之间加个随机延迟比如time.sleep(random.uniform(1, 3))让访问节奏更接近人的操作行为。再进一步是加大加到2到5秒的间隔宁可多花几分钟也不要去撞防火墙。还有尽量避开目标网站的高峰期比如晚上八点到十点的访问高峰期限流更严爬取成功率更低。从根上看除非你只是抓几十条练练手否则在正式项目里大规模持续抓取某个网站前一定要确认其条款是否允许。4.2 请求头、Cookie与对端的反爬策略有时候你加好了随机延时结果还是被识别成爬虫那大概率是请求头暴露了破绽。最常见的两种情况是User-Agent要求版本太低或者缺少Referer、Accept-Language这些常规浏览器必带的头参数。我的请求头里会至少放5个字段User-Agent、Referer、Accept、Accept-Language、Connection。多研究一下别人常用的一组请求头配置比你自己瞎试要快得多。网站如果要求登录后才能看数据你就需要在Requests会话里带上登录后的Cookie信息session.get(url, headersheaders, cookiescookies)这种方式基本都能行。还要强调一下反爬手段层出不穷Header伪装只是第一步。如果对方加了验证码或滑块最好不要尝试绕过——毕业设计没有必要在这个层面死磕。换个公开数据源或者申请一个API接口都比“对抗反爬”更有价值。4.3 中文乱码一套编码问题通吃的标准操作爬虫抓回来的页面中文经常显示成乱码这是新手最容易怀疑人生的时刻。出现乱码十有八九是编码声明问题网页可能是UTF-8也可能是GBK/GB2312你的解析进程却用了另一种编码去decode。我的处理方式是先探测再解码别死记硬背某个网站用什么编码。用requests库先获取response.content再通过response.encoding判断网页声明的字符集如果还是不放心可以直接用response.encoding response.apparent_encoding强制让程序根据内容推测。另外网页里声明的是charsetutf-8但实际内容里混着GBK的大陆这种“表里不一”的情况也不少见。遇到这种混编码问题最省事的方法是抓回来以后统一用字符串的encode和decode做一次转换把脏数据丢给Pandas清洗。4.4 模型评估指标不理想怎么办如果你跑完模型发现R2只有0.3甚至更低先别急着换算法我建议按这个顺序排查特征是不是选得够全面、数据量够不够、目标列有没有异常值、模型参数太低还是太高。我第二次跑这个项目时R2从0.6涨到0.82关键就在于多加了两个特征房龄和距离商圈的距离。这说明房价预测的精度大量来自于特征工程而非算法复杂度。另一个常见的坑是数据里存在成交价与挂牌价混在一起的情况直接把这两类数据混训会让模型学不到有效的规律。处理思路是设置总价上限门槛把异常高的价格记录排除掉。如果数据量实在太少比如只有三五百条随机森林的效果也会受限。这时候可以考虑用交叉验证去用满数据集的利用效率或者在展示时侧重于“分析”而不是“精准预测”。毕设项目里结论严谨比数字漂亮更重要。5. 答辩和文档里这几个经验很加分5.1 把“过程”讲出来比背原理更有效答辩时评委最喜欢问的一类问题是“你在这个项目里遇到的最大困难是什么你是怎么解决的”如果你只是背教材上的概念很容易被追问垮掉。但如果你能讲出一个具体的故事比如在爬虫限流下怎么调参、特征工程阶段怎么发现字段不统一评委不但不会难为你反而会觉得你真的下了功夫。我建议把项目中几个具体的“原始数据示例”截图保存下来处理之后的效果也截图对比展示。数据清洗环节尤其这样做一张清洗前后的对比表信息量很可能超过你讲十分钟概念。5.2 把数据合规的经验写进文档在你的项目文档和答辩PPT里至少要留一小段讲数据采集合规性。写明抓取频率、数据用途、是否遵守robots协议。这个细节在评委眼里代表你写代码之外还有工程素养非常加分。我在实际操作时是这么处理的限定采集间隔只记录数量有限的样本并明确标注数据来源和采集时间论文里也写明“本系统抓取的数据仅用于学术研究”。这种做法既让项目经得起查也显得你考虑周全。写在最后我回头复盘这个项目时最大的体会是它真正难的不是哪个单独的环节而是把爬虫、清洗、建模、展示这四步串成一条完整的链。单独抓一个网页两小时就能写完单独训练一个随机森林模型半小时也能出结果可是中间的衔接环节最磨人——数据格式怎么统一、特征列怎么对齐、前端图表的数据结构怎么设计这些才是真正容易被低估的工作量。如果你正在准备毕业设计我强烈建议你把项目拆成阶段目标每完成一个阶段就留一次可复现的记录。不要一上来就追求“系统完整”先把爬虫抓下来再把清洗跑通再单独训练出一个能输出数字的模型最后才套上Flask和ECharts。每一步都有实实在在的产出心态会稳很多。最后再分享一个小技巧训练好的模型文件model.pkl、清洗好的数据、以及那组固定好的random_state打包压缩保存到三个不同的位置。答辩前如果你要重跑一遍模型只要有这“三件套”十分钟内就能把系统恢复到全胜状态。就这一点关键时刻能帮你避免很多无谓的焦虑。
返回列表