首页 / 资讯中心 / 文章详情

Django+爬虫+大数据:二手房信息采集系统全流程实战解析

Django+爬虫+大数据:二手房信息采集系统全流程实战解析 ★ FEATURED ARTICLE
每年到毕设季总会有一批人抱着“基于Django大数据技术的网络爬虫的二手房屋信息采集系统”这类题目来找我问到底怎么落地。老实说这种题目听起来有门槛但它本质上就是一条“数据采集—数据清洗—数据存储—数据分析与可视化”的完整链路。真正需要的技术栈并不复杂Django做后端框架Requests或Scrapy做爬虫MySQL存数据Pandas做指标计算ECharts做展示。我这次把这样一个二手房信息采集系统从需求拆解到完整实现走了一遍包括爬虫、数据模型、定时任务、可视化、部署以及最后答辩的演示思路全部整理在下面供正在做同类课题或者想入门爬虫与大数据项目的同学参考。1. 从毕设题目读出什么需求拆解与项目定位1.1 三层核心诉求Web开发、爬虫、大数据先别急着写代码第一步永远是拆题。这个题目表面上是“一个系统”实际考核了三条能力线Django代表Web开发能力网络爬虫代表数据采集能力大数据技术代表数据处理与分析能力。三者被“二手房屋信息采集”这个业务场景串起来最终要交付的东西是一个能跑、能演示、能讲清楚设计意图的完整项目。我一般喜欢把这类系统画成一条流水线爬虫从目标站点抓取房源数据清洗后写入数据库Django提供接口和管理后台数据分析模块对房价、户型、区域分布做统计计算前端页面用图表把结果展示出来。这条链路里任何一个环节单独拿出来都不算难难的是把它们无缝拼接起来并且保证数据一致性、任务可重复执行、系统能稳定运行。拆解清楚了后面每一步都有明确目标。1.2 为什么是Django而不是Flask或SpringBoot很多同学会问Python后端又不是只有Django为什么偏偏选它我的理由是Django几乎把“毕设需要的通用功能”都内置了。举个例子房源数据入库后Django自带的ORM可以免去手写SQL的麻烦Admin后台能够直接看到数据库里的数据、修改字段、导出记录这在演示的时候非常加分。Flask虽然轻巧但用户认证、Admin、ORM、表单这些模块都需要自己集成反而拉长开发周期。至于SpringBoot如果你Java基础很好当然没问题但和Python生态的爬虫、数据分析库配合起来跨语言整合的成本更高。做毕设讲究的是在有限时间内跑通全链路Django是当前背景下最顺手的选择。1.3 系统功能边界与演示目标一个常见的误区是“什么功能都想做”加用户评论、加推荐算法、加实时房价监控……功能堆得越多答辩时候被追问的风险越高。我这次严格限定了边界采集房源列表与详情、按区域和价格筛选、统计区域均价与挂牌量、展示价格趋势与户型分布、设置定时增量采集。至于个性化推荐、实时计算、支付交易这类无关模块一概不做。边界的价值在于让系统有一条清晰的主线。答辩时老师问“这个模块为什么这么设计”你能从功能闭环的角度讲清楚如果塞了一堆无关功能反而暴露设计上的混乱。记住一句话毕设系统的价值在于逻辑自洽不在于功能数量。2. 系统架构与数据链路一条数据从网页到图表的路2.1 分层架构与模块划分整个系统我分成四层数据源层、采集层、服务层、展示层。数据源层就是目标二手房信息平台采集层负责抓取列表页和详情页解析出结构化字段服务层是Django应用承担API、数据库读写、定时任务调度展示层包含前端页面和Admin后台。这四层的依赖关系是单向的上层只调用下层接口下层不反向依赖上层。这样做有一个实际好处如果目标网站改版了只需要改采集层的解析逻辑Django服务和前端页面完全不用动。我见过不少同学把采集逻辑直接写在视图函数里结果页面改版一次就要重构半个项目教训非常深刻。2.2 核心数据模型设计数据模型是整个系统的地基。我设计了五张核心表区域表、房源表、价格快照表、采集任务表、任务日志表。房源表是最核心的字段设计如下class House(models.Model): platform models.CharField(max_length50, default安客居) house_id models.CharField(max_length100, uniqueTrue) title models.CharField(max_length255) region models.ForeignKey(Region, on_deletemodels.SET_NULL, nullTrue) total_price models.FloatField(verbose_name总价/万) unit_price models.FloatField(verbose_name单价/元每平米) area models.FloatField(nullTrue, blankTrue) room models.CharField(max_length30) floor models.CharField(max_length50) orientation models.CharField(max_length20) decoration models.CharField(max_length20) publish_date models.DateTimeField(nullTrue, blankTrue) create_time models.DateTimeField(auto_now_addTrue) update_time models.DateTimeField(auto_nowTrue) class Meta: db_table house indexes [ models.Index(fields[region, total_price]), models.Index(fields[unit_price]), ]这里有两个设计心得。第一house_id必须加唯一约束它是目标平台内部编号是去重和增量更新的依据。第二价格不要存字符串直接存FloatField单位统一成“万”和“元每平米”否则后面做聚合计算时会陷入单位混乱的泥潭。价格快照表专门记录每天各房源的价格变化为后续“价格趋势图”提供数据支撑。2.3 技术选型MySQL、Redis、Celery各司其职存储与中间件选了三个互补的组件MySQL负责房源数据的持久化Redis承担URL去重集合、Celery消息队列和热门查询的缓存Celery负责定时触发爬虫任务。为什么不是只用MySQL因为爬虫在去重阶段会频繁判断“这个详情页是否已抓取”如果每次都查MySQL在几万条URL面前效率很低。Redis的SADD和SISMEMBER操作可以在毫秒级完成判断而且天然带有过期策略非常适合做采集状态管理。Celery则把“定时启动爬虫”和“Django主进程”解耦爬虫更新时不会阻塞用户访问页面。2.4 大数据组件使用的“度”很多同学一听到“大数据”三个字立刻想到Hadoop、Spark、Hive结果在本机折腾三天虚拟机都没起来。我的看法是毕设要体现大数据技术但不等于堆砌大数据组件。房源数据基本是十万级到百万级MySQL加Pandas完全能扛住重要的是把数据清洗、统计分析、指标计算这条链路做完整。如果论文必须体现“大数据框架”可以考虑在分析模块引入Spark的Local模式用DataFrame完成同样的聚合计算并和Pandas版本做性能对比。这种方式不需要搭建集群也能够写出实验数据。相比起空谈Hadoop生态这种有真实数据支撑的对比更能打动答辩老师。技术是为数据服务的不是为了在系统结构图里多画几个框。3. 爬虫模块实战采集、解析、去重与反爬应对3.1 目标页面结构与请求分析爬虫的第一步不是写代码而是打开目标网站搞清楚数据是怎么展现在页面上的。我这次选择的“安客居”二手房板块列表页展示房源摘要详情页包含完整信息。用浏览器开发者工具查看网络请求后发现列表页大部分数据直接渲染在HTML里不需要额外模拟AJAX接口请求因此采用Requests加BeautifulSoup的方案就足够比Scrapy调试起来更快。分页规律也值得记录第一页URL形如/house/list/p1/第二页是/p2/。有些平台的总页数有限制比如最多100页那么就需要结合区域筛选把每个区域单独抓取避免漏数据。还有一点移动端页面和PC端页面的HTML结构往往不同选择一个稳定的入口非常重要我最终固定用PC端页面并且把请求头中的User-Agent统一设置为常见浏览器版本。3.2 字段解析与清洗逻辑列表页解析的核心是从每条房源节点中提取有效字段。我封装了一个解析函数大致逻辑如下def parse_list_html(html, region): soup BeautifulSoup(html, html.parser) items soup.select(.house-item) result [] for item in items: try: title item.select_one(.til a).get_text(stripTrue) total_price_text item.select_one(.total-price).get_text(stripTrue) total_price float(total_price_text.replace(万, )) unit_price_text item.select_one(.unit-price).get_text(stripTrue) unit_price float(unit_price_text.replace(元/平, ).strip()) result.append({ title: title, total_price: total_price, unit_price: unit_price, region: region, }) except AttributeError: continue return result解析时不要直接在页面上取所有字段详情页的字段往往更完整。我的策略是列表页拿价格和标题详情页再补充面积、户型、楼层、朝向、装修情况。两处数据通过house_id关联最后合并成一条记录。清洗逻辑中尤其要注意三种脏数据价格字段带汉字、面积字段为0、时间格式不统一。这些脏数据不处理干净到了Pandas聚合阶段会直接报错或产出离谱结果。3.3 增量采集、去重与断点续爬设计增量采集之前先想清楚一个问题目标平台每天都有新挂牌房源、有下架房源、有价格变动全量重爬耗时且容易触发风控所以必须做增量更新。我的做法是双集合去重。Redis里维护一个crawled:house_ids集合爬虫解析到一条房源后用SISMEMBER判断是否已抓取如果没抓取就把house_id写入待处理队列。详情页抓取成功后用SADD补回集合。这样断点续爬变得非常自然不管进程中途挂掉多少次重启任务时只需要重新从列表页入口开始已采集的URL会自动跳过。任务结束后统计新增、更新、跳过和失败四条数据写进日志表。3.4 反爬应对UA池、访问节奏与Cookies这个环节没必要藏着掖着但也要守住底线采集频率必须控制在合理范围内不能对目标站点造成压力。我做了三件事。第一构建UA池在请求时随机抽取一个常见的浏览器UA字符串第二两次请求之间随机休眠1到3秒避免固定间隔被识别第三在请求头中带上从浏览器复制的Cookie和Referer字段保持会话一致性。并发方面我用ThreadPoolExecutor把线程数限制在4到6个实测采集速度已经足够。这里要特别提醒不要用任何绕过验证码、破解登录、高频并发采集的手段。毕设的目的是验证数据链路的完整性不是和对方平台做技术对抗。访问太激进会导致IP被限制反而让整个系统演示时拿不到数据得不偿失。3.5 健壮性设计异常重试与日志爬虫最大的特点就是不确定性网络抖动、页面改版、反爬策略变化任何一环出问题都会让任务中断。我为采集函数加了一个带重试机制的执行器请求失败自动重试最多3次每次间隔递增解析失败则记录到异常日志不阻塞整批任务。每个房源的处理过程都输出结构化日志格式包含时间、房源ID、动作类型、耗时方便事后追踪。一开始我觉得这些“防御性代码”没意义直到一次演示前半小时目标网站突然给列表页加了一层验证组件整个爬虫跑不动了。因为有容错日志我迅速定位到是页面结构变化临时切换备用解析规则才救回演示。这个经历让我明白爬虫模块的健壮性比采集速度重要得多。4. Django后端与数据分析API、定时任务与统计指标4.1 Django项目骨架与App划分项目结构决定代码的可维护性。我创建了5个Appcollect负责采集任务的调度与执行house负责房源信息管理analysis负责数据统计和指标计算api提供REST接口users处理简单的登录权限。python manage.py startapp collect python manage.py startapp house python manage.py startapp analysis python manage.py startapp api python manage.py startapp users有人会觉得App分这么细有点多余但实际开发中收益很明显爬虫逻辑改动时只启动collect相关测试数据分析升级时不会碰到采集代码。每个App还能独立写单元测试答辩时提到“模块化设计”也有实际代码支撑。4.2 核心API的设计思路前端图表需要数据接口数据接口需要稳定返回JSON。我使用Django REST Framework实现了5个核心接口接口路径作用主要参数/api/houses/list/房源列表与筛选region、price_min、price_max、room/api/houses/summary/全局统计卡片数据无/api/regions/price-trend/区域价格趋势region、days/api/regions/heatmap/区域挂牌量与均价city/api/houses/export/导出筛选结果同列表接口所有接口都统一走DRF的序列化器查询参数交给request.query_params处理。列表接口根据用户选择的区域、总价区间、户型条件动态拼接ORM查询集返回时分页处理每页20条。演示时可以通过前端筛选器连续点击观察接口返回的变化效果比静态页面好很多。4.3 用Pandas完成价格与户型分析数据分析是“大数据技术”的直接体现。我写了一个独立脚本定期从MySQL拉取数据到DataFrame然后完成清洗、聚合和计算。import pandas as pd from sqlalchemy import create_engine from django.conf import settings engine create_engine(settings.DB_DSN) df pd.read_sql(SELECT region_id, total_price, unit_price, area, room, orientation FROM house, engine) # 清洗异常值 df df[df[area] 3] df df[df[total_price] 10000] df df.dropna(subset[unit_price]) # 区域均价与挂牌量 region_stats df.groupby(region_id).agg( median_price(unit_price, median), avg_price(unit_price, mean), house_count(house_id, count) ).reset_index()清洗阶段三个硬阈值是我实测后定的面积小于3平米的记录直接删除总价超过10000万的视为错误数据缺少单价的记录不能参与均价计算。聚合完成后结果保存到统计表或直接写入缓存前端接口读取时不需要重复计算。户型分布、朝向占比、楼层结构同样用value_counts统计后转成图表所需格式。4.4 Celery定时任务让采集自动跑起来爬虫不能每次都手动启动我配置了Celery定时任务每天凌晨2点执行增量采集凌晨3点执行数据分析和统计。这样用户每天早上打开系统看到的数据都是最新的演示效果非常自然。# tasks.py shared_task def run_incremental_spider_task(): spider IncrementalSpider() spider.run() shared_task def run_analysis_task(): analysis HouseAnalysis() analysis.build_all_statistics()# settings.py 中 Celery Beat 配置 CELERY_BEAT_SCHEDULE { incremental-spider-daily: { task: collect.tasks.run_incremental_spider_task, schedule: crontab(hour2, minute0), }, analysis-daily: { task: analysis.tasks.run_analysis_task, schedule: crontab(hour3, minute0), }, }为什么要把采集任务交给Celery而不是直接在Django里写一个循环因为Celery能把任务调度、失败重试、执行日志统一管理起来而且任务执行时可以并行处理数据不会阻塞Web服务。开发机上跑通Celery Beat后整个系统的自动化能力就立起来了。5. 可视化与后台管理让数据看得见5.1 前端选型与页面布局前端我没有引入复杂框架选了Bootstrap加原生JavaScript加ECharts。原因是Django模板渲染简单直接不需要额外构建工具本地跑通速度快。页面布局采用经典的数据驾驶舱样式顶部是四个统计卡片显示房源总量、区域均价、今日新增、平均面积中部左侧放区域热力地图右侧放价格趋势折线图底部是区域挂牌量柱状图和户型占比饼图。统计卡片的数字来自/api/houses/summary/接口初始化页面时用一次fetch全部拉回来。整页只有一个入口后续所有图表的数据都从独立的接口获取这样每个图表都能单独刷新不会因为一个接口失败导致整页空白。5.2 核心图表实现地图、热力图、趋势图ECharts用起来最顺手的部分是地图。我提前准备了一份本市行政区划的地理信息数据运行时通过接口把每个区域的均价和挂牌量传进去用visualMap组件把颜色映射到价格区间。热力图改用散点图效果模拟每个房源经纬度作为坐标值热度权重取单价。价格趋势图是折线图横轴是日期纵轴是均价数据来自价格快照表按天聚合的结果。除了展示图表我在每个图上方都加了区间筛选器比如只查看总价300万到500万之间的房源这样演示时可以和接口参数联动体现系统的查询能力。5.3 Django Admin与自定义管理后台Django Admin是毕设演示的隐藏加分项。只需注册模型就能直接在后台对房源、区域、任务日志做增删改查。我额外自定义了两个管理动作一个用来手动触发采集任务一个用来导出房源数据为Excel。# admin.py admin.action(description手动触发增量采集) def trigger_incremental_spider(modeladmin, request, queryset): from collect.tasks import run_incremental_spider_task run_incremental_spider_task.delay() admin.action(description导出选中房源为Excel) def export_selected_houses(modeladmin, request, queryset): import pandas as pd data list(queryset.values()) df pd.DataFrame(data) response HttpResponse(content_typeapplication/vnd.ms-excel) response[Content-Disposition] attachment; filenamehouses.xlsx df.to_excel(response, indexFalse) return response这两个动作在答辩现场非常实用老师说“能不能跑一下采集”你点一个按钮任务日志开始滚动老师问“数据能不能导出来”你勾选几条记录直接下载Excel。这种“可操作性”比任何架构图都有说服力。6. 调试、部署与答辩准备从本地跑通到完整演示6.1 本地开发最容易踩的四个坑我把开发过程中最折磨人的几个问题列出来提前踩过就不用再受苦。第一个坑是版本兼容。Python 3.12刚发布时某些Django扩展库没有适配跑起来一堆报错。第二次我直接锁定Python 3.10、Django 4.2、DRF 3.14的版本组合所有依赖都稳定。第二个坑是MySQL字符集。建库时如果没有指定utf8mb4房源标题里的特殊字符存进去会变问号。建库语句务必加上CREATE DATABASE house_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;第三个坑是Celery在Windows下的兼容性。Windows下默认的进程模型和Celery不兼容启动worker时会卡住。开发机上我用了eventlet模式启动部署到Linux后自然就稳定了。第四个坑是爬虫采集到一半被限制。我在开发阶段为了赶进度把并发调得过高结果IP被限制整个开发环境访问目标站点都受影响。后来把所有请求频率调低问题才彻底解决。记住开发期的采集频率一定要克制。6.2 远程部署Linux Nginx uWSGI本地跑通只完成了一半真正能被老师检查的系统还需要部署到服务器上。部署流程我整理成六个步骤安装Python和MySQL创建虚拟环境并安装依赖迁移数据库并创建管理员账号启动Celery worker和Beat用uWSGI跑Django用Nginx反向代理静态文件和接口。远程调试的实用技巧是用SSH连接服务器后在IDE里配置远程解释器这样就可以直接在服务器上打断点调试采集任务。部署过程有一个容易忽略的点Django的DEBUG必须设为False同时把ALLOWED_HOSTS配置成服务器IP或域名否则静态文件加载会出问题。我用Nginx托管静态文件目录后前端图表加载速度明显提升。6.3 演示数据准备与答辩节奏答辩演示最忌讳临场出意外所以我在正式演示前会跑一次完整的数据更新流程确保系统里至少有两到三个区域、几百条以上的真实房源数据。演示顺序按照数据链路来讲先打开页面看总览点击区域筛选看到列表变化然后进入后台展示定时任务日志里的采集记录接着手动触发一次采集任务观察新数据入库最后打开分析页面展示价格趋势、户型和区域对比。时间分配上15分钟的演示我通常这样安排3分钟讲系统结构和数据链路7分钟现场操作演示5分钟展示核心代码和设计思路。全程不要逐行念代码重点讲“这个模块解决什么问题、为什么这样设计、数据从哪里来到哪里去”回答提问时会从容很多。6.4 扩展方向与二次开发建议这个系统的扩展空间其实很大。技术层面可以把分析模块升级为Spark Local模式做性能对比也可以接入房价预测模型用历史价格快照训练一个简单的回归模型在页面上展示下月预测均价。业务层面可以加入小区维度聚合、租房信息采集、地铁沿线房价分析或者在任务完成时自动生成PDF日报并发送邮件。不过我想说的是扩展方向是锦上添花不是必需品。我做完这个项目的最大体会是毕设的核心价值在于把一条数据链路完整跑通并且每一步都经得起追问。采集、清洗、存储、分析、展示这五个环节环环相扣只要把一个闭环做到位就已经能证明你的工程能力和数据思维了。如果你正在做类似的系统我的建议是别一开始就盯着“大数据”三个字先把数据从网页到数据库再到图表完整跑通。真正花时间的往往不是框架代码而是清洗脏数据、处理异常情况、完善任务日志这些“看不见”的工作。把这些基础打好系统才能真正称得上“能用、能演示、能答辩”。希望这篇梳理能帮你省掉一部分弯路。
阅读完成 · 觉得有帮助?
咨询建站