Python爬虫+Django+ECharts:58同城租房数据采集分析系统设计

发布时间:2026/10/9 8:42:06
Python爬虫+Django+ECharts:58同城租房数据采集分析系统设计 临近毕业季总能看到各种计算机毕业设计源码的帖子刷屏其中基于Python的58同城租房数据采集分析系统算是出镜率极高的一类题目。我的第一反应是这题确实适合用来做毕业设计。它不是那种纯粹增删改查的管理系统也不是那种闭着眼调包的深度学习应用而是把爬虫、Web框架、数据库、数据分析、可视化这条完整的技术链路串了起来一台普通笔记本就能跑通数据源现成且每天都有海量更新。做完之后你手上会有一套能演示、能讲数据故事、能应对答辩追问的成系统。这篇文章我就把这个项目的完整设计思路、核心实现细节、实操过程以及我踩过的坑一口气写透照着做你也能复现顺便还能理解为什么这么做。这套系统的核心价值在于它不是一个玩具而是一个真有数据在流动的产品——爬虫从58同城房屋出租页抓取真实的挂牌信息Django后端负责数据建模与接口输出前端通过ECharts把区域均价、户型分布、租金趋势变成可视化图表。适合两类人看一类是正在做或打算做相关毕设的学生另一类是想入门爬虫可视化这条线、想积累一个拿得出手的项目的自学者。我会把从零到一的全过程展开包括Requests爬虫怎么写、Django怎么接数据、数据怎么清洗、图表怎么出以及最常见的反爬拦截和乱码问题怎么处理。1. 项目整体设计与思路拆解1.1 核心需求解析这个系统到底要解决什么问题先不要把题目看复杂。把58同城租房数据采集分析系统拆开其实就三个动作采集、分析、展示。采集是把58同城某城市或者多城市正在挂牌的租房信息抓下来包括标题、小区、户型、面积、租金、朝向、楼层、发布时间、链接这些字段。分析是把采集到的数据做统计比如哪个区域平均租金最高、一居室和两居室价格差多少、租金和面积之间到底有没有线性关系。展示是把分析结果变成人话做成柱状图、地图、折线图、散点图让非技术背景的答辩老师也能一眼看懂。为什么选58同城而不是链家或者贝壳这套逻辑我在实际调研中踩过聊一下。链家和贝壳的反爬强度比较高登录校验、滑块验证、数据接口加密一个不少对学生项目来说光是过反爬就要耗掉大量时间而且容易触发风控可能你把IP都给封了数据还没抓全。58同城作为综合分类信息平台租房板块的页面结构相对规整反爬强度属于有一定挑战但不至于打不进去的水平比较适合做教学级的爬虫案例。更重要的是58上有大量个人房源和中介房源混在一起数据多样性好分析出来的房间差异更有真实感——这恰恰是毕业设计想要的效果有数据特色能讲出故事。分析模块的定位也不是高深算法而是要接地气。我见过很多毕设把数据分析写成两张图一张饼图一张柱状图完了。那其实更像数据统计不是分析。要做出分析感至少要回答几个具体问题热门租房区域排序、户型的租金中位数、面积与租金的相关系数、顶层与中间楼层的价格差异、近地铁房源是否真的更贵。这些问题需要代码去计算、去对比、去解释也就是把数据转化为结论这才是系统真正的竞争力。1.2 技术选型Django加Requests这套组合拳为什么能打技术栈看起来朴素但每一个选型都有推敲。后端框架用Django而不用Flask核心原因是Django自带一套完整的应用骨架——ORM、Admin后台、模板引擎、路由系统全都有你不必再花时间搭建基础设施可以把精力集中在数据逻辑上。对毕业设计来说时间就是最贵的资源。Django的ORM还能让你用Python类直接操作数据库表不用手写SQL这对我这种不想在答辩时被问懵的人来说非常友好。爬虫部分用Requests而不用Scrapy也是一个务实的选择。Scrapy确实功能强大异步并发、分布式扩展、内置中间件都有但学习曲线陡峭配置文件多对不少刚入门的人来说光是把Spider、Pipeline、Item这些概念理清楚就得花一周。Requests则是轻量级的HTTP客户端几十行就能完成一个合格的单线程爬虫逻辑透明不容易出诡异问题。说句实在话毕设要做的是稳定地完成不是炫技式地完成。你拿Requests写完答辩时老师问你每一行干什么你能清清楚楚讲出来这比拿一堆你都没吃透的框架代码更有说服力。如果后续真需要爬取海量数据再升级到Scrapy也不迟。前端可视化选用ECharts没有第二个纠结的必要。ECharts是百度开源的图表库文档是全中文的例子极其丰富柱状图、地图、词云都是几行代码的事。配合Django的模板渲染把后端传过来的JSON数据直接灌进option配置项页面加载完成图表自动绘制流畅度很好。这一套组合就是用最少的代码干最漂亮的活同时保证了项目的完整性和演示效果。数据库方面开发和演示阶段用SQLite就够单文件、零配置、不需要单独安装服务。但如果你的毕设要求看起来更企业化建议改成MySQLDjango的ORM对接MySQL也很简单改一下settings里的数据库配置和驱动就行。这一点我在后面实操部分会详细说。1.3 功能模块的清晰边界一个完整的系统代码不能糊在一锅粥里。我的划分方式是三大模块加一个入口爬虫采集模块、数据清洗与入库模块、数据查询与可视化模块加上Django项目作为统一入口。爬虫模块独立成一个子应用负责请求和解析产出标准化字典清洗模块做字段校验、类型转换、去重去错可视化模块通过Django的视图函数访问数据库聚合数据后回传模板渲染图表。模块之间只传递干净的数据互不渗透。这样的好处是调试时能快速定位问题——爬虫挂了看爬虫模块图表不输出看数据接口代码清晰答辩时画架构图也容易。2. 核心细节解析与实操要点2.1 Requests爬虫伪装、解析、翻页三板斧写爬虫之前先明确一个底线本地学习、做毕业设计、采集公开的非敏感数据频率要克制不要给对方服务器制造负担。58同城租房页的正常人类访问频率大概是一秒一次请求以内我建议设置2-3秒的随机延时绝不能把别人网站当接口压测。XPath解析里面有几个关键点要说透。58同城租房列表页的每个房源项通常会包含标题、地址、房间信息、租金这些元素我实践下来最稳定的做法是先定位每个房源的根节点再在根节点内部继续相对路径查找。示例列表页的房子条目可以这样解析import requests from lxml import html headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://bj.58.com/chuzu/, } session requests.Session() session.headers.update(headers) def parse_list_page(url): resp session.get(url, timeout10) tree html.fromstring(resp.text) items tree.xpath(//div[classcontent__list--item]) if not items: return [] result [] for it in items: try: title it.xpath(.//a[classcontent__list--item--title]/text())[0].strip() room_info it.xpath(.//p[classcontent__list--item--room]/span/text()) # room_info 通常返回 [2室1厅, 58平米, 南北] 这类列表 rent it.xpath(.//span[classcontent__list--item--price]/em/text())[0] result.append({ title: title, room: room_info[0] if len(room_info) 0 else , area: room_info[1] if len(room_info) 1 else , rent: float(rent), }) except (IndexError, ValueError): continue return result这段代码里有我反复调整出来的两个坑要提醒你。第一content__list--item--title的文本节点里可能混着换行和空格必须strip()掉第二租金的价格是放在em标签里、单位是元/月提取之后要转成float后面做分析才有数值意义。如果某些房源没有某个字段XPath取到的列表是空的直接[0]会抛IndexError所以要包一层try或先判断长度。翻页的逻辑更简单58同城的列表页URL往往带有/p{n}/这样的路径第一页通常是根URL第二页是/p2/第三页是/p3/。我在实现时是自动拼URL然后加上一个total_pages参数控制最大页数防止程序跑起来停不下来。翻页过程中还会遇到未知的动态内容加载如果你发现后一页的解析结果一直为空先手动打开浏览器F12看一眼真实URL结构不要死磕代码。2.2 Django集成模型设计、入库与Admin后台Django侧的关键是先定义好Model字段要和爬虫产出的字典对得上。我建议一开始就把字段设计的宽松一点宁可多定义几个空字段也不要之后天天改表结构。一个实用的House模型至少包含这些字段标题、区域、小区名如果有、户型、面积、朝向、楼层、租金、付款方式、发布链接、发布时间、创建时间。类型上租金用FloatField面积也用FloatField发布时间用DateTimeField其他一律用CharField长度留足。from django.db import models class House(models.Model): title models.CharField(max_length255, verbose_name标题) district models.CharField(max_length50, verbose_name区域, blankTrue) room_type models.CharField(max_length50, verbose_name户型, blankTrue) area models.FloatField(verbose_name面积(㎡), nullTrue, blankTrue) direction models.CharField(max_length10, verbose_name朝向, blankTrue) floor models.CharField(max_length50, verbose_name楼层, blankTrue) rent models.FloatField(verbose_name月租金(元), nullTrue, blankTrue) url models.URLField(uniqueTrue, verbose_name房源链接) pub_time models.DateTimeField(verbose_name发布时间, nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue, verbose_name入库时间) class Meta: verbose_name 租房信息 verbose_name_plural verbose_name db_table house def __str__(self): return self.title把url字段设为uniqueTrue是这个设计的点睛之笔。爬虫入库时用get_or_create(urlitem[url])就能天然去重——同一房源重复抓取不会产生重复记录这是我在实际项目中验证过最省事的数据去重方案。但也要提醒你58同城的详情页URL可能会有跟踪参数比如?PGTID...入库前要做一层URL净化只保留核心链接部分否则同一个房子的不同点击链接会被当成不同记录。Admin后台配置建议也做一小步在admin.py里注册House并自定义list_display、search_fields和list_filter。这样你进入Django的后台直接就能按小区名搜索、按区域过滤、按租金排序这对检查和清理数据非常有用。2.3 数据清洗数值提取、缺失处理与异常值拦截数据清洗这个环节很多学生觉得重复无聊实际恰恰是决定分析是否靠谱的核心。我从58采集到的原始字段常常长这样户型是2室1厅1卫、面积是58平米、租金是3200元/月这些都是字符串必须转成结构化数据。处理方式写个统一的清洗函数把所有字段过一遍import re def clean_area(val): match re.search(r(\d(\.\d)?), val) return float(match.group(1)) if match else None def clean_price(val): match re.search(r(\d(\.\d)?), val) return float(match.group(1)) if match else None def extract_room_type(val): # 只保留数字室数字厅的部分 match re.search(r(\d室\d厅), val) return match.group(1) if match else 未知清洗时还有一个容易被忽略的点缺失值不能直接删。比如朝向为空可能是房源本来就不写也可能是页面结构漏了解析。我建议统计每列的空值比例如果某个字段空值超过30%在分析里就降权处理而不是简单删行。异常值也要拦截比如月租金标成999999元、面积写成2000平米的明显错误超过合理区间后用中位数填充或直接标记为缺失避免影响后面的均值计算。3. 实操过程与核心环节实现3.1 从零搭建项目环境、虚拟目录与依赖先花五分钟把环境理干净。我推荐用Python 3.8以上的版本下载地址去Python官网安装时记得勾选Add Python to PATH不然后面命令行敲python会报不是内部或外部命令。然后创建虚拟环境是一个好习惯下面这段命令行足以让你在Windows下快速准备好环境mkdir rent_analysis cd rent_analysis python -m venv venv venv\Scripts\activate pip install django requests lxml pandas openpyxl为什么装pandas虽然Django本身也能做统计但pandas处理数据框、做分组聚合、算相关系数实在太顺手了。openpyxl是为了让你能够快速把清洗后的数据导出成Excel答辩的时候把Excel亮出来比口头空谈数据分析有说服力得多。装完之后django-admin startproject rent_project .创建项目python manage.py startapp crawler创建爬虫子应用python manage.py startapp analysis创建分析子应用。这里有个结构细节爬虫应用建议单独放因为它跟Web请求逻辑不耦合分析应用放聚合计算和视图模板和静态资源留给Django统一的templates/static目录。这样一个项目内核就完整了。3.2 爬虫运行与数据入库的完整流程爬虫我选择以管理命令的形式挂在Django下这样可以直接调用Django的ORM不需要额外写数据库连接。实现方式在crawler应用建一个management/commands/spider.py文件。听过这个模式的同学会觉得是烧火棍但你想想有了Django的环境你的脚本可以直接House.objects.create()不需要手动处理数据库会话这有多爽。一个基本逻辑from django.core.management.base import BaseCommand from crawler.spider import crawl_pages # 你自己封装的核心函数 from rental.models import House class Command(BaseCommand): help 爬取58同城租房数据并入库 def add_arguments(self, parser): parser.add_argument(--pages, typeint, default10, help爬取页数) parser.add_argument(--city, typestr, defaultbj, help城市缩写) def handle(self, *args, **options): city options[city] total_pages options[pages] data crawl_pages(city, total_pages) for item in data: House.objects.get_or_create( urlitem[url], defaults{ title: item[title], district: item.get(district, ), rent: item.get(rent), area: item.get(area), room_type: item.get(room_type, ), }, ) self.stdout.write(f成功入库 {len(data)} 条记录)管理命令的好处是最后你可以直接执行python manage.py spider --pages 20 --city bj跑完看终端输出。入库之后务必去Admin后台抽查几条记录确认数据真实落地且没有明显的解析错位。3.3 可视化图表与数据接口的落地方法数据要变成图表关键在接口。我习惯的做法是在analysis应用里写视图函数用Django的JsonResponse返回聚合结果。前端模板通过Fetch或Ajax请求这个接口拿到JSON后灌进ECharts。一个典型的区域均价接口长这样from django.http import JsonResponse from django.db.models import Avg from rental.models import House def district_stats(request): rows House.objects.exclude(rent__isnullTrue) \ .exclude(district) \ .values(district) \ .annotate(avg_rentAvg(rent)) \ .order_by(-avg_rent)[:15] data { districts: [r[district] for r in rows], avg_rent: [round(r[avg_rent], 2) for r in rows], } return JsonResponse(data)接口写好之后模板里引入ECharts的CDN写一段初始化代码基本就够了div idrent_chart stylewidth: 100%; height: 400px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script fetch(/api/district_stats/) .then(res res.json()) .then(data { var chart echarts.init(document.getElementById(rent_chart)); var option { title: { text: 各区平均租金TOP15 }, tooltip: {}, xAxis: { data: data.districts }, yAxis: {}, series: [{ type: bar, data: data.avg_rent }] }; chart.setOption(option); }); /script用这种方式你要做多少张图表就写多少接口和多少段option逻辑清晰、易于调试。比把全部数据一次性塞进模板里要干净得多。至于具体展示哪些图表建议至少有四张区域租金均价柱状图、户型占比饼图、租金分布直方图、面积与租金散点图。这四张图分别回答了哪里贵、什么户型多、价格分布、价格与面积关系刚好覆盖数据分析的基本面。如果还想加分可以做一张近地铁房源租金对比的横向条形图或者用词云展示热门小区名这类细节会让答辩的深度立刻不一样。4. 常见问题与排查技巧实录4.1 反爬拦截403、封IP与跳转验证页这是95%的人都会踩的坑。表现是直接跑Requests请求返回的状态码是200但解析出来的items列表为空——说明你被服务器友好地发到了一个验证页或者空页面。解决方法有两层。第一层是完善请求头我之前给的Headers里至少要包含User-Agent和Referer如果能加Accept、Accept-Language更好构造得像一个真实的浏览器请求。第二层是控制请求节奏把抓取间隔设置在2-3秒同时做一个随机的重试机制当某页解析结果为空时不要立刻重试先暂停10秒再试一次。如果你真的连续抓了几百页可能会有局部IP被临时限制。我的经验策略是一次运行的总请求数控制在200次以内跑完一次就休息几分钟再跑。不要一天24小时挂着脚本这既不道德也容易让源网站对你拉黑。在毕业设计里完成几百条数据样本已经足够支撑分析不需要贪多。4.2 解析结果错位XPath结果的空列表与错乱字段XPath解析有时候会遇到列表页结构变化或者某个字段是动态渲染的情况。58同城具体版本的页面布局我踩过无数次有时标题所在节点不是content__list--item--title而是多包了一层a或class名称里面多了空格。我的排查方法是先用Chrome DevTools打开页面选中目标元素右键→Copy→Copy XPath再用lxml实验性解析调试通了再写进正式代码。原始复制出来的XPath往往又长又脆建议自己拆成相对路径。解析时不要一次性把整个列表页什么都抓了先抓一条打印出来看看结构确认无误后再全量跑。4.3 编码乱码页面抓下来中文全是方块这是另一个高频坑。58同城页面编码通常是UTF-8但有时候响应头里的charset不明确Requests自动解码就出问题。解决办法很简单拿到response后手动指定编码再解析比如resp.encoding utf-8 tree html.fromstring(resp.text)如果还是乱码尝试resp.encoding gbk因为有些老站点用的是GBK。总之一句话先确认编码再谈解析。另外写入数据库时也要确认连接的字符集SQLite默认UTF-8问题不大但MySQL要指定charsetutf8mb4否则会出现Incorrect string value报错。4.4 数据库与依赖的小问题速查我把日常遇到的一堆小坑整理成一个速查表供你参考现象可能原因处理方法pip install 很慢默认源在国外使用国内镜像源如pip install -i https://pypi.tuna.tsinghua.edu.cn/simplemanage.py startapp报错未进入虚拟环境先venv\Scripts\activate激活环境页面加载不显示图表接口返回JSON异常浏览器F12看Network响应检查模型字段是否为空爬虫报ConnectionError网络波动或目标拒绝加异常捕获并暂停一段时间重试Django Admin中文乱码数据库字符集问题确保数据库/表字符集为utf8mb45. 项目扩展与个人实操体会5.1 把系统做得更有深度感的几种思路单一爬取统计展示容易给人一种工作量不够的感觉所以我特别建议你再做两件小事。第一件把数据导出功能加上——在Admin后台或前端页面提供一键导出CSV/Excel按钮。这不仅是功能完整性的加分项也是你在答辩现场演示数据分析结果可复用的最好证据。第二件加一个简单的租金预测模块。你可以用采集到的面积、区域、户型这三个特征去训练一个线性回归模型或者直接用sklearn包预测某个房源的合理租金范围。这个模块在你数据量超过500条之后效果立竿见影答辩时老师问你系统有什么分析深度你直接展示预测结果和实际挂牌价的偏差一下子就拉高了项目的技术层次。进阶一点可以做大模型结合用当前流行的本地或在线API对话式查询XX区两室户型的平均租金是多少系统先把问题解析成数据库查询条件再把查询结果用自然语言回复。这个思路在给你的标题里面出现了大模型关键词如果能在演示环节跑通就是独一无二的项目亮点。不过我建议先把基础功能全部搞稳定再去碰AI能力否则调试时间会失控。5.2 答辩与文档准备如何把这个项目讲得像拿过优秀毕设答辩里面最怕的不是被问住而是讲不清自己做了什么。我推荐你在写文档时采用提出问题→设计思路→实现过程→结果展示的叙述结构把上面说的每一个模块当成一个故事来讲。爬虫部分突出如何绕过反爬并在数据质量上做控制分析部分突出如何从原始数据中发现租房规律可视化部分突出如何让结论直观可见。文档里附上核心代码片段、图表截图以及几张数据库表结构的截图这些素材比任何墙角都硬。最后准备一段3分钟的现场演示词先刷新后台看到数据量再打开页面展示图表最后点击导出Excel一气呵成。5.3 我踩过几次坑之后的真心建议回头看看我最想提醒你的一句话是不要把时间浪费在把爬虫写的多优雅而要关注数据全不全、准不准。我的第一个版本爬虫上线之后抓回来2000条数据结果一分析发现有将近400条的租金字段是None因为58的某些房源展示的是面议而不是具体价格。我当时没做拦截导致后面所有均值、排序、相关性分析全都偏掉。后来我在清洗模块里加了一道必须包含租金数字的过滤分析结果立刻正常了。这就是经验在起作用——很多数据问题不是算法不行而是脏数据太多。第二个经验是关于爬虫要不要做分布式/多线程的。很多同学一上来就搞ThreadPoolExecutor结果把对方服务器打挂了自己的IP也封了毕设还没开始就结束。我建议老老实实单线程加延时跑几百条数据足够又快又稳。要显得有技术含量可以在爬虫设计里加一个请求策略的抽象类说明你可以扩展成代理池、并发抓取但演示时不用真的启动并发。以上这些就是我完成这个项目后的核心沉淀。如果按照这个思路走你会发现自己得到的并不仅仅是一份能交差的设计而是一套从数据获取到价值呈现的完整方法论。这套方法论以后给你换任何一个数据源、任何一个分析对象你都用得上。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询