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

资讯详情

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

Python租房数据采集分析系统:Requests+Django+ECharts实战

Python租房数据采集分析系统:Requests+Django+ECharts实战 北上广深打拼过的朋友大概都体会过被房东临时通知涨租、被中介带着连跑三天却看不到一套真房源的那种无力感。我当年为了换房硬是把 58 同城、贝壳、自如几个平台翻了个底朝天手动复制粘贴房源信息建了个 Excel 表结果数据一多就卡死筛选条件更是没法看。后来我意识到与其手动去翻不如写一套程序自动抓取、统一存库、再用图表把价格分布、区域均价、户型占比一目了然地画出来。于是我做了这个基于 Python 的 58 同城租房数据采集分析系统技术栈是 Requests 爬虫 Django 框架 ECharts 可视化整套流程跑通之后选房效率提升了不止一个量级。这套系统虽然挂的是“毕业设计”的名头但它的实用价值一点不虚——完整覆盖了从数据采集、清洗入库、后端接口到前端可视化大屏的闭环。无论你是正在找毕设方向、想入门爬虫与数据分析还是单纯觉得手动看房太痛苦想搞个工具这套代码和思路都能直接抄作业。下面我把整套系统从架构设计到落地实现、再到后来我自己踩过的坑全部拆开讲清楚。1. 项目整体设计与技术选型思路1.1 为什么是 Requests 而不是 Scrapy很多人一看到“爬虫”两个字脑子里蹦出来的第一个词就是 Scrapy。确实Scrapy 是业界公认的工业级爬虫框架内置并发调度、中间件、Item Pipeline看起来什么都给你准备好了。但在这个项目里我偏偏选了 Requests 这个轻量级库原因很简单业务量级决定工具选型。58 同城的租房列表页一套城市一个区的数据量也就是几千条量级而且租房信息更新频率不算特别高。用 Requests 加多线程或者简单的循环请求完全能在几分钟内跑完一个区的数据。Scrapy 的启动成本和学习成本都比较高——你不仅要理解 Spider、Pipeline、Middleware 这些抽象概念调试起来也比纯脚本麻烦。毕设项目最重要的是把核心链路讲清楚、把代码跑通、把逻辑展示明白Requests 足够用了而且代码可读性更强答辩的时候更容易说清楚每一步在干什么。另外还有一个实际考虑58 同城的页面结构并不算特别复杂大多数房源信息是服务端渲染直接输出在 HTML 里的或者通过一个简单的 Ajax 接口返回 JSON。Requests 加 BeautifulSoup 的组合用最直接的方式就能把数据拿到手根本不需要绕一圈去上 Scrapy。如果你以后真要做大规模分布式采集再迁移到 Scrapy 也不迟Requests 写的解析逻辑照样可以复用。1.2 为什么用 Django 而不是 Flask 或 FastAPI后端框架我选了 Django核心原因有两个自带 ORM 和 Admin 后台。对于这种以数据处理为核心的小项目Django 的 ORM 让你不用手写 SQL 就能完成建表、插入、查询、聚合统计简直是数据分析场景的福音。比如我要统计某个区域内各户型挂牌数量的占比一句 ORM 查询加上一个 group by 就能搞定如果换成 Flask你得自己拼 SQL、自己管理数据库连接平白多出一堆胶水代码。Django 的 Admin 后台也是一个被很多人低估的神器。爬虫把数据写进 SQLite 或 MySQL 之后你不需要写任何前端页面就能通过 Admin 后台可视化地查看、筛选、修改数据。在毕设答辩演示的时候直接打开 Admin 页面展示数据入库情况比对着终端敲 SQL 命令帅多了也直观多了。当然如果你不喜欢 Django 这种“全家桶”风格用 Flask 替代也完全可行接口逻辑几乎不用改。但 Django 的成熟生态和自带功能确实能省下很多不必要的麻烦尤其是你同时要兼顾爬虫和可视化两个大模块的时候能少操心一件事就是赚到。1.3 可视化方案选型ECharts、Pyecharts 还是纯前端可视化部分我最终采用的是 ECharts 前端图表库数据通过 Django 提供的 JSON 接口动态获取。为什么不直接用 Python 端的 Pyecharts 生成 HTML 图表因为 Pyecharts 虽然写起来方便但生成的是静态页面每次数据更新都要重新执行一遍脚本生成交互体验很死板。而 ECharts 配合 Ajax 动态请求页面打开的时候实时向后端拉数据后续你想加筛选条件、时间维度对比都只需要改前端代码就行灵活性强太多了。还有一个隐藏的好处用 ECharts 意味着前端只需要一个静态页面加几个 JS 文件不需要引入 Node.js 环境也不依赖 Webpack 那一堆构建工具对没有前端基础的读者非常友好。你只需要照着 ECharts 官网的文档把配置项复制过来改改数据源就能画出漂亮的柱状图、饼图、地图。这套方案的教学价值和学习成本对于毕设场景来说是最平衡的。2. 核心模块详解采集、存储、接口与展示2.1 爬虫模块页面结构分析是第一步写爬虫之前最重要的一件事不是写代码而是打开浏览器开发者工具把页面结构摸清楚。以 58 同城租房列表页为例页面中每个房源条目通常是一个li标签里面嵌套了多个div分别承载房源标题、价格、户型、面积、朝向、楼层、小区名称和位置链接。我用的解析流程是这样的先用 Requests 带上完整的请求头去 get 页面 HTML然后用 BeautifulSoup 定位到列表容器再遍历每个条目提取需要的字段。这里有个关键点——不要用正则表达式去匹配 HTMLHTML 结构复杂多变正则写起来痛苦、维护起来更要命。BeautifulSoup 的 find 和 find_all 方法配合 CSS 选择器语义清晰出错的概率低得多。字段设计上我最终保留了这些核心信息标题、价格元/月、租赁方式整租/合租、户型几室几厅、面积平米、朝向、楼层、小区名、区域所在区/商圈、详情页链接、抓取时间。实际存库的时候价格、面积、户型这些字段可能带着“元/月”“㎡”“室厅”这些单位字符必须做一步字段清洗转成 int 和 float 类型后面做统计才不会出问题。2.2 存储建模SQLite 起步MySQL 扩展数据存储这块我强烈建议毕设先用 SQLite后期需要再切 MySQL。SQLite 是 Python 内置支持的数据库零配置一个文件搞定一切Django 的 ORM 对 SQLite 的支持也非常完善你不需要安装任何额外的数据库软件就能跑通整个项目。我实际测过一万条以下的数据 SQLite 的读写性能完全没压力对毕设场景来说绰绰有余。Django 的模型定义非常直白我建了一张 House 表字段和爬虫提取的字段一一对应。比较关键的设计决策有两个一是给详情页链接字段加了uniqueTrue用来做去重约束重复采集的时候直接忽略已存在的记录二是给价格、面积这类数值字段设定了合理的取值范围待会讲数据清洗的时候你就知道这个约束有多重要了。2.3 接口设计Django View 返回 JSON 数据前端图表需要数据接口就是数据和页面之间的桥梁。我设计了四个主要接口分别是总览统计接口、区域均价接口、户型分布接口和价格区间分布接口。每个接口都接收可选的区域参数方便前端做联动筛选。Django 里返回 JSON 的方式有几种我推荐使用 JsonResponse它直接帮你把 Python 字典转成 JSON 字符串还自动处理了中文编码问题比手动 json.dumps 加 HttpResponse 省心得多。接口逻辑的核心其实就两步第一步根据请求参数构造 ORM 查询条件第二步用 aggregate 或 annotate 做聚合统计。2.4 可视化页面一套页面打通数据链路前端页面我只做了一个index.html通过 ECharts 初始化多个图表实例每个图表对应一个 Ajax 请求。页面顶部放筛选条件点击“确认筛选”按钮之后所有图表统一重新拉取数据并刷新这样用户就能按区域、按价格段自由探索数据。如果你想做得更炫一点可以加一个地图组件用 ECharts 的地图展示各区域的平均租金热力分布。58 同城的数据是带商圈信息的比如“国贸”“望京”“回龙观”把经纬度补上就能画散点图。我当时时间有限没做这一步如果你做毕设有富余精力这绝对是一个加分项。3. 实操过程从环境搭建到页面展示全记录3.1 环境准备与依赖安装这个项目用到的核心依赖其实非常少requests、beautifulsoup4、django、pandas可选、echarts前端文件可以从 CDN 引入。我建议用虚拟环境隔离项目依赖避免全局环境被搞乱。如果你用的是 Anaconda可以直接conda create -n rent python3.9创建干净环境然后执行pip install requests beautifulsoup4 django pandasDjango 的版本我建议选 4.x 或者 3.2 LTS两者都行别贪新选 5.x有些第三方库的兼容性还没跟上。装完之后用django-admin startproject rent_system .创建工程再用python manage.py startapp house创建应用整个项目的骨架就出来了。3.2 数据采集脚本完整代码跑通爬虫脚本是最核心的部分我贴一段当时写的主逻辑代码你在自己机器上跑之前记得把 URL 里的城市和区县参数换成你自己的目标。import requests from bs4 import BeautifulSoup def fetch_rent_list(city, district, page1): url fhttps://{city}.58.com/zufang/{district}/pn{page}/ 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, Referer: fhttps://{city}.58.com/zufang/{district}/, Accept-Language: zh-CN,zh;q0.9, } resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 if resp.status_code ! 200: print(f请求失败: {resp.status_code}) return [] soup BeautifulSoup(resp.text, html.parser) items soup.select(div.content__list--item) parsed [] for item in items: title_tag item.select_one(h3.content__list--item-title a) price_tag item.select_one(span.property__price strong) desc_tag item.select_one(p.content__list--item-source) if not title_tag or not price_tag: continue parsed.append({ title: title_tag.get_text(stripTrue), link: title_tag.get(href), price: int(price_tag.get_text(stripTrue)), detail_desc: desc_tag.get_text(stripTrue) if desc_tag else , }) return parsed注意几个细节一是resp.encoding utf-8必须显式设置58 的响应头有时候会漏掉 charset不设置的话中文大概率乱码二是选择器div.content__list--item是 58 当前版面的热门类名如果页面改版需要重新定位三是异常处理要做单条解析出错不能中断整个循环用 try-except 包住解析逻辑才行。3.3 数据清洗与入库拿到原始字段之后绝对不能直接往数据库里怼因为 HTML 里的文本格式五花八门——价格可能带“元/月”面积可能是“85㎡”户型可能是“3室2厅1卫”。我的清洗策略是写一个clean_house_data()函数统一做三件事去掉单位字符、强制类型转换、过滤异常值。def clean_house_data(raw): price_text raw.get(price_text, 0).replace(元/月, ).strip() area_text raw.get(area_text, 0).replace(㎡, ).replace(平米, ).strip() try: price int(float(price_text)) except ValueError: price 0 try: area float(area_text) except ValueError: area 0 if price 100 or price 200000: price 0 if area 5 or area 1000: area 0 return {**raw, price: price, area: area}价格范围过滤这段我解释一下为什么这么设正常租房月租低于 100 元的几乎不可能存在除非是打隔断的小单间或者偏远地区但数据库里宁缺毋滥高于 20 万的是商铺或者整栋租赁不属于普通租房范畴。面积小于 5 平米是隔断中的隔断大于 1000 平米大概率是仓库或者厂房误入租房分类。这些边界值都是我实际跑了数据之后总结出来的能帮你挡掉不少脏数据。清洗完之后通过 Django ORM 做批量入库from house.models import House def save_house_records(cleaned_list): objs [] for item in cleaned_list: house House( titleitem[title], linkitem[link], priceitem[price], areaitem[area], layoutitem.get(layout, ), districtitem.get(district, ), ) objs.append(house) House.objects.bulk_create(objs, ignore_conflictsTrue)bulk_create的好处是一次 SQL 插入多条数据效率翻好几倍。ignore_conflictsTrue配合链接字段的唯一约束保证重复采集时不会炸主键冲突。3.4 后端接口与图表联调数据入库之后就该写接口了。我举一个区域均价接口的例子from django.http import JsonResponse from django.db.models import Avg from house.models import House def avg_price_by_district(request): district request.GET.get(district, ) qs House.objects.exclude(price0).exclude(district) if district: qs qs.filter(districtdistrict) result list(qs.values(district).annotate(avg_priceAvg(price)).order_by(district)) return JsonResponse({data: result}, safeFalse)这个接口的逻辑非常清晰前端传一个可选的 district 参数后端用 ORM 先过滤掉 price 为 0 的脏数据再按区域分组计算平均值。前端拿到数据之后往 ECharts 的 bar 图配置里塞就行$.get(/api/avg_price/, {district: currentDistrict}, function (res) { var regions res.data.map(item item.district); var prices res.data.map(item Math.round(item.avg_price)); myChart.setOption({ xAxis: { data: regions }, series: [{ data: prices, type: bar }] }); });整套链路就是爬虫采集 → 清洗入库 → Django ORM 聚合 → JSON 接口输出 → ECharts 渲染。每一步都是单行道数据流非常清晰任何人来看代码都能快速理解系统在做什么。3.5 可视化的三个核心图表我最终在页面上放了三个图表一个是区域均价柱状图直观对比各区租金水平一个是户型分布饼图展示整租/合租以及几室户型的占比一个是价格区间直方图统计不同预算段的可选房源数量。这三个图表加在一起基本覆盖了租房用户最关心的三个问题哪里贵、都有什么户型、我的预算能选什么。直方图的实现细节我多说一句价格区间要先在后台分好桶不要直接拿原始价格丢给前端画。我用的区间划分是 0-1000、1000-2000、2000-3000、3000-5000、5000-8000、8000 以上这几个区间覆盖了大部分城市的租房价格分布。分桶逻辑放在后端接口里用条件聚合实现比前端用 JavaScript 做分组干净得多。4. 常见问题与排查技巧实录4.1 爬虫请求被拦截返回 403这是所有爬虫新手遇到的第一个拦路虎。58 同城的反爬手段不算严厉但基本的 User-Agent 校验是有的——如果你用默认的python-requestsUA 去请求服务器大概率直接拒绝。解决方法是把请求头补全尤其要带上浏览器内核版本的 User-Agent。我实测有效的配置是前面代码里写的那一串如果你用浏览器复制出来的完整 UA 字符串效果更好。另外一个容易忽略的点是Referer头。有些站点会检查请求来源如果 Referer 为空或者跨域同样会触发反爬。把上一级页面的 URL 填进去模拟正常用户从列表页点击进入详情页的路径被拦的概率会大大降低。如果加了请求头还是被限流那就是请求频率太快了。最简单的缓解方案是在两次请求之间加一个随机延时比如time.sleep(random.uniform(1, 3))让请求节奏看起来像人。再进阶一点可以用代理 IP但对毕设来说完全没必要别把问题搞复杂。4.2 解析结果为空定位不到目标元素页面结构解析不出来百分之九十的原因是页面结构和你的选择器不匹配。58 同城的页面改版比较频繁网上很多老教程里的 class 名称早就失效了。我的排查套路是这样的先在浏览器里按 F12 打开开发者工具用 Elements 面板手动定位房源条目所在的 HTML 节点然后把鼠标悬停在目标元素上右键选择 Copy → Copy selector拿到最新的选择器路径最后把拿到的选择器替换到代码里。如果手动定位之后还是拿不到数据还有一个隐蔽的原因——页面可能不是服务端直接渲染的。有些列表页的数据是通过异步接口加载的直接 get 页面 HTML 只能拿到一个空壳。这种情况的解法是在浏览器开发者工具切到 Network 面板刷新页面找 XHR 类型的请求看看里面有没有返回房源 JSON 数据的接口有的话直接请求那个接口解析逻辑反而更简单。4.3 中文乱码编码设置不当Requests 拿到页面之后如果你发现中文全是乱码第一反应检查响应头里的编码声明。正常的设置方式是resp.encoding resp.apparent_encoding或者手动指定utf-8。但要注意apparent_encoding是通过内容智能检测编码的如果页面很大检测会慢所以我更推荐手动强制指定。还有一个小坑如果页面声明的是gb2312或gbk你强设成utf-8照样乱码这种情况下用resp.encoding gbk试试。另外在 Django 返回 JSON 的时候JsonResponse 默认已经做了中文转义处理不会出现乱码。真正容易乱码的是你把数据打印到控制台的时候Windows 的 CMD 默认编码可能是 cp936这时候要么改终端编码要么直接把数据写进文件再查看别在终端上死磕。4.4 数据库数据重复主键冲突跑了一次爬虫之后第二次再跑同样的采集任务发现程序报错说 unique constraint failed。原因就是详情页链接字段的唯一约束在起作用。正确做法不是去掉约束而是用bulk_create(objs, ignore_conflictsTrue)忽略重复插入。这样既保证了数据库里每条房源记录是唯一的又不会因为重复采集导致程序崩溃。如果你用的是 Django 的get_or_create方法也可以实现同样的效果但速度会比 bulk_create 慢不少数据量大时不推荐。4.5 图表不显示数据接口返回空列表页面打开之后图表一片空白最常见的原因是接口查出来的结果确实为空。排查思路按顺序来先用浏览器直接访问接口 URL比如http://127.0.0.1:8000/api/avg_price/看返回的内容是什么如果是空列表回数据库里查House.objects.count()确认是不是根本没有数据入库如果数据库有数据再看接口的过滤条件是不是把数据全过滤掉了比如exclude(price0)把所有条目的价格都滤掉了那就要回头检查清洗逻辑。还有一个很容易被忽略的坑Ajax 请求跨域。如果你的前端页面是直接用 file 协议打开的而不是通过 Django 的静态文件服务加载浏览器发起 Ajax 请求时会被同源策略拦截。解决办法很简单不要直接双击 index.html而是通过python manage.py runserver启动服务然后访问http://127.0.0.1:8000/static/index.html或配置 Django 的模板路由来访问页面。5. 从毕设到生产力进阶扩展思路5.1 数据维度升级接入大模型做智能房源描述标题里提到了大模型这里我就把这部分的延展讲透。传统的房源分析只停留在数字层面——价格、面积、户型、区域。但真正决定一套房子值不值得租的因素很多隐藏在描述文本里比如“靠近地铁”“随时看房”“精装修”“押一付三”“可短租”“中介勿扰”等等。这些非结构化的文本信息普通 SQL 查询是无法直接分析出价值的但如果借助大模型你就能做智能标签提取和评分。具体的实现思路是先写一个数据清洗管道把房源标题和描述整理成文本记录然后整理一批典型房源特征词作为 Prompt 模板的输入批量调用大模型接口让它输出结构化的标签 JSON。比如输入一段房源描述模型返回{near_subway: true, decoration: 精装, payment: 押一付三, short_rent: false}这些标签可以直接存回数据库新增字段然后前端就可以按“近地铁”“可短租”等条件做筛选了。这个扩展方案的技术难度其实不高难点全在 Prompt 设计和成本控制上。我建议先用少量样本测试 Prompt 的准确性确认稳定了再批量跑。另外要控制并发请求避免触发接口限流。这个功能加完之后你的毕设就从“数据分析系统”升级成了“智能租房分析平台”从创新性到实用性都上了一个大台阶。5.2 定时任务与增量采集爬虫写出来之后如果你希望系统自动每天采集一次数据观察租金变化趋势就需要给爬虫加定时调度。最简单的方案是用系统自带的 cron 或 Windows 任务计划程序每天凌晨跑一次采集脚本。更优雅的方案是把采集逻辑封装成 Django 的管理命令然后用python manage.py crontab add这类第三方库来管理或者接入 Celery Beat 做分布式定时任务。对于毕设来说我建议用最朴素的方案写一个 shell 脚本调用 Django 的 manage.py 自定义命令配合 cron 设定凌晨 2 点执行——这个时间点服务器负载低58 同城的更新也基本完成了。跑完之后再自动执行一次数据统计脚本这样每天早上打开系统看到的就是前一天的最新租房数据。5.3 部署上线与答辩演示注意点毕设验收的时候你不能只在自己电脑上跑万一答辩现场环境不对就尴尬了。我强烈建议提前把项目部署到一台云服务器上或者至少准备好一整套离线环境依赖清单。部署方案最省事的是用 Docker写一个 Dockerfile 把 Python 环境和项目代码打包再配合 SQLite 文件卷挂载一条docker-compose up -d就能跑起来。现场演示的应急预案也要做准备一份预采集好的静态数据万一现场网络不通导致爬虫跑不了至少还能展示数据分析和可视化页面。图表页面的数据流设计成既可以请求实时接口、也可以读取本地 JSON 文件这个双保险机制就是我在实际答辩中靠它救场的。最后的一点真心话这个项目前前后后我迭代了三版踩过的坑比我预想的多得多但也正是这些坑让我把整个技术栈吃透了。如果你是自己用来找房前期数据可能不够全跑一个礼拜采集之后积累的数据量会让你对所在城市的租金格局有个脱胎换骨的认识。如果是做毕设我建议不要只停留在“把代码跑通”的层面多想想查出来的数据能说明什么业务现象能支撑什么结论——这才是答辩时你区别于其他同学的底气。
返回列表