蔚来汽车销售数据可视化分析平台

项目概述

本文详细介绍一个基于 Django 开发的蔚来汽车销售数据可视化分析平台。该项目从零开始构建了一套完整的数据采集、数据存储、数据展示、数据分析和个性化推荐系统,帮助用户深入了解蔚来汽车的市场表现。

项目在线演示地址:小红书

项目 GitHub:主页Github


目录结构

Car_Sales/
├── Car_Sales/                      # Django 项目配置目录
│   ├── settings.py                 # 项目配置文件
│   ├── urls.py                     # 全局路由配置
│   └── db_config.py                # 数据库配置
├── sales/                          # 核心业务应用
│   ├── management/commands/        # Django 自定义管理命令
│   │   └── import_nio_sales.py     # 历史数据导入命令
│   ├── migrations/                 # 数据库迁移文件
│   ├── templates/sales/           # 页面模板(HTML)
│   ├── models.py                   # 数据模型(ORM)
│   ├── services.py                 # 业务逻辑层
│   ├── views.py                    # 视图函数
│   ├── urls.py                     # 路由配置
│   └── forms.py                    # 表单类
├── static/                         # 静态资源文件
│   ├── css/                        # 自定义样式
│   ├── js/                         # JavaScript 文件
│   │   └── dashboard/              # 可视化图表脚本
│   └── vendor/                     # 第三方库(ECharts、Tailwind、FontAwesome)
├── templates/                      # 全局模板
├── scraw.py                        # 数据爬虫脚本
└── manage.py                       # Django 管理入口

一、技术栈

1.1 后端技术

技术说明
Python 3主要编程语言
Django 5.2.4Web 框架
MySQL关系型数据库
Django ORM数据库操作抽象层
Django Auth用户认证系统

1.2 前端技术

技术说明
HTML5 + CSS3页面结构与样式
Tailwind CSS原子化 CSS 框架(本地 CDN 加载)
ECharts数据可视化图表库(本地静态资源)
Font Awesome图标库(本地静态资源)
Vanilla JavaScript前端交互逻辑

1.3 数据采集

技术说明
requestsHTTP 请求库
PyMySQLMySQL 驱动
JSON数据解析

二、数据模型设计

项目的核心数据模型定义在 [models.py](file:///d:/PycharmProjects/Car_Sales/sales/models.py) 中。

2.1 车辆销量数据(VehicleData)

class VehicleData(models.Model):
    brand_name = models.CharField("品牌", max_length=50, default="蔚来")
    series_id = models.IntegerField("车系ID", db_index=True)
    series_name = models.CharField("车系名称", max_length=100)
    month = models.IntegerField("月份", db_index=True)
    rank_no = models.IntegerField("排名", default=0)
    sales_count = models.IntegerField("销量", default=0)
    min_price = models.FloatField("最低价", null=True, blank=True)
    max_price = models.FloatField("最高价", null=True, blank=True)
    price_text = models.CharField("价格区间", max_length=50, blank=True)
    dealer_price = models.CharField("经销商价格", max_length=50, blank=True)
    image_url = models.URLField("图片", blank=True)
    description = models.TextField("简介", blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["series_id", "month"], name="unique_vehicle_month"
            )
        ]

设计亮点:

  • 使用 UniqueConstraint 确保同一车系同一月份只有一条全国销量记录,避免数据重复
  • db_index=True 对 series_id 和 month 字段建立索引,提升查询性能
  • 通过 likes 和 favorites 的反向关系,快速获取点赞数和收藏数

2.2 用户模型(CustomUser)

class CustomUser(AbstractUser):
    phone = models.CharField("手机号", max_length=20, blank=True)
    avatar = models.URLField("头像地址", blank=True)
    bio = models.TextField("个人简介", blank=True)

继承 Django 内置的 AbstractUser,扩展了手机号、头像和个人简介字段。

2.3 社交互动模型

class Favorite(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    vehicle = models.ForeignKey(VehicleData, on_delete=models.CASCADE)

class LikeRecord(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    vehicle = models.ForeignKey(VehicleData, on_delete=models.CASCADE)

class Comment(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    vehicle = models.ForeignKey(VehicleData, on_delete=models.CASCADE)
    content = models.TextField("评论内容")

class BrowseHistory(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    vehicle = models.ForeignKey(VehicleData, on_delete=models.CASCADE)

每个社交模型都使用 UniqueConstraint 确保用户对同一车型只有一条记录。


三、数据采集爬虫

数据爬虫脚本 [scraw.py](file:///d:/PycharmProjects/Car_Sales/scraw.py) 从懂车帝网站抓取汽车销量数据。

3.1 爬虫配置

RANK_DATA_URL = "https://www.dongchedi.com/motor/pc/car/rank_data"

TARGET_BRANDS = [
    {"brand_id": 112, "brand_name": "蔚来"},
    {"brand_id": 202, "brand_name": "理想"},
    {"brand_id": 195, "brand_name": "小鹏"},
    {"brand_id": 535, "brand_name": "小米汽车"},
    {"brand_id": 426, "brand_name": "极氪"},
]

支持抓取 5 个新能源汽车品牌的销量数据:蔚来、理想、小鹏、小米汽车、极氪。

3.2 核心爬取逻辑

def fetch_brand_month_sales(session, month, brand):
    payload = request_json(
        session,
        RANK_DATA_URL,
        params=build_rank_params(month, brand["brand_id"]),
    )
    data = payload.get("data") or {}
    return {
        "sales_list": data.get("list") or [],
        "latest_month": latest_month,
    }

构建请求参数:

def build_rank_params(month, brand_id):
    return {
        "aid": "1839",
        "app_name": "auto_web_pc",
        "city_name": "",
        "count": "100",
        "offset": "0",
        "month": str(month) if month else "",
        "brand_id": str(brand_id),
        "rank_data_type": "11",
        # ...
    }

3.3 数据入库

def replace_month_sales(conn, month, sales_list):
    rows = transform_sales_rows(month, sales_list)
    with conn.cursor() as cursor:
        cursor.execute("DELETE FROM sales_vehicledata WHERE month = %s", (month,))
        cursor.executemany("""
            INSERT INTO sales_vehicledata (...) VALUES (...)
            ON DUPLICATE KEY UPDATE ...
        """, rows)
    conn.commit()
    return len(rows)

采用 DELETE + INSERT 的方式实现数据的完整替换,确保数据是最新的。使用 ON DUPLICATE KEY UPDATE 处理重复键冲突。

3.4 运行爬虫

python scraw.py

程序会自动:

  1. 创建数据库(如果不存在)
  2. 计算目标月份范围(从 2023 年 1 月到上月)
  3. 按品牌、按月份依次抓取数据
  4. 将数据批量写入 MySQL 数据库

四、业务服务层

[services.py](file:///d:/PycharmProjects/Car_Sales/sales/services.py) 包含了项目的核心业务逻辑,包括数据筛选、可视化数据处理、推荐系统和预测系统。

4.1 数据筛选

def get_filtered_vehicle_qs(filters=None, include_all_brands=False):
    qs = get_vehicle_base_qs(include_all_brands=include_all_brands)

    month = filters.get("month")
    price_range = filters.get("price_range")
    series_keyword = filters.get("series_keyword")

    if month:
        qs = qs.filter(month=month)
    if series_keyword:
        qs = qs.filter(series_name__icontains=series_keyword)
    if price_range == "lt20":
        qs = qs.filter(min_price__lt=20)
    elif price_range == "20_30":
        qs = qs.filter(min_price__gte=20, min_price__lt=30)
    # ... 其他价格区间
    return qs

支持多维度筛选:月份、价格区间、车型关键字。

4.2 大盘总览数据

def get_overview_data(qs=None):
    rows = get_national_vehicle_rows(source_qs=qs)

    month_counter = Counter()
    series_counter = Counter()
    for row in rows:
        month_counter[row["month"]] += row["sales_count"]
        series_counter[row["series_name"]] += row["sales_count"]

    return {
        "summary": summary,
        "month_chart": month_chart,      # 月度销量趋势
        "hot_series": hot_series,        # 热门车型 Top10
        "series_compare": series_compare,# 累计销量车型对比
    }

4.3 竞品分析数据

def get_competitor_data(qs=None):
    brand_sales = Counter()
    brand_prices = {}
    for row in rows:
        brand_name = row["brand_name"]
        brand_sales[brand_name] += row["sales_count"]
        if row["min_price"] is not None:
            brand_prices[brand_name].append(row["min_price"])

    for brand_name, total_sales in brand_sales.items():
        avg_price = sum(prices) / len(prices)
        data.append({"brand_name": brand_name, "total_sales": total_sales, "avg_price": avg_price})
    return sorted(data, key=lambda item: item["total_sales"], reverse=True)

4.4 价格区间分布

def get_price_data(qs=None):
    buckets = {"20万以下": 0, "20-30万": 0, "30-40万": 0, "40万以上": 0}
    month_prices = {}

    for item in rows:
        price = item["min_price"]
        if price is None:
            continue
        if price < 20:
            buckets["20万以下"] += 1
        elif price < 30:
            buckets["20-30万"] += 1
        # ...

    avg_price_by_month = [
        {"month": month, "avg_price": sum(prices) / len(prices)}
        for month, prices in sorted(month_prices.items())
    ]
    return {"bucket_data": [...], "avg_price_by_month": [...]}

五、协同过滤推荐系统

推荐系统基于协同过滤算法,利用用户的收藏、点赞、评论、浏览记录构建隐式评分矩阵。

5.1 隐式评分构建

def _build_user_series_matrix():
    matrix = defaultdict(lambda: defaultdict(float))

    # 收藏 +4.0 分
    for user_id, series_id in Favorite.objects.filter(...).values_list("user_id", "vehicle__series_id"):
        matrix[user_id][series_id] += 4.0

    # 点赞 +3.0 分
    for user_id, series_id in LikeRecord.objects.filter(...).values_list("user_id", "vehicle__series_id"):
        matrix[user_id][series_id] += 3.0

    # 浏览 +1.0 分
    for user_id, series_id in BrowseHistory.objects.filter(...).values_list("user_id", "vehicle__series_id"):
        matrix[user_id][series_id] += 1.0

    # 评论 +2.0 分 * 评论次数
    comment_stats = Comment.objects.filter(...).values("user_id", "vehicle__series_id").annotate(total=Count("id"))
    for item in comment_stats:
        matrix[item["user_id"]][item["vehicle__series_id"]] += item["total"] * 2.0

    return matrix

不同行为赋予不同权重:收藏 > 点赞 > 评论 > 浏览。

5.2 相似度计算

def _cosine_similarity(source_vector, target_vector):
    overlap = set(source_vector.keys()) & set(target_vector.keys())
    if not overlap:
        return 0.0

    numerator = sum(source_vector[key] * target_vector[key] for key in overlap)
    source_norm = sqrt(sum(value * value for value in source_vector.values()))
    target_norm = sqrt(sum(value * value for value in target_vector.values()))

    return numerator / (source_norm * target_norm)

使用余弦相似度计算用户之间的相似程度。

5.3 推荐生成

def get_recommendations(user, limit=6):
    if not user.is_authenticated:
        # 未登录用户,返回热门车型
        popular_scores = _get_popular_series_scores()
        return [row_map[series_id] for series_id, _ in popular_scores.most_common(limit)]

    # 获取当前用户的评分向量
    target_scores = dict(matrix.get(user.id, {}))
    consumed_series_ids = set(target_scores.keys())

    if not target_scores:
        # 用户无行为记录,返回热门
        return popular_recommendations

    # 找到相似用户,推荐相似用户喜欢但当前用户未交互的车型
    candidate_scores = Counter()
    for other_user_id, other_scores in matrix.items():
        similarity = _cosine_similarity(target_scores, other_scores)
        if similarity <= 0:
            continue
        for series_id, score in other_scores.items():
            if series_id in consumed_series_ids:
                continue
            candidate_scores[series_id] += similarity * score

    # 如果推荐数量不足,补充热门车型
    if len(ranked_series_ids) < limit:
        popular_scores = _get_popular_series_scores(exclude_series_ids=...)
        ranked_series_ids.extend([...])

    return [row_map[series_id] for series_id in ranked_series_ids[:limit]]

六、销量预测系统

预测系统集成三种预测算法,预测未来 1-2 个月的销量。

6.1 简单移动平均

def _moving_average_forecast(sales_values, steps=2, window_size=3):
    series = list(sales_values)
    forecast = []
    for _ in range(steps):
        window = series[-window_size:] or series
        predicted = sum(window) / len(window)
        forecast.append(predicted)
        series.append(predicted)
    return forecast

取最近 3 个月的均值作为预测值。

6.2 加权移动平均

def _weighted_moving_average_forecast(sales_values, steps=2, window_size=3):
    series = list(sales_values)
    forecast = []
    for _ in range(steps):
        window = series[-window_size:] or series
        weights = list(range(1, len(window) + 1))
        denominator = sum(weights) or 1
        predicted = sum(value * weight for value, weight in zip(window, weights)) / denominator
        forecast.append(predicted)
        series.append(predicted)
    return forecast

越近期的月份权重越高,例如 window=[a,b,c],权重=[1,2,3],预测值 = (a×1 + b×2 + c×3) / 6。

6.3 线性趋势回归

def _linear_trend_forecast(sales_values, steps=2):
    count = len(sales_values)
    x_values = list(range(1, count + 1))
    y_values = list(sales_values)

    x_mean = sum(x_values) / count
    y_mean = sum(y_values) / count
    numerator = sum((x - x_mean) * (y - y_mean) for x, y in zip(x_values, y_values))
    denominator = sum((x - x_mean) ** 2 for x in x_values) or 1
    slope = numerator / denominator
    intercept = y_mean - slope * x_mean

    return [_safe_round(intercept + slope * (count + step)) for step in range(1, steps + 1)]

使用最小二乘法拟合线性趋势,预测未来销量。


七、前端可视化

7.1 ECharts 集成

项目使用本地化的 ECharts([echarts.min.js](file:///d:/PycharmProjects/Car_Sales/static/vendor/echarts/echarts.min.js)),无需 CDN。

7.2 图表初始化

// overview.js
const chart1 = echarts.init(document.getElementById('chart1'), 'dark');
window.NioApp.bindChartResize([chart1, chart2, chart3]);

使用深色主题 'dark',适配网站深色背景风格。

7.3 图表配置

chart1.setOption({
    backgroundColor: 'transparent',
    tooltip: window.NioApp.nioTooltip(),
    grid: window.NioApp.nioGrid(),
    xAxis: {
        type: 'category',
        data: monthData.map(item => item.month),
    },
    yAxis: {
        type: 'value',
    },
    series: [{
        type: 'line',
        smooth: true,
        data: monthData.map(item => item.total_sales),
        lineStyle: { width: 3, color: '#3b82f6' },
        areaStyle: {
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: 'rgba(59,130,246,0.45)' },
                { offset: 1, color: 'rgba(59,130,246,0.02)' }
            ])
        }
    }]
});

7.4 预测页面图表

预测页面展示历史销量和多种算法的预测结果:

// 历史销量(实线)
{
    name: '历史销量',
    type: 'line',
    smooth: true,
    data: historySales,
    lineStyle: { width: 4, color: '#3b82f6' },
    areaStyle: { ... }
}

// 预测销量(虚线)
{
    name: '简单移动平均',
    type: 'line',
    smooth: true,
    lineStyle: { width: 3, type: 'dashed', color: '#34d399' },
    data: [...]  // 前半部分为 null,后半部分为预测值
}

八、视图与路由

8.1 URL 路由

# sales/urls.py
urlpatterns = [
    path("", views.home_view, name="home"),
    path("dashboard/overview/", views.dashboard_overview_view, name="dashboard_overview"),
    path("dashboard/overview/api/", views.dashboard_overview_api_view, name="dashboard_overview_api"),
    path("dashboard/competitor/", views.dashboard_competitor_view, name="dashboard_competitor"),
    path("dashboard/user/", views.dashboard_user_view, name="dashboard_user"),
    path("dashboard/region/", views.dashboard_region_view, name="dashboard_region"),
    path("dashboard/price/", views.dashboard_price_view, name="dashboard_price"),
    path("recommend/", views.recommend_view, name="recommend"),
    path("predict/<str:series_name>/", views.predict_view, name="predict"),
    # ...
]

8.2 仪表盘视图

def dashboard_overview_view(request):
    filters = get_dashboard_filters(request)
    qs = get_filtered_vehicle_qs(filters)
    data = get_overview_data(qs)
    context = {
        "title": "大盘总览",
        "summary": data["summary"],
        "overview_initial_data": data,
    }
    context.update(get_dashboard_filter_context(filters))
    return render(request, "sales/dashboard_overview.html", context)

8.3 API 接口

def dashboard_overview_api_view(request):
    filters = get_dashboard_filters(request)
    qs = get_filtered_vehicle_qs(filters)
    data = get_overview_data(qs)
    return JsonResponse(data)

前端页面加载时使用初始数据(SSR),筛选条件变化时通过 API 异步获取新数据(CSR)。


九、项目功能模块

9.1 首页功能

  • 展示销量总览数据
  • 按车型关键字筛选
  • 按月份筛选
  • 按价格区间筛选
  • 支持销量、价格、车型名称排序
  • 分页展示
  • 个性化推荐列表

9.2 数据可视化

页面功能
大盘总览月度销量趋势图、热门车型柱状图、累计销量车型排行
竞品分析品牌销量与均价双轴图、品牌销量占比饼图
用户画像收藏偏好、点赞偏好、浏览偏好分析
车型排行全国车型销量排行榜
价格分析价格区间分布柱状图、月均最低价趋势

9.3 用户交互

  • 用户注册 / 登录 / 登出
  • 个人资料管理
  • 收藏车型
  • 点赞车型
  • 评论车型
  • 浏览历史记录
  • 密码修改
  • 账号注销

9.4 个性化推荐

  • 已登录用户:基于协同过滤的个性化推荐
  • 未登录用户:热门车型推荐
  • 行为不足时:自动回退为热门推荐

9.5 销量预测

  • 支持选择具体车型
  • 三种预测算法:简单移动平均、加权移动平均、线性趋势回归
  • 预测未来 1-2 个月销量
  • 预测结果对比图表

十、模板页面

10.1 大盘总览页面

<!-- dashboard_overview.html -->
{% extends "base.html" %}

{% block content %}
<!-- 汇总卡片 -->
<section class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
    <div class="panel p-5">
        <div class="text-sm text-slate-400">总记录</div>
        <div data-summary-card class="mt-3 text-3xl font-bold text-white">{{ summary.total_records }}</div>
    </div>
    <!-- ... 其他卡片 -->
</section>

<!-- 月度销量趋势图 -->
<section class="panel p-5">
    <div id="chart1" class="chart-box-lg"></div>
</section>

<!-- 热门车型图 -->
<section class="panel p-5">
    <div id="chart2" class="chart-box-lg"></div>
</section>
{% endblock %}

10.2 预测页面

<!-- predict.html -->
<section class="panel p-5">
    <h3>历史销量与预测走势</h3>
    <div id="trend-chart" class="chart-box-lg"></div>
</section>

<section class="panel p-5">
    <h3>预测结果对比</h3>
    <div id="compare-chart" class="chart-box-lg"></div>
</section>

<!-- 传递给前端的数据 -->
{{ predict_data|json_script:"predict-data" }}

十一、项目部署

11.1 环境要求

  • Python 3.10+
  • MySQL 5.7+ 或 MySQL 8.0+

11.2 安装依赖

pip install django pymysql requests

11.3 配置数据库

编辑 Car_Sales/db_config.py:

def get_mysql_config():
    return {
        "HOST": "localhost",
        "PORT": 3306,
        "USER": "your_username",
        "PASSWORD": "your_password",
        "NAME": "car_sales",
        "CHARSET": "utf8mb4",
    }

11.4 数据库迁移

python manage.py migrate

11.5 运行爬虫

python scraw.py

11.6 启动开发服务器

python manage.py runserver

访问 http://127.0.0.1:8000/ 查看网站。


十二、项目截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十三、总结

本项目完整实现了一个汽车销售数据可视化分析平台,涵盖了以下技术亮点:

  1. 完整的数据 pipeline:从爬虫采集到数据库存储到 Web 展示
  2. Django 全栈开发:使用 Django 框架完成前后端完整开发
  3. ECharts 数据可视化:多种图表类型展示销售数据
  4. 协同过滤推荐算法:基于用户行为的个性化推荐
  5. 多算法销量预测:移动平均、加权移动平均、线性回归
  6. 用户系统与社交互动:注册登录、收藏点赞评论评论
  7. 响应式前端设计:使用 Tailwind CSS 适配多种屏幕尺寸

希望本项目对你学习 Django Web 开发、数据可视化和推荐系统有所帮助!

更多推荐