FEATURED · 精选文章

基于Django与爬虫的二手房数据可视化系统设计与实现

发布时间 / 2026/9/18 4:22:01
来源 / 创域科博编辑部
栏目 / 资讯中心
基于Django与爬虫的二手房数据可视化系统设计与实现 1. 项目到底做什么从毕业设计到可演示系统的需求拆解每年到这个节点总有不少人在后台问我毕业设计该选什么方向。我的建议一直很明确选题别贪大要做那种“技术栈覆盖广、代码量适中、演示效果好”的项目。今天要拆解的这套“基于Django数据可视化网络爬虫的二手房屋信息采集系统”就是非常典型的一类——它把后端框架、爬虫、数据库、前端可视化全部串起来一套代码能讲出五六个技术点答辩时老师问什么你都有东西接。1.1 这个项目解决了哪些实际问题先说人话这个系统要干的事就是自动去目标网站抓取二手房的挂牌信息比如小区名称、户型、面积、总价、单价、朝向、楼层这些字段抓完之后存进MySQL再通过Web页面把数据用图表展示出来。整个链路是“数据采集 - 数据存储 - 数据展示”非常完整。你可能觉得这不就是爬虫加个网页吗其实没那么简单。当你把爬虫和Django放在一起时会遇到几个平时单练时碰不到的问题爬虫的触发时机怎么控制是手动点按钮爬还是设置定时任务爬取的数据如何清洗、去重、格式化直接存入库里会出现大量脏数据。页面图表需要的数据格式和数据库原始表结构不一致时怎么在视图层做聚合数据量到几万条之后Django ORM查询速度变慢怎么优化这些正是毕业设计答辩时最容易被打分老师追问的细节。哪怕你只是做到了“能跑”只要把这些问题想清楚并在文档里写明白就已经拉开一大截差距。1.2 为什么是Django爬虫MySQLECharts这个组合先别急着写代码选型这件事值得说道说道。Django这个东西说它是“大而全”一点都不冤。它自带ORM、Admin后台、模板引擎、表单处理最核心的是它有一个非常清晰的MTV架构Model-Template-View非常适合用来做“信息管理系统”类的毕设。你不需要像Flask那样自己拼第三方库也不像Spring Boot那样还得先折腾Maven依赖Django装上就能跑对时间紧张的毕业生来说就是救命稻草。爬虫这边很多人纠结是学Scrapy还是用requestsBeautifulSoup。我的看法很直接毕设项目requestsBeautifulSoup就够了。Scrapy的性能优势在单机小规模采集场景下体现不出来反而会带着一大堆配置和中间件、管道、爬虫类的概念光调试就要花掉不少时间。你自己写一个简单的爬虫脚本逻辑透明答辩时老师问“这个字段你是怎么解析出来的”你可以直接回答——这就是你亲手写的代码底气完全不一样。MySQL是存数据的主力Django官方对MySQL的支持非常成熟只需要在settings里配置一下连接参数再用ORM定义模型类建表、增删改查基本不用写SQL。ECharts做可视化不用多说纯前端方案配置灵活图表交互效果也拿得出手配合Django提供的JSON接口前后端各干各的活思路清晰。一句话总结技术选型的核心逻辑尽量用你熟悉或能快速上手的技术同时保证每个环节都有“可讲的点”。这套组合恰好每一层都有话说。2. 爬虫模块把网页变成结构化数据的关键几步爬虫是整个系统的数据源头判断一个爬虫写得好不好不是看抓了多少条数据而是看三点能不能稳定抓到目标字段、能不能应对轻微的反爬、数据入库之后干不干净。这一节我把整个流程拆开讲。2.1 爬虫的完整工作流程一个常规的网页爬虫核心流程就是四步发起请求、解析HTML、提取字段、保存数据。先看发起请求。如果是requests库一个最简单的GET请求长这样import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.example.com/ } resp requests.get(https://www.example.com/ershoufang/, headersheaders, timeout10) print(resp.status_code)这里有几个容易被新手忽略的细节。第一个是headers里的User-Agent很多网站会拦截没有UA的请求直接返回403或者跳转验证页。第二个是timeout必须设置不设的话请求挂起会卡死整个采集任务。第三个是编码问题如果resp.text出现乱码先检查resp.encoding是不是被错误识别了手动指定为utf-8或gbk之后通常就正常了。拿到HTML之后进入解析阶段。BeautifulSoup配合lxml解析库是常见组合from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, lxml) # 根据页面结构调整选择器 items soup.select(div.house-item) for item in items: title item.select_one(a.title).text.strip() price item.select_one(span.price).text.strip()选择器的写法完全取决于目标站点的HTML结构。这个没法说统一套路我建议你打开浏览器按F12亲自找到存放标题、价格、户型信息的标签再去写select或select_one。有个新手常犯的错误用id选择器去定位列表页的重复元素id在HTML里是唯一的列表项应该优先用class或者标签层级关系来定位。2.2 提取字段的细节与数据清洗假设我们要采集的页面结构大致包含这些字段小区名称、所在区域、户型几室几厅、面积、总价、单价、朝向、楼层、建筑年份、房源链接。解析之后原始数据往往是这样的金湖湾 3室2厅 128.5平 精装 南北通透 185万这种原始字符串没法直接存数据库必须做拆分和清洗。清洗这一步我可太有发言权了很多毕设项目死在这里——数据库里存了一堆3室2厅|128.5平|185万这种拼接字符串后期做可视化时根本没法聚合。推荐的清洗逻辑是用正则表达式提取关键数字和特征import re def parse_house_text(raw_text): result {} # 提取户型如 3室2厅 layout_match re.search(r(\d)室(\d)厅, raw_text) if layout_match: result[bedrooms] layout_match.group(1) result[living_rooms] layout_match.group(2) # 提取面积 area_match re.search(r([\d.])平, raw_text) if area_match: result[area] float(area_match.group(1)) # 提取总价 price_match re.search(r(\d)万, raw_text) if price_match: result[total_price] float(price_match.group(1)) return result清洗的核心原则是能分字段存储的绝不合并存一个字符串能用数值类型的绝不用文本类型。因为Django的ORM筛选用的是字段名如果全塞在一个字段里后面做“按价格区间筛选”“按面积排序”全都无从谈起。再说去重。爬虫多次运行之后同一个房源会被重复采集。去重最简单的方案是给source_url加唯一约束入库前先查这个链接是否已经存在。用Django ORM表达就是if not HouseInfo.objects.filter(source_urlurl).exists(): HouseInfo.objects.create(**data)别小看这个exists判断它能救你于数据爆炸之中。2.3 反爬应对与采集节奏控制这里得先声明一句本文讲爬虫的目的仅限于毕业设计和技术学习实操时务必遵守目标网站的robots协议和使用条款只采集公开的展示数据控制请求频率不要对目标站点造成压力。尤其要注意不要在博文或者代码注释里明说任何“针对某平台”的字眼只以“目标站点”代称即可。最常见的反爬手段就是限速。处理方式非常简单在两次请求之间sleep一下import time import random # 随机延时避免规律性请求 time.sleep(random.uniform(2, 5))为什么用random.uniform而不是固定sleep因为固定间隔的请求模式很容易被识别为脚本行为加入随机抖动之后请求节奏更像真人浏览。再一种反爬是要求登录后查看这个毕设阶段一般不用绕直接采集无需登录即可访问的公开列表页就行。还有一种是页面数据通过Ajax动态加载requests拿到的HTML里根本没有房源数据这时候你需要去Network面板里找到真正的数据接口可能是一个返回JSON的地址直接请求那个接口反而更简单。采集节奏也得控制。一次性把目标站点的几千页全部抓完既不现实也不礼貌。建议是做一个“按页采集”的功能每次指定抓取前N页或者只抓取特定区域和特定条件的数据样本。对毕设来说1000条到5000条有效数据就足够撑起整个可视化了数据贵在质量不在数量。3. 数据可视化让MySQL里的数字自己“说话”爬虫把数据存进MySQL只是第一步真正决定答辩演示效果的是可视化页面。这块的实现核心就一句话前端框架负责画图后端负责提供结构化的JSON数据两边通过Ajax异步通信。下面拆开说。3.1 数据接口设计视图返回JSON而不是HTMLDjango的视图函数不一定非要返回render渲染的HTML页面。对可视化大屏来说更合理的做法是先返回一个承载页面框架的HTML然后页面里的图表再去请求独立的API接口获取JSON数据。比如你想做一个展示区域均价柱状图先定义一个接口视图from django.http import JsonResponse from django.db.models import Avg from .models import HouseInfo def district_avg_price(request): data ( HouseInfo.objects .values(district) .annotate(avg_priceAvg(unit_price)) .order_by(-avg_price) ) result { districts: [item[district] for item in data], prices: [round(item[avg_price], 2) for item in data], } return JsonResponse(result)这个接口设计有两点值得说。第一聚合计算放在数据库层用values().annotate()组合效率远高于把几千条记录拉进Python再手动循环求平均值。第二JSON的key直接用前端好消费的短命名前端拿到之后甚至不需要再做什么转换直接用就行。前端页面引用ECharts再用fetch或axios请求这个接口拿到数据后setOption一个图表就出来了fetch(/api/district-avg-price/) .then(res res.json()) .then(data { var chart echarts.init(document.getElementById(priceChart)); chart.setOption({ xAxis: { type: category, data: data.districts }, yAxis: { type: value }, series: [{ type: bar, data: data.prices }] }); });这是我觉得最适合毕设演示的写法接口逻辑简单明了答辩时你可以直接从接口讲到前端渲染整条链路非常清楚。3.2 常用的几种可视化图表场景我建议你至少上四类图表足够撑起一个信息量丰富的看板区域房源数量柱状图统计每个行政区的在售房源数让人一眼看出哪个区房源供应多。面积-总价散点图以面积为横轴、总价为纵轴每个点代表一套房可以做颜色的第二维度映射来区分不同区域这种图展示出来数据密度高非常出效果。户型分布饼图把2室、3室、4室的占比画出来直观反映主流户型。均价Top10小区横向条形图排序后取前10个小区适合展示单价最高的热门小区。每种图表对应后端一两个接口接口里无非是annotate聚合、order_by排序、切片limit的组合。这不是堆代码而是在向老师展示你对数据维度的理解。3.3 可视化大屏的布局思路也许你会说毕业设计不是做企业大屏不用那么花哨。但其实一个简单整洁的看板页面反而比css动画满天飞的大屏更有效。布局上我推荐经典的“上下左右”结构顶部放标题和整体数据摘要比如总房源数、均价、平均面积、最高单价这几个卡片型指标。中部分两列左侧放区域柱状图、户型饼图右侧放面积-价格散点图。底部放小区均价排行榜。这样整个页面信息分布均衡同时呼应了答辩时你想强调的几个核心数据分析角度。做一个这种页面Django模板里正常写HTML和CSS图表区域用div占位页面加载后按顺序请求多个接口。即使模板逻辑不复杂也要规范地用Django模板变量传递数据比如把统计卡片里的数字在view里算好之后render到模板而不是在JS里再发请求这样更符合Django的使用习惯。4. 数据库设计与Django集成可视化做得再华丽底层数据库设计不合理也一样白搭。这一节讲讲表结构怎么设计、Django ORM有哪些坑、后台管理怎么配。4.1 MySQL表结构设计说实话二手房屋信息这种数据表结构不需要搞多范式核心就一张房源主表。字段设计上要注意的点是能用数值的坚决不用文本能拆开的字段坚决不合并。推荐的核心字段大致如下字段名类型说明idint主键自增titlevarchar(255)房源标题districtvarchar(50)所在区域locationvarchar(255)小区或详细位置layout_descvarchar(50)户型描述如3室2厅bedroomsint室数量living_roomsint厅数量areadouble建筑面积单位平方米total_pricedouble总价单位万元unit_pricedouble单价单位元/平方米orientationvarchar(20)朝向floor_descvarchar(50)楼层描述source_urlvarchar(500)房源原始链接用于去重created_atdatetime采集时间在Django里定义模型就可以用ORM建表。一个完整的例子是这样from django.db import models class HouseInfo(models.Model): title models.CharField(max_length255) district models.CharField(max_length50) location models.CharField(max_length255) layout_desc models.CharField(max_length50) bedrooms models.IntegerField(default0) living_rooms models.IntegerField(default0) area models.FloatField(default0) total_price models.FloatField(default0) unit_price models.FloatField(default0) orientation models.CharField(max_length20, blankTrue) floor_desc models.CharField(max_length50, blankTrue) source_url models.CharField(max_length500, uniqueTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table house_infosource_url加unique约束就是前面说去重的关键。有的同学图省事直接不加唯一约束结果采集脚本每跑一次数据库就翻倍后面清理成本高得很。4.2 Django访问MySQL的配置Django连接MySQL的基本配置写在settings.py里DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: house_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }用之前记得先安装PyMySQL并且在项目包目录下的__init__.py里写入import pymysql pymysql.install_as_MySQLdb()这个细节太容易踩坑了不写这两行Django会报找不到MySQLdb模块。另外一个常见问题是字符集MySQL默认创建数据库时如果没有指定utf8mb4中文存进去很可能变成乱码。建议在MySQL客户端里这样建数据库CREATE DATABASE house_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;utf8mb4和utf8的区别在于前者支持表情符号虽然房屋数据里用不到emoji但防一手总没错而且utf8mb4对中文兼容性更好。4.3 Django Admin后台管理与数据维护Django自带的Admin后台是毕设演示时的另一个亮点。你只需要在admin.py里注册模型from django.contrib import admin from .models import HouseInfo admin.register(HouseInfo) class HouseInfoAdmin(admin.ModelAdmin): list_display (title, district, area, total_price, unit_price, created_at) list_filter (district, orientation) search_fields (title, location) ordering (-created_at,)这几十行代码写完你就拥有一个可以直接在浏览器里管理房源数据、按区域筛选、按关键词搜索的后台。答辩时现场演示一遍“我从后台能看到刚刚采集入库的数据能按区域过滤”比任何截图都有说服力。5. 常见问题与排查技巧实录最后这部分我把自己做这类项目时踩过的坑按“症状-原因-解法”的方式整理出来。你照着这个清单排查大概率能省下好几个晚上的调试时间。5.1 爬虫拿不到数据页面怎么排查症状请求返回200但解析后列表为空。排查思路先打印response.text的前500个字符看看返回的到底是什么。我遇到过好几种情况一是返回的是JavaScript挑战页内容里有一大段script二是跳转到了验证码页面三是页面本身的结构变了没有之前分析的那些class名。解决方案分别是对抗JS挑战、降低请求频率或更换采集时段、重新检查页面结构。每一步都是为了靠近真实的目标——搞清楚网页到底返回了什么。5.2 MySQL写入中文乱码乱码问题几乎每个人都遇到过。你先对照这几个位置数据库字符集是不是utf8mb4settings.py里OPTIONS有没有指定charset页面显示端有没有指定meta charset utf-8这三个地方不一致就会出现乱码。最简单的排查顺序是从数据库最后查起先查库里存的是什么再往上推。如果库里存的就是乱码说明是写入端问题检查字符集配置如果库里正常而页面上乱码那是读取或渲染端的问题。这就是很典型的“看数据在哪一步变形”的排查思路。5.3 查询慢和数据量过大的优化数据几千条时基本不用考虑性能一旦到几万条Django ORM性能问题就会出现。初级优化手段是给高频查询的字段加索引比如district和unit_priceclass HouseInfo(models.Model): district models.CharField(max_length50, db_indexTrue) unit_price models.FloatField(default0, db_indexTrue)另一个常见问题是N1查询。新手在页面循环里查关联表每个循环都发一次数据库请求压垮性能。房屋信息这个项目只有单表倒不严重但如果你扩展了小区表、区域表就一定要注意select_related和prefetch_related的使用。页面上如果数据太多建议后端做分页前端用Ajax翻页而不是一次性加载全部。Django的Paginator就够用from django.core.paginator import Paginator def house_list(request): all_data HouseInfo.objects.all() paginator Paginator(all_data, 20) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, house/list.html, {page_obj: page_obj})5.4 爬虫采集时间过长中断怎么办采集几百页数据中间网络波动导致请求异常整个脚本直接退出这是常见到不能再常见的事。解决方式很简单每条数据处理都包裹try-except异常时打印日志、跳过当前条不中断主流程。再配合前面分析的source_url唯一约束即使中断后重跑已入库的数据也会自动跳过不必担心重复。for page in range(1, total_pages 1): try: items fetch_page(page) except Exception as e: print(f第{page}页抓取失败: {e}) continue for item in items: save_house(item)写代码时留好日志习惯方便事后定位问题这不只是毕设技巧也是工作习惯。6. 项目扩展与答辩发挥建议最后这部分不是凑字数而是想给你几条实际可操作的后续扩展方向顺便聊聊答辩时的表达技巧。6.1 想加分可以怎么扩展爬虫跑的定时化把采集脚本做成Django管理命令比如用manage.py crawl_houses调用再配合系统的定时任务定期执行这样系统就能自动更新数据。这一条非常加分因为它让系统从“手动运行脚本”升级为“自动化定时采集”。区域对比分析在可视化页面增加一个区域选择器选择不同区域后散点图和柱状图联动更新。实现上就是给接口加一个district参数前端切换时重新请求接口。价格预测如果你的数学基础还行可以用sklearn里的线性回归拿面积、房龄、所在区域做特征训练一个简单的房价预测模型再在页面上增加一个“估价工具”模块。这里不需要特别复杂的调参一个线性回归就足够撑起一道“算法结合”的展示题。6.2 答辩时怎么讲这套系统答辩不是讲你写了多少代码而是讲你的设计思路和解决问题的能力。建议按“为什么这么做、遇到什么问题、怎么解决的”来讲。比如爬虫模块不要只演示“我能抓数据”而是主动讲“我遇到了数据重复所以给source_url加了唯一约束我遇到了反爬限速所以加了随机延时”。老师听到这种回答印象分会明显不一样。可视化模块强调你在接口层的聚合设计说明你是通过数据库聚合来减少数据传输量而不是前端拿全量数据来算——这种“会做技术取舍”的表述是答辩中关键的加分项。整个项目做下来你其实掌握了Django全栈开发的完整闭环从数据采集到数据入库再到数据展示。这几项技能并不是孤立的——它们也是大多数数据类业务系统的通用骨架。真正重要的是吃透“数据从哪里来、存在哪里、怎么展示”这条链路这个思路换任何领域都能复用。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻