如果你正在为毕业设计选型发愁又恰好对 Python 后端开发、数据采集和图表展示这几个方向感兴趣那“基于 Django 数据可视化 网络爬虫的安客居二手房屋信息采集系统”真的可以认真考虑一下。这个题目不是那种只写一个简单的 CRUD 增删改查而是把“爬虫拿数据 - MySQL 存数据 - Django 做后端 - ECharts 画图表”这条全链路完整串了起来既能锻炼工程能力答辩时又有非常直观的演示亮点。我最初拿到这个题时也有点犯怵但真正把项目撸完之后发现只要把每个环节的关键点理清楚整个系统其实是层层递进的并没有想象中那么复杂。这篇就完整分享一下我的设计思路、核心实现和踩坑记录希望能给你一个可以直接参考复现的完整方案。1. 项目总体设计为什么是 Django 爬虫 可视化的组合1.1 这个选题解决的三个核心问题毕设选题最怕的就是“大而空”。单纯做一个 Django 博客系统技术难度不够答辩老师随便一问就冷场只写爬虫又缺少 Web 端表现力不像一个完整的系统。而这个选题高明的地方在于它通过“二手房信息采集”这个具体业务场景把三个层次的核心问题一次全部解决掉了。数据获取层的问题如何从目标网站稳定、高效地抓取结构化的房源数据。这涉及到网络请求、HTML 解析、数据清洗和反爬规避属于爬虫工程的核心能力。数据存储层的问题如何将爬下来的零散数据规整到 MySQL 中并保证数据不重复、不丢失、可统计。这考验的是表结构设计和数据约束的功底。数据展示层的问题如何用 Django 的 MTV 架构把数据库里的数据变成可视化大屏、列表页面和统计报表供用户检索和浏览。这就回到了 Web 开发的主战场。更有意思的是这个系统做出来的东西不是“玩具”。只要爬虫定时跑一次数据库里就能积累几百上千条真实房源记录可视化图表能直观呈现出“哪个区域房源最多”“价格分布集中在哪里”“户型占比如何”。这种“数据是真的、分析是实的”的演示效果在很多只会做增删改查的毕设项目里非常突出。1.2 技术选型背后的取舍当时我在技术栈上纠结过一阵比如爬虫部分到底用 Scrapy 还是 requests BeautifulSoup可视化是直接用 Django 模板循环渲染还是引入 ECharts数据库要不要用 SQLite 省事。最后定下来的方案是这样的也说说我当时的思考。爬虫部分requests BeautifulSoup。Scrapy 确实功能强大异步并发、中间件、管道都封装好了但对于这个项目来说有点重。实际上二手房网站的详情页结构相对规整我们需要的字段也就是小区名、区域、户型、面积、朝向、楼层、总价、单价等十几个requests BeautifulSoup 足够搞定而且代码更直观答辩时也很好讲清楚每一步在做什么。如果启动多线程爬取反而容易被封 IP不如老老实实加延时慢慢跑。后端框架Django 3.x。Django 自带 Admin 后台、ORM、模板引擎和表单系统非常适合内容管理类的项目。这里要特别说一下 Django 的 MTV 模式很多新手容易把它和 MVC 混淆其实 MTV 就是 Model-Template-ViewModel 负责跟数据库打交道一个 Python 类映射一张表增删改查全部走 ORM不用手写 SQLTemplate 负责页面展示里面可以使用 Django 模板语法写变量和循环把后台数据动态填充到 HTML 中View 负责业务逻辑接收浏览器请求处理数据再返回渲染后的模板页面。这个模式的好处是把数据和展示彻底解耦了。比如你需要在首页加一个“近一年价格走势图”只要改 View 传给模板的数据模板和 ECharts 的 JS 代码基本不用大动。MTV 三个字母在面试和答辩里被问到的概率极高一定得理解透。可视化部分ECharts。为什么不直接在 Django 模板里用 HTML 表格展示因为毕设要的是“眼前一亮”。ECharts 是百度开源的一个纯 JS 图表库只要在后端接口里返回 JSON 数据前端就能渲染出柱状图、饼图、地图、折线图等大屏效果。它完全免费对中文支持极好二手房项目的“区域房源分布图”“价格区间分布图”用 ECharts 展示非常合适。数据库MySQL 8.0。相比 SQLiteMySQL 在企业里用得更多安装配置、索引优化、字符集处理也都是面试常考点。题目里明确要求用 MySQL那就顺理成章。后面我会详细讲到 Django 连接 MySQL 时的编码配置和常见坑。1.3 系统功能模块划分整个系统我划分成了四个主模块开发时按顺序推进每一步都有独立产出爬虫采集模块负责向目标站点发起请求、解析房源列表页和详情页、清洗数据字段最终输出规范化的 Python 字典列表。数据持久化模块通过 Django ORM 或批量 SQL 将清洗后的数据写入 MySQL同时做重复记录检测确保数据质量。数据可视化与展示模块包含首页可视化大屏、房源列表页、房源详情页、区域/价格/户型维度的统计图表以及按条件检索筛选。后台管理模块利用 Django 自带的 Admin 实现数据管理不用额外写太多代码就能对房源记录做增删改查。实际开发中我建议严格按这个顺序来先把数据打通再做展示不然会出现“可视化页面写完了但库里一条数据都没有”的尴尬局面。而且每完成一个模块就可以局部测试一次问题能早发现早处理。模块技术要点产出物爬虫采集requests、BeautifulSoup、time 延时房源数据结构化字典数据持久化Django ORM、MySQL 8.0、去重约束house_info 数据表可视化展示ECharts、Ajax、Django View/JsonResponse可视化大屏与图表后台管理Django Admin 自动生成数据管理系统2. 数据采集层爬虫系统的设计与实现2.1 爬虫的整体抓取逻辑安客居二手房的页面结构说简单也简单说麻烦也麻烦。简单在于列表页的每个房源卡片都结构类似解析规律容易总结麻烦在于详情页里字段比较多而且偶尔会出现“核心卖点”这种可选字段解析时必须考虑容错。我实现的爬虫按照“列表页 — 详情页”两级抓取策略运行。第一步是构造列表页 URL。目标网站的列表页 URL 往往带有一个城市标识和页码参数我把它整理成一个方法每次传页码进去就能生成新的链接。比如第 n 页的 URL 大概是https://xxx.com/{city}/list/pg{n}/再带上约定的请求头就可以发起 GET 请求。第二步是解析列表页。这里我用 BeautifulSoup 的find_all方法定位每个房源卡片。你在浏览器里按下 F12 检查元素会发现每一个房源卡片都由某个 class 为houseList-item或类似命名的div包裹卡片里有房源标题、小区名称、位置区域、户型、面积、总价这些简要信息同时还有一个详情页的链接。这一步的核心代码逻辑类似于import requests from bs4 import BeautifulSoup url https://xxx.com/{city}/list/pg{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 } resp requests.get(url.format(citychengdu, page1), headersheaders, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.houseList-item): title_tag item.select_one(.title) if title_tag: title title_tag.get_text(stripTrue) detail_url title_tag.get(href) print(title, detail_url)第三步是访问详情页把详细字段补齐。列表页已经能拿到不少信息了但像 “建造年代”“产权年限”“配备电梯”这类信息还得进详情页才能拿到。这一步要注意详情页的标签结构比如某个字段的值可能在div classinfo下面的多个span里。我建议先抓两个详情页下来把 HTML 保存成本地文件然后逐个字段去匹配比反复请求线上接口稳妥得多。2.2 反爬规避与请求头伪装二手房网站的防爬力度中等不登录也能看到大部分信息但直接拿默认的 requests 头去请求成功率很低。我在测试时就遇到了一直返回 403 的情况排查后发现是请求头没有伪装完整。日常实操里建议至少设置这样几个请求头参数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,image/avif, image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, Referer: https://www.xxx.com/ }User-Agent用来告诉服务器“我是一个真实浏览器”Referer表示请求来源。有些后端会根据 Referer 做防盗链判断少了它可能会被拒。如果发现还是被拦截可以在请求前随机睡眠 2 到 5 秒甚至集成fake_useragent这个库来自动切换 UA实测下来稳定很多。提示爬虫代码只能用于学习与研究不要对目标站点发起高频大规模请求否则既容易危害对方服务也可能给自己带来麻烦。本项目所有数据仅做技术演示建议用小批量数据完成功能验证即可。2.3 字段清洗与结构化处理爬下来的数据绝对不能直接入库因为网页里的文本往往带着空格、换行、单位甚至广告尾巴。以价格字段为例字符串可能是总价 198万也可能是198万元如果不处理存到数据库里就只能当字符串用没法参与排序和统计。我的清洗做法是这样总价用正则re.search(r(\d\.?\d*)万, raw_price)提取数字再转成 float。这样在可视化图表中才能把总价当数值做区间统计。单价同理提取(\d\.?\d*)元/平中的数字存成数值字段。面积从88.5㎡这种文本里提取浮点数注意中文状态下的㎡字符。朝向、楼层、装修这几个字段是选项类文本直接strip()去空格后保存如果缺失则填空字符串或 None。发布日期统一格式化为YYYY-MM-DD方便后续按时间筛选。处理之后再封装成一个clean_data(raw_item)函数返回干净字段组成的字典。这个字典就直接传给 Django ORM 的create或update_or_create方法。字段清洗这块建议自己写单元测试用几条脏数据验证不要光靠肉眼检查。3. 数据持久化MySQL 表设计与数据入库细节3.1 数据库与数据表结构设计数据清洗完之后接下来就是设计 MySQL 表。二手房源信息字段不算特别多但有几个点值得注意。我最初的设计是建一张大表把所有字段全塞进去后面发现这样做有几个隐患一是字段太多导致 ORM 映射类很长二是如果以后想扩充数据源表结构改动会很麻烦。不过作为毕设项目“一张主表 必要索引”已经够了关键是要把索引和字段类型设计好。我的house_info表设计如下CREATE TABLE house_info ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL COMMENT 房源标题, community VARCHAR(100) DEFAULT COMMENT 小区名称, district VARCHAR(50) DEFAULT COMMENT 所在区域, house_type VARCHAR(50) DEFAULT COMMENT 户型, area DECIMAL(10, 2) DEFAULT 0.00 COMMENT 建筑面积(㎡), total_price DECIMAL(10, 2) DEFAULT 0.00 COMMENT 总价(万元), unit_price DECIMAL(10, 2) DEFAULT 0.00 COMMENT 单价(元/㎡), orientation VARCHAR(20) DEFAULT COMMENT 朝向, floor VARCHAR(50) DEFAULT COMMENT 楼层信息, decoration VARCHAR(50) DEFAULT COMMENT 装修情况, publish_date DATE DEFAULT NULL COMMENT 发布日期, detail_url VARCHAR(500) DEFAULT COMMENT 详情页链接, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 采集时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT二手房源信息表;几个设计关键点面积、总价、单价这些字段数值型一定要用DECIMAL不要用FLOAT。FLOAT在 MySQL 里是近似值统计时可能出现精度偏差比如198.0变成197.99999。文本字段统一加DEFAULT 或允许 NULL避免因空值导致 ORM 写入报错。字符集一定要用utf8mb4不要用utf8。因为房源标题里可能会有特殊字符utf8在 MySQL 里其实是utf8mb3无法存储四个字节的字符一旦遇到就得报错。给district和house_type加上普通索引。可视化页面经常按这两个字段分组统计有索引后查询速度明显更快。MySQL 8.0 的安装配置需要注意一点安装时选择Use Legacy Password Encryption还是Use Strong Password Encryption for Authentication对老版本客户端有影响如果你用 Django 3.2 及以上版本配合 mysqlclient 库选强加密默认的 caching_sha2_password没多大问题。如果连接时报Authentication plugin caching_sha2_password cannot be loaded可以在 MySQL 的配置文件或命令行里把用户的认证插件改成mysql_native_passwordALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY yourpassword; FLUSH PRIVILEGES;3.2 Django 连接 MySQL 的配置与模型定义数据库建好之后在settings.py里配置数据库连接。这里我踩过一个坑直接复制网上的配置时OPTIONS里写的是charset: utf8mb4结果启动项目后所有中文写入报错。后来发现是 MySQL 连接时还需要同时指定初始化命令正确的配置长这样DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: anjuke_house, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES } } }注意NAME是数据库名需要在 MySQL 里先创建好init_command里的STRICT_TRANS_TABLES可以避免部分情况下非法数据被静默处理掉。如果你本地跑的是 Linux 服务器HOST记得改成对应地址PORT别写成字符串以外的类型。接下来用 Django 的 ORM 定义模型类文件写在models.py里from django.db import models class HouseInfo(models.Model): title models.CharField(max_length255, verbose_name房源标题) community models.CharField(max_length100, blankTrue, default, verbose_name小区名称) district models.CharField(max_length50, blankTrue, default, verbose_name所在区域) house_type models.CharField(max_length50, blankTrue, default, verbose_name户型) area models.DecimalField(max_digits10, decimal_places2, default0, verbose_name建筑面积(㎡)) total_price models.DecimalField(max_digits10, decimal_places2, default0, verbose_name总价(万元)) unit_price models.DecimalField(max_digits10, decimal_places2, default0, verbose_name单价(元/㎡)) orientation models.CharField(max_length20, blankTrue, default, verbose_name朝向) floor models.CharField(max_length50, blankTrue, default, verbose_name楼层) decoration models.CharField(max_length50, blankTrue, default, verbose_name装修) publish_date models.DateField(nullTrue, blankTrue, verbose_name发布日期) detail_url models.CharField(max_length500, blankTrue, default, verbose_name详情页链接) created_at models.DateTimeField(auto_now_addTrue, verbose_name采集时间) class Meta: db_table house_info verbose_name 二手房源 verbose_name_plural verbose_name def __str__(self): return self.title写完后依次执行python manage.py makemigrations python manage.py migratemigrate 之后 MySQL 里就会自动生成house_info表字段跟模型一一对应。此时可以在 Django shell 里测试 ORM 是否正常python manage.py shellfrom houseapp.models import HouseInfo HouseInfo.objects.all().count()如果返回 0 说明连接没问题可以把爬虫清洗好的数据批量灌进去。3.3 数据入库与去重策略数据入库最简单的方式是一条一条HouseInfo.objects.create(**item)但如果数据量到了几百上千条速度会明显变慢。我建议用bulk_create批量插入一次插入几百条再commit效率高很多。这里还要处理一个问题——重复数据。爬虫如果跑两遍同一条房源记录可能会重复入库所以我在模型设计时没有直接加唯一约束因为详情页 URL 可能有小小变动而是用代码来去重。去重逻辑可以这样写先把所有已存在详情页 URL 的集合拿出来然后用集合成员判断过滤新数据。假如有 1000 条存库记录这个 set 的内存占用并不大完全可行。from houseapp.models import HouseInfo existing_urls set(HouseInfo.objects.values_list(detail_url, flatTrue)) new_items [] for item in cleaned_data_list: if item[detail_url] in existing_urls: continue new_items.append(HouseInfo(**item)) if new_items: HouseInfo.objects.bulk_create(new_items, batch_size200)这段代码里有几个细节要讲一下values_list(detail_url, flatTrue)会返回一个只含detail_url的列表再转成 set 后查找效率是 O(1)bulk_create的batch_size参数控制单批次写入条数200 是一个比较安全的值能避免一次性过大的 SQL 语句。假如你希望修改已存在记录而不是跳过可以用update_or_create(defaults..., detail_url...)按自己的业务需求选。4. 后端接口与数据可视化Django MTV 模式落地4.1 视图与路由设计数据库里有数据之后后端的工作就是把数据变成接口和页面。Django 里每个请求都经过“URL 路由 — 视图函数 — 模板/JSON”这条链路。我在项目里创建了一个名为houseapp的 App然后针对不同页面设计了这样几组路由# houseapp/urls.py from django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), path(house/list/, views.house_list, namehouse_list), path(house/detail/int:pk/, views.house_detail, namehouse_detail), path(api/district_stats/, views.district_stats, namedistrict_stats), path(api/price_stats/, views.price_stats, nameprice_stats), path(api/area_stats/, views.area_stats, namearea_stats), path(export/csv/, views.export_csv, nameexport_csv), ]这里我特意把 API 路由和页面路由分开/api/开头的接口返回 JSON 数据专门给 ECharts 用普通页面返回 HTML。这种结构清晰也符合企业里“前后端分离”的习惯。首页视图的代码大概是这样from django.shortcuts import render def index(request): total_count HouseInfo.objects.count() districts HouseInfo.objects.values_list(district, flatTrue).distinct().count() avg_price HouseInfo.objects.aggregate(avgAvg(unit_price))[avg] or 0 context { total_count: total_count, district_count: districts, avg_price: round(avg_price, 2), } return render(request, houseapp/index.html, context)首页视图的作用是给顶部统计卡片提供几个核心数字。每次用户打开首页都会从数据库实时聚合由于数据量不大性能完全没问题。4.2 JSON 数据接口让 ECharts 有数据可以画ECharts 本身是前端库它要想画出图来必须拿到 JSON 数据。我写了三个接口分别是区域房源统计、总价区间分布和户型占比下面以区域房源统计为例from django.http import JsonResponse from django.db.models import Count def district_stats(request): data list( HouseInfo.objects .values(district) .annotate(totalCount(id)) .order_by(-total) ) return JsonResponse({data: data})这个接口返回的格式类似{ data: [ {district: 锦江, total: 156}, {district: 武侯, total: 132}, {district: 高新, total: 98} ] }ECharts 里的xAxis.data和series.data可以分别用data.map(item item.district)和data.map(item item.total)拿到。这里有一个容易出错的地方values(district).annotate(totalCount(id))出来的 district 如果是空字符串图表里也会显示一个空标签所以最好在数据清洗阶段就把未知区域置为未知。4.3 可视化大屏的布局与 ECharts 配置要点可视化大屏是这个项目最出彩的部分。我当时参考了很多大屏模板把首页设计成深色背景的长页面顶部放统计卡片中间是左右两栏图表底部放热力图或列表。这里我不建议你直接去抄一个复杂的大屏源码因为那些模板往往带了大量额外的依赖和炫技效果反而难以融入 Django 模板体系。我的做法是在templates/houseapp/index.html里用 Bootstrap 的栅格系统布局每个图表对应一个div再单独建一个dashboard.js文件负责初始化 ECharts。比如区域分布图的 JS 核心如下var chart echarts.init(document.getElementById(districtChart)); fetch(/api/district_stats/) .then(response response.json()) .then(data { chart.setOption({ tooltip: {}, xAxis: { data: data.data.map(item item.district) }, yAxis: {}, series: [{ type: bar, data: data.data.map(item item.total) }] }); });几个 ECharts 配置要特别注意图表容器必须有明确高度。很多新手把div设置了宽度但没设置高度结果图表死活不显示。ECharts 初始化时要求容器有width和height建议在 CSS 里给每个图表容器加height: 400px。setOption的时机。一定要等到容器在页面中可见后再调用如果放在display:none的容器里图表宽度会算成 0导致只显示一条竖线。解决办法是手动调用chart.resize()。数据接口报错时的容错。用fetch().then().catch()链式调用最好加一个.catch()提示后端出错别让整个页面白屏。如果有条件可以再加一个地图组件把二手房数据落到城市各区域地图上。ECharts 的地图数据需要额外引用 GeoJSON 文件Django 里可以直接把它放在static/echarts/map/目录用官方 registerMap 注册。4.4 CSV 导出功能与 StreamingHttpResponse系统的“导出数据”功能在毕设里特别加分但很多同学不知道怎么做。其实 Django 提供了一个非常适合文件导出的响应类型 ——StreamingHttpResponse它和普通的HttpResponse最大区别是不会一次性把所有内容加载进内存而是逐步把数据流式写给浏览器。在做 CSV 导出时尤其合适。搜索热词里有“django streaminghttpresponse 参数content_type和content-disposition”这里正好详细说一下我自己调试这两个参数的经历。import csv from django.http import StreamingHttpResponse def export_csv(request): def csv_iterator(): writer csv.writer(sys.stdout) yield \ufeff yield from ([标题, 区域, 户型, 面积, 总价, 单价] for _ in [0]) for house in HouseInfo.objects.all().iterator(): yield [house.title, house.district, house.house_type, house.area, house.total_price, house.unit_price] response StreamingHttpResponse(csv_iterator(), content_typetext/csv; charsetutf-8) response[Content-Disposition] attachment; filenamehouses.csv return response这里有一个非常关键的点CSV 文件用 Excel 打开时如果直接以 UTF-8 编码写出中文Excel 会出现乱码。解决办法是先在文件开头写一个\ufeffBOM 头。我在调试时最初漏了这一步生成的 CSV 用记事本打开没问题发给答辩老师后用 Excel 打开全是乱码排查了好久才发现是 BOM 的问题。再来讲这两个参数的作用content_typetext/csv; charsetutf-8告诉浏览器响应的内容类型是 CSV并且编码是 UTF-8。如果不加 charset某些浏览器可能默认按 ISO-8859-1 解码中文直接乱码。Content-Disposition里的attachment表示这是一个附件浏览器会触发下载而不是直接打开filename指定下载的文件名。注意如果文件名里有中文或空格必须用 RFC 5987 的格式filename*UTF-8houses.csv或者直接用 ASCII 文件名不然部分浏览器会截断文件名。在 Django 3.x 中StreamingHttpResponse的content_type参数是构造函数里第一个位置参数直接传字符串即可。Content-Disposition响应头则需要通过response[Content-Disposition]这样设置不能塞进构造函数这是官方文档里写得很清楚但很多人实际会搞混的地方。4.5 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, house_type, area, total_price, unit_price) list_filter (district, house_type, decoration) search_fields (title, community) ordering (-created_at,)这个后台对毕设答辩来说就是个管理界面截图的好素材也能方便你自己修改爬虫抓到的异常数据。如果想更好看一点可以简单替换 Admin 的base_site.html模板把默认的 “Django administration” 标题换成“二手房信息采集系统后台”细节管理能力拉满。5. 调试、部署与常见问题排查实录5.1 开发调试中的典型问题项目开发过程中我踩过的坑不少这里挑几个最有代表性的写出来希望能帮你少走弯路。第一个坑是 MySQL 时区报错。Django 连接 MySQL 后启动或插入数据时偶尔会报ValueError: MySQL server unavailable or has been shutdown这个报错信息比较有误导性实际原因可能是 settings.py 里TIME_ZONE和数据库时区不匹配。在我的项目里USE_TZ True而TIME_ZONE UTC导致 Django 写入 DateTimeField 时会把本地时间转成 UTC 再存进去。排查半天后我把设置改成了TIME_ZONE Asia/Shanghai同时 MySQL 连接串里加一句init_command: SET time_zone 08:00冲突才消失。第二个坑是中文乱码。这个问题分两处一处是 HTML 模板显示乱码另一处是爬虫抓到的网页源码本身就是乱码。HTML 乱码通常在模板头部加meta charsetutf-8就好爬虫乱码则要看响应头里的编码声明。我遇到过resp.encoding识别成 ISO-8859-1 的情况后来统一在requests.get后手动执行resp.encoding utf-8或者更稳妥一点从resp.apparent_encoding推断真实编码。不过这个推断开销稍大数据量大的时候反而拖慢速度大多数情况下直接指定 utf-8 就行。第三个坑是 Assets 静态文件 404。Django 开发环境中静态文件由django.contrib.staticfiles处理但进入生产部署后静态文件必须由 nginx 这类服务器处理。否则页面能打开CSS 和 JS 全部 404图表一个大屏全白。解决办法是在settings.py里设置STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles)然后在项目根目录执行python manage.py collectstatic把所有静态文件收集到staticfiles目录交给 nginx 做别名映射。这个操作需要先确保django.contrib.staticfiles在INSTALLED_APPS里。5.2 项目同步与代码讲解的准备工作如果你打算把这个项目当毕设代码讲解是重头戏。我建议你把整个项目分成四个部分给同学或答辩老师讲每部分控制在 5 到 8 分钟第一部分讲需求分析说明为什么选二手房数据从哪来系统有哪些角色。第二部分讲数据库设计现场打开 MySQL 或 Django Admin 展示house_info表结构解释字段和索引。第三部分讲爬虫重点讲请求头伪装、解析流程和清洗逻辑最好现场跑一次爬虫并抓几条数据展示入库过程。第四部分讲可视化切换 ECharts 图表并说明数据接口如何提供 JSON。Git 仓库方面建议用国内访问快的代码托管平台仓库里放一份 README写清楚环境版本、安装步骤、数据库初始化命令、爬虫启动命令和系统访问地址。这样可以避免答辩时临时配置环境浪费太多时间。5.3 Windows 环境下的部署参考waitress nginx毕设演示一般在自己电脑上跑部署这块可以不要求太高。但如果你想让老师通过局域网直接访问你的项目python manage.py runserver 0.0.0.0:8000的做法在演示时不够专业而且并发能力很差。比较轻量稳妥的方案是 waitress nginx。waitress 是一个纯 Python 的 WSGI 服务器在 Windows 下比 gunicorn 友好gunicorn 在 Windows 下无法直接运行。安装并启动pip install waitress waitress-serve --listen0.0.0.0:8000 houseproject.wsgi:applicationnginx 在 Windows 下也有官方 exe 版本配置一个 server 块把/static/指向staticfiles目录其余请求代理到 8000 端口server { listen 80; server_name 你的IP; location /static/ { alias C:/path/to/staticfiles/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样在局域网里用http://你的IP就能访问系统部署效果比裸 runserver 好看很多。部署时还要注意防火墙放行端口不然别人访问会直接超时。5.4 常见问题速查表最后整理一份我自己调试时反复遇到的问题速查表建议直接打印出来放在手边问题现象可能原因解决方法爬虫请求返回 403请求头不完整或被识别补全 UA、Referer加随机延时中文写入 MySQL 报错库表字符集不是 utf8mb4建库时指定CHARSETutf8mb4makemigrations找不到变化App 未注册或模型没导入检查 INSTALLED_APPS导入 modelsECharts 图表不显示容器无高度或数据未加载设置容器 height检查 fetch 接口CSV 导出用 Excel 打开乱码缺少 BOM 头文件开头写\ufeffDjango Admin 样式丢失未 collectstatic执行collectstatic并配置静态文件局域网无法访问防火墙拦截或绑定地址不对防火墙放行端口绑定 0.0.0.0MySQL 认证插件报错caching_sha2_password把用户认证改成 mysql_native_password插入数据时区不对USE_TZ 与本地时区不一致TIME_ZONE 设为 Asia/Shanghai5.5 一点实话这类项目的加分扩展方向如果你的毕设时间还有富余我强烈建议在现有基础上做两个低成本但效果显著的扩展。第一个是把爬虫改成可以切换城市抓取在后台做一个表单让用户输入城市名爬虫根据城市代码动态构造 URL。这样能从“单城市演示”升级成“多城市数据对比”视觉和功能都有了档次提升。第二个是加一个简单的一键爬取按钮在 Django 后台点一下就能触发爬虫脚本爬取完成后自动跳转到大屏页面演示时效果非常震撼。这两个扩展都不需要改动现有表结构只需在原有视图和模板上加少量代码。但要提醒的是要控制爬取范围和频率不建议做全站抓取少量数据足以满足演示和论文数据支撑需求。在整个项目的编码调试过程中我个人最大的体会是爬虫和可视化之间的“数据管道”才是这个项目的灵魂。很多人把注意力放在爬虫能爬到多少数据上其实真正决定系统质量的是你把网页文本变成数据库数字时做了多少细致的清洗以及你在展示层能否用结构化的方式把这些数字变成有说服力的图表。只要这一步打通了Django 的 MTV 模式、MySQL 的表设计、ECharts 的图表配置都会自然地被串联起来。最后再分享一个小技巧开发时给爬虫模块加一个单独的命令行入口比如python manage.py runspider --pages5这样不用反复打开 Django shell 就能快速调试和采集数据整个过程会顺畅很多。希望这篇能帮你把这个项目真正跑起来。 SEO 优化官网定制响应式建站教育培训建站