Django数据分析与可视化实战:从数据导入到ECharts图表完整链路 📅 发布时间:2026/9/9 5:54:27 👁 浏览次数: 这是一篇你可能搜索了很久都没搜到完整链路的技术文章。大多数教程只讲 Django 登录注册或者只讲数据分析很少有人把“Django 后端 数据分析逻辑 前端可视化图表”串成一个完整项目。如果你正好想做一个以数据分析与可视化为核心的 Django 实战项目下面这条路径可以直接跟着走。先说判断对绝大多数中小规模数据分析场景Django 配合数据聚合查询和 ECharts 可视化已经足够不需要一上来就上全套大数据组件。Django 要发挥优势的地方不是去跟 Spark 比计算性能而是把分析结果通过 Web 形式交付出去让非技术同学也能看到趋势、发现异常、辅助决策。这篇文章就围绕“手机数据”这个主题从模型定义、数据导入、聚合分析、接口设计到可视化页面完整跑通一个可扩展的项目。1. 这篇文章真正要解决的问题很多人学 Django 时都有一种感觉单个 API 会写ORM 基本操作也会但把数据和图表放在一起就不知道从哪下手。数据可能放在 Excel 里也可能在 CSV 文件中甚至是一张数据库表。问题不在于数据本身而在于怎样才能让数据变成浏览器里可交互的图表并且让图表背后的分析逻辑可复用、可维护。这里有三层典型痛点数据无法及时共享。分析结果停留在脚本输出或者 Excel 截图里业务人员看不到最新数字。分析逻辑和 Web 代码混在一起。视图函数里写大量计算改一个口径要翻半天代码最后数据对不上。可视化只停在静态图。没有交互筛选能力想从“品牌销量”下钻到“某个价位段”只能重新跑一遍脚本。本文会用手机数据做示例逐步解决这些问题。读完你能获得一条可复用的项目骨架Django 负责数据存储和接口服务层负责聚合分析前端 ECharts 负责交互展示。这套结构可以迁移到电商销售、用户行为、设备监控等场景。2. Django 数据分析可视化的核心概念2.1 数据分析在这里指的是什么在这个项目里“数据分析”不是指机器学习或复杂的统计建模而是面向业务指标的聚合分析。比如按品牌统计总销量和平均价格按价格区间统计机型数量和销量份额分析价格与销量的关系排序找出销量前十的机型。这些分析动作对应数据库操作就是GROUP BY、SUM、AVG、ORDER BY等。Django ORM 本身就支持这些聚合方法是效率最高的一种方式。只有当数据量非常大或聚合逻辑特别复杂时才需要引入 Pandas 或外部计算引擎。2.2 Django 请求处理流程一个 Django 请求的完整链路是浏览器发起请求 - URLconf 路由匹配 - 视图函数/视图类处理 - 调用服务层或 ORM 查询 - 返回 JSON 或渲染模板 - 浏览器图表展示在这个项目里核心链路是两条页面访问/返回渲染好的dashboard.html数据请求浏览器加载页面后用 JavaScript 请求/api/brand-sales/拿到 JSON 数据再交给 ECharts 绘制图表。为什么要分开因为图表需要数据数据和页面分离后后续如果要把接口提供给大屏或小程序直接复用即可。2.3 可视化方案怎么选当前常用的可视化方案有几类方案数据对接方式学习成本适合场景ECharts前端 JS 直接接收 JSON中低最推荐文档全中文社区活跃Chart.js前端 JS 接收 JSON低简单图表轻量场景PlotlyPython 生成 HTML 或 JSON中偏 Python 生态交互强大pyechartsPython 生成配置和模板中想在 Python 层控制图表自研 SVG/Canvas前端绘制高定制化程度极高的场景我选择 ECharts 的原因是它在实际项目里最常用而且不需要在 Python 层拼图表配置后端只提供标准 JSON前端负责展示职责清晰。2.4 数据流设计这个项目的数据流可以总结成一句话数据从 CSV 进数据库服务层把数据库聚合结果转成 JSON前端用 ECharts 渲染。CSV 数据 - Django 导入命令 - SQLite/MySQL 表 - ORM 聚合查询 - JSON 接口 - ECharts 图表 - 业务决策这样的好处是无论数据源是 Excel、爬虫结果还是手工录入只要进了数据库后面就能统一分析。3. 环境准备与项目初始化3.1 环境版本建议在开始之前建议先准备好以下环境Python 3.10 或更高版本Django 4.x 版本推荐使用 4.2 LTS 或更新的稳定版本SQLite 内置数据库即可本文示例不使用 MySQL 也可以运行前端图表使用 ECharts 5直接通过 CDN 引入无需安装额外依赖。Django 版本不必强求最新以稳定为准。下面的代码在 Django 4.2 和 Django 5.0 上都能运行。3.2 安装 Django 并创建项目建议在虚拟环境中进行避免污染系统环境。mkdir django-phone-analysis cd django-phone-analysis python -m venv venv # Windows 下激活方式为 venv\Scripts\activate source venv/bin/activate pip install django创建一个项目和应用django-admin startproject config . python manage.py startapp phones这里我故意把项目配置目录命名为config而不是默认的django_phone_analysis原因是项目名称短一些后续写 import 时会方便很多。创建完成后修改config/settings.py把phones应用加入INSTALLED_APPS# config/settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, phones, ]在项目根目录下创建templates文件夹然后在TEMPLATES配置中加入模板路径import os TEMPLATES [ { BACKEND: django.template.backends.django.DjangoTemplates, DIRS: [os.path.join(BASE_DIR, templates)], APP_DIRS: True, OPTIONS: { context_processors: [ django.template.context_processors.debug, django.template.context_processors.request, django.contrib.auth.context_processors.auth, django.contrib.messages.context_processors.messages, ], }, }, ]这样设置的意思是Django 会优先到项目根目录的templates文件夹查找模板文件。3.3 配置路由入口修改config/urls.py把默认首页指向 phones 应用# config/urls.py from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(, include(phones.urls)), ]同时创建phones/urls.py完整路由到后面章节再补充先建好文件即可。到这里项目的骨架已经搭好可以先把开发服务器跑起来确认没有问题python manage.py runserver不需要写任何业务代码只要访问http://127.0.0.1:8000/admin/能看到登录页环境就算通了。4. 数据模型与数据准备4.1 数据库表设计分析手机数据一个常见的业务表至少需要以下字段手机型号所属品牌销售价格年销量用户评分存储规格。Django 模型定义如下# phones/models.py from django.db import models class PhoneRecord(models.Model): brand models.CharField(品牌, max_length50) model_name models.CharField(手机型号, max_length100) price models.DecimalField(价格, max_digits10, decimal_places2) sales models.IntegerField(销量) rating models.DecimalField(评分, max_digits3, decimal_places2) storage models.CharField(存储规格, max_length50) created_at models.DateTimeField(创建时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) class Meta: db_table phone_record verbose_name 手机数据记录 ordering [-sales] def __str__(self): return f{self.brand} {self.model_name}使用DecimalField的原因是价格和评分这类数据对精度有要求如果使用浮点数后续做合计时可能出现类似0.1 0.2的精度问题。4.2 生成模拟数据正式环境下数据可能来自爬虫、业务数据库或 Excel。为了测试项目可以先生成一份 CSV 文件再用 Django 命令导入。下面是生成模拟数据的 Python 脚本放在项目根目录scripts/generate_phone_data.py# scripts/generate_phone_data.py import csv import random brands [华为, 苹果, 小米, OPPO, vivo, 荣耀, 魅族, 三星] storages [64GB, 128GB, 256GB, 512GB] with open(phones_data.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([brand, model_name, price, sales, rating, storage]) for i in range(1, 101): brand random.choice(brands) price random.choice([1999, 2499, 2999, 3499, 3999, 4999, 5999, 6999, 8999]) sales random.randint(5000, 100000) rating round(random.uniform(6.0, 9.9), 2) storage random.choice(storages) writer.writerow([ brand, f{brand} Phone {i}, price, sales, rating, storage ])这个脚本生成了 100 条手机数据包含品牌、型号、价格、销量、评分和存储规格字段内容和真实数据有差距但完全足够演示分析流程。4.3 编写导入命令直接在渲染页面时同步读取 CSV 文件不是个好方案因为每次刷新网页都会重新解析一次文件。更合理的做法是把数据一次性导入数据库后面分析时就通过数据库查询。在phones应用下创建management/commands目录结构如下phones/ management/ __init__.py commands/ __init__.py import_phones.py导入命令代码如下# phones/management/commands/import_phones.py import csv from django.core.management.base import BaseCommand, CommandError from phones.models import PhoneRecord class Command(BaseCommand): help 从 CSV 文件导入手机数据 def add_arguments(self, parser): parser.add_argument(csv_file, typestr, helpCSV 文件路径) def handle(self, *args, **options): csv_file options[csv_file] try: with open(csv_file, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: PhoneRecord.objects.update_or_create( model_namerow[model_name], defaults{ brand: row[brand], price: float(row[price]), sales: int(row[sales]), rating: float(row[rating]), storage: row[storage], } ) except FileNotFoundError: raise CommandError(f文件不存在{csv_file}) except KeyError as e: raise CommandError(fCSV 缺少必要列{e}) self.stdout.write(self.style.SUCCESS(手机数据导入完成))使用update_or_create可以保证重复运行时不会插入同样的数据它会根据model_name判断这条记录是否已经存在如果存在就更新。执行导入python manage.py makemigrations phones python manage.py migrate python manage.py import_phones phones_data.csv可以在 Django shell 里确认数据量python manage.py shell from phones.models import PhoneRecord print(PhoneRecord.objects.count()) print(PhoneRecord.objects.order_by(-sales).values_list(brand, model_name, sales)[:5])如果看到 100 条数据和销量前五的记录数据准备环节就完成了。5. 数据分析逻辑封装5.1 常见的分析指标拿到手机数据后可以分析哪些东西对一个手机销售分析项目来说最常用的指标是下面几个各品牌总销量对比看哪个品牌市场占有率高各品牌平均价格看品牌定位是高端还是性价比路线价格区间分布看产品定价结构价格与评分关系看价格高的产品是不是评分也更高销量前十机型看明星单品。这些指标中一部分适合用柱状图一部分适合用饼图一部分适合用散点图。5.2 不要在视图里堆代码新手最容易犯的错误是把分析逻辑全写到views.py里。这样短期内能跑通但一旦后续要增加指标或者同一个指标要在多个图表中使用视图函数就会变成上千行难以维护的代码。更好的做法是把分析逻辑独立到services.py模块中。视图只负责调用服务和返回数据不关心内部实现。# phones/services.py from django.db.models import Sum, Avg, Count from phones.models import PhoneRecord def get_brand_sales(): 各品牌总销量和平均价格 return ( PhoneRecord.objects .values(brand) .annotate( total_salesSum(sales), avg_priceAvg(price), countCount(id), ) .order_by(-total_sales) ) def get_price_distribution(): 按价格区间统计机型数量和销量 buckets [ (0, 1999, 1500元以下), (2000, 2999, 2000-2999元), (3000, 3999, 3000-3999元), (4000, 4999, 4000-4999元), (5000, 99999, 5000元以上), ] result [] for min_price, max_price, label in buckets: row ( PhoneRecord.objects .filter(price__gtemin_price, price__ltemax_price) .aggregate( total_salesSum(sales), countCount(id), ) ) result.append({ range: label, total_sales: row[total_sales] or 0, count: row[count] or 0, }) return result def get_rating_price_data(): 价格与评分散点图数据 return list( PhoneRecord.objects .values(price, rating, brand) [:300] )这里的设计思路是每个函数只负责一件事返回标准 Python 类型不直接依赖 HttpRequest 或 JsonResponse。这样做的好处是同样的函数稍加改动就能复用到其他接口。5.3 为什么用 Django ORM 而不是 Pandas有些教程喜欢把数据库数据全部查出来再转成 Pandas DataFrame 处理。这种方式在小数据量时没问题但会带来两个问题数据量稍大时内存占用会快速增长自己手写的分组聚合逻辑通常不如数据库原生聚合高效。因此本文的推荐策略是能用 ORM 聚合就先用 ORM只有遇到非常复杂的规则计算再考虑用 Python 代码或 DataFrame 处理。6. 视图、URL 与 JSON 接口6.1 视图函数设计视图层需要完成两个任务渲染页面返回 JSON 数据。在 Django 中可以用render和JsonResponse分别实现。# phones/views.py from django.http import JsonResponse from django.shortcuts import render from . import services def dashboard(request): 渲染分析看板页面 return render(request, phones/dashboard.html) def brand_sales_api(request): 各品牌销量与均价接口 data list(services.get_brand_sales()) return JsonResponse({code: 0, data: data}) def price_distribution_api(request): 价格区间分布接口 data services.get_price_distribution() return JsonResponse({code: 0, data: data}) def rating_price_api(request): 价格与评分关系接口 data services.get_rating_price_data() return JsonResponse({code: 0, data: data})这里统一返回了一个包含code和data的 JSON 结构。这看起来很简单但实际上是一个有用的接口约定通过code判断请求是否成功通过data承载业务数据。后续如果要加错误提示或分页信息不需要修改整个结构。6.2 路由配置完善phones/urls.py# phones/urls.py from django.urls import path from . import views urlpatterns [ path(, views.dashboard, namedashboard), path(api/brand-sales/, views.brand_sales_api, namebrand_sales_api), path(api/price-distribution/, views.price_distribution_api, nameprice_distribution_api), path(api/rating-price/, views.rating_price_api, namerating_price_api), ]完成之后可以启动服务器直接访问接口验证python manage.py runserver浏览器访问http://127.0.0.1:8000/api/brand-sales/会看到类似 JSON 数据{ code: 0, data: [ { brand: 小米, total_sales: 392707, avg_price: 4606.53, count: 14 } ] }接口正常返回说明从数据库到视图的链路已经打通。7. ECharts 可视化页面实现7.1 页面布局可视化页面采用简单的上下左右布局顶部放标题中间是图表区域。为了让代码足够简单本文使用一个页面展示多张图表不引入 Vue 或 React全靠 ECharts 自身的 API。创建模板文件templates/phones/dashboard.html!-- templates/phones/dashboard.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title手机数据分析与可视化/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script style body { margin: 0; padding: 20px; background: #f5f7fa; font-family: Microsoft YaHei, Arial, sans-serif; } h1 { text-align: center; color: #333; } .chart-container { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; } .chart-box { width: 560px; height: 400px; background: #fff; border-radius: 8px; padding: 10px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } .chart-box h3 { text-align: center; } /style /head body h1手机数据分析与可视化/h1 div classchart-container div classchart-box h3各品牌手机销量与均价/h3 div idbrandChart stylewidth:100%;height:320px;/div /div div classchart-box h3价格区间分布/h3 div idpriceChart stylewidth:100%;height:320px;/div /div div classchart-box h3价格与评分关系/h3 div idscatterChart stylewidth:100%;height:320px;/div /div /div script function fetchJson(url) { return fetch(url).then(response response.json()); } function initBrandChart() { const chart echarts.init(document.getElementById(brandChart)); fetchJson({% url brand_sales_api %}).then(res { const brands res.data.map(item item.brand); const sales res.data.map(item item.total_sales); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: brands, axisLabel: { rotate: 30 } }, yAxis: { type: value, name: 销量 }, series: [ { name: 销量, type: bar, data: sales, itemStyle: { color: #4fc3f7 } } ] }); }); } function initPriceChart() { const chart echarts.init(document.getElementById(priceChart)); fetchJson({% url price_distribution_api %}).then(res { const ranges res.data.map(item item.range); const sales res.data.map(item item.total_sales); chart.setOption({ tooltip: { trigger: item }, legend: { bottom: 0 }, series: [ { name: 销量, type: pie, radius: [30%, 70%], data: ranges.map((range, index) ({ name: range, value: sales[index] })) } ] }); }); } function initScatterChart() { const chart echarts.init(document.getElementById(scatterChart)); fetchJson({% url rating_price_api %}).then(res { const data res.data.map(item [item.price, Number(item.rating)]); chart.setOption({ tooltip: { trigger: item }, xAxis: { type: value, name: 价格 }, yAxis: { type: value, name: 评分, min: 0 }, series: [ { name: 手机, type: scatter, data: data, symbolSize: 10 } ] }); }); } initBrandChart(); initPriceChart(); initScatterChart(); /script /body /html这段模板完成了三件事通过 CDN 引入 ECharts定义三个带 id 的div容器分别承载柱状图、饼图和散点图页面加载完成后用fetch请求接口再把数据设置到对应图表中。7.2 在 Django 模板中使用 URL 反向解析上面代码中使用了{% url brand_sales_api %}这是 Django 模板的 URL 反向解析它会根据urls.py里的 name 自动生成对应的 URL。这样即使后续调整了路由地址页面里也不需要手工改。如果直接硬编码/api/brand-sales/本地开发没问题但项目一旦部署到子目录或者地址有变化所有页面链接都要改相当麻烦。8. 完整效果验证启动服务后访问http://127.0.0.1:8000/应该能看到一张包含三个图表的看板。为了确认图表是否正常这里给出一个标准的验证顺序确认接口能返回 JSON访问/api/brand-sales/如果看到数据说明后端没问题。确认模板能渲染打开首页右键查看源代码确认 ECharts 的 CDN 地址正常加载。确认 JavaScript 没有报错按 F12 打开开发者工具切到 Console 标签如果有报错首先看 fetch 请求的状态码和返回内容。确认图表容器有高度ECharts 图表最容易出现的问题是容器高度为 0导致图表不显示。上面的代码中每个图表容器都设置了height: 320px或height: 400px可以避免这个问题。从实践来看图表不显示 90% 的原因是容器没有高度其次是请求接口失败导致res.data为 undefined后面在写前端代码时要注意这两点。9. 常见问题与排查方法问题现象可能原因排查方式解决方案首页图表空白ECharts 容器高度为 0开发者工具查看图表容器尺寸给容器设置明确的 height接口返回中文乱码CSV 编码与读取方式不匹配查看 CSV 文件编码使用 utf-8-sig 读取保证后端返回 JSON 时ensure_asciiFalse或由 Django 自动处理导入 CSV 提示缺少列CSV 表头不一致打印 CSV 表头统一表头字段名数据重复没有去重逻辑查看表中重复记录使用 update_or_create 或先按业务唯一键去重价格精度不对使用 FloatField查看数据库存储值价格和评分改用 DecimalField图表显示英文或默认配色不符合要求未配置 ECharts 全局设置检查 ECharts 版本引入中文语言包或按需求配置 color8080 端口被占用本地其他程序占用使用 lsof 或任务管理器查看更换端口运行python manage.py runserver 8081使用 Pandas 计算后数据与 ORM 聚合不一致浮点数精度或空值处理差异对比聚合结果优先用 ORM 聚合特殊场景统一处理空值这里特别说明一下 CSV 读取时的utf-8-sig。使用 Excel 导出的 CSV 文件在 Windows 下通常带有 BOM 头直接用utf-8读取时第一列的列名可能会变成\ufeffbrand导致 DictReader 取不到brand字段。用utf-8-sig可以自动去除 BOM这也是为什么上面导入命令特意写成encodingutf-8-sig。10. 最佳实践与工程建议写一个能跑通的 Django 数据分析项目不难但要写成一个能上线的项目还是要花很多心思。下面结合实践给出一些建议。10.1 分析逻辑独立成服务层视图函数应该只做三件事接收请求、调用服务、返回响应。所有聚合规则都放到services.py或类似模块中这样在新增分析指标时不会影响已有功能也方便写单元测试。在项目比较大的时候还可以按业务域拆分服务模块比如services/brand.py、services/price.py避免单个文件越来越臃肿。10.2 数据导入与页面请求分离不要在用户访问页面时去解析 CSV 或 Excel 文件而是先通过命令或后台任务把数据写入数据库。原因是文件解析的耗时不确定如果文件较大页面请求会被阻塞用户只能一直等待。数据量较大时可以这样处理python manage.py import_phones phones_data.csv这样导入操作变成了一条明确命令什么时候执行、执行结果如何都更方便控制。10.3 给频繁查询加缓存各品牌总销量这种指标在数据不更新的情况下无论用户刷新多少次结果都是一样的。如果访问量较大每次都做全表聚合显然不划算。可以考虑使用 Django 缓存框架把聚合结果缓存一段时间比如 5 分钟from django.core.cache import cache from .models import PhoneRecord from django.db.models import Sum, Avg def get_brand_sales_with_cache(): data cache.get(brand_sales) if data is not None: return data data list( PhoneRecord.objects .values(brand) .annotate(total_salesSum(sales), avg_priceAvg(price)) .order_by(-total_sales) ) cache.set(brand_sales, data, 300) return data使用缓存时要注意数据更新后要及时清理或更新缓存否则用户看到的是旧数据。10.4 接口协议要稳定前后端交互的接口最好有固定格式。好的格式会让前端代码更简单比如统一使用{ code: 0, message: success, data: [] }前端只关心code 0时的数据如果接口出错统一的message也能直接显示给用户。这个约定虽然简单但能避免很多协作中的理解偏差。10.5 数据安全与权限数据分析和可视化项目中常常会涉及敏感业务数据。如果系统要对外提供访问必须做好权限控制。最简单的方案是使用 Django 自带登录认证把看板页面放在登录之后如果分析接口涉及更细粒度的权限可以引入 Django 权限系统或第三方权限框架。即使在本地调试也不要直接把数据库密码、密钥写在代码里建议使用环境变量或配置中心管理敏感配置。10.6 架构演进方向当数据量增长到 Django ORM 聚合已经吃力时可以有两条演进路线把聚合计算下沉到数据库给常用查询字段加索引优化 SQL必要时使用物化视图引入数仓或实时计算使用 ClickHouse、Doris 或 Spark 做离线分析Django 只负责展示结果。但这些都是后话。文章开头的判断在这里需要再次强调在数据量没有达到百万行级以上之前不要为了所谓的“大数据架构”而引入不必要的组件。先把 Django 数据分析和可视化链路跑通再根据实际瓶颈决定要不要引入更重的工具。11. 总结与后续学习方向这篇文章从零搭建了一个基于 Django 手机数据分析与可视化项目核心要点可以概括为三点一是数据先入库。CSV 先通过 Django 命令导入数据库后续分析全部走 ORM 聚合查询避免每次刷新页面都去解析文件。二是分析逻辑独立。服务层负责聚合计算视图层负责接口返回模板和 JavaScript 负责图表展示。各层职责清晰后续扩展新指标只需要加服务函数和接口。三是可视化交给 ECharts。后端只返回结构化 JSON前端负责渲染柱状图、饼图和散点图。数据和展示分离接口可以被页面、大屏、小程序等多端复用。接下来你可以做三个方向的延伸练习把示例数据替换成真实的手机销售或电商数据集调整字段和聚合逻辑做成一个自己业务场景下的分析看板增加筛选条件比如按品牌、价格区间、存储规格筛选后重新渲染图表让看板具备交互分析能力把项目部署到服务器上配置静态文件服务和 gunicorn 进程管理让看板真正给业务同学使用。如果你现在手头正好有一个数据分析项目不知道怎么落地成 Web 应用建议直接把本文的骨架拿过去改会比从零开始顺畅很多。