拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python电商销量预测全链路:采集→Django→Transformer→可视化

用 Python 做电商数据分析最容易卡住的不是某个库不会用而是采集、销量预测、Django 后端接口和可视化这几个环节没法串成一条完整链路。尤其当业务方真正要的是一个“输入商品编号输出未来 7 天预测销量曲线”的功能时你会发现爬虫拿到的字段和模型要求的字段对不上模型算出来的结果又不知道通过什么接口返回给前端。下面按全链路落地顺序拆一遍重点会放在 Transformer 销量预测模块如何和数据、后端、可视化连接起来。适合正在做电商数据分析项目或者想用 Python 完成一个端到端数据产品的读者。先说结论想把这个项目跑稳必须先把每一层的输入输出约定清楚后面调模型才有意义。1. 想做好电商销量预测先拆链路而不是先上模型电商销量预测看起来是一个算法问题实际是一个数据工程问题。很多人第一步就跑去研究 Transformer结果真正开始接数据的时候才发现自己连“每天有多少订单、退了多少、促销覆盖了哪几天”都还没有一份干净的表。所以与其先说模型不如先看完整链路由哪些环节组成。1.1 这套全链路到底解决什么问题一个完整的 Python 电商数据分析项目最终交付的不应该只是一个 notebook而应该是一个可以重复跑、可以查询、可以展示的数据服务。链路大概是这样数据采集/导出 - 数据清洗与补全 - Django 后端存储 - 特征窗口构建 - Transformer 模型训练与预测 - 接口返回 - 前端可视化展示这个链路解决的实际问题有三类每天怎么把新产生的销量数据稳定收进来而不是靠手动下载 Excel。怎么把历史销量、价格、点击、库存等字段整理成模型能使用的训练数据。怎么把预测结果通过 Django 接口提供给业务方或前端图表并持续更新。在这个项目里最核心的预测对象一般是一个 SKU 在某个时间粒度的销量常见做法是预测未来 7 天或 14 天。如果业务方只问“下周总体卖多少”用一个简单的统计基线可能就够了。一旦细化到单个 SKU还要考虑促销、节假日、周期性变化就需要更复杂的特征和模型。1.2 为什么单点 Demo 能跑串起来却很难我见过不少项目单看每个部分都没有问题。爬虫脚本能抓回数据Django 接口能返回 JSONTransformer 在公开数据集上也能训练。但把它们接在一起时问题一个接一个冒出来。最常见的三个断点日期粒度不一致。爬虫抓下来的是按小时或按订单级别的数据模型需要的是按天聚合如果采集阶段没有做好日期归约后面做特征窗口会非常混乱。字段名不统一。数据源里叫salesQtyPython DataFrame 里改成sales_qtyDjango ORM 模型里又是sales_qty前端图表却读取sales。每个环节一次改名排查成本就成倍增加。训练和线上预测的切分方式不一致。训练时有人随手用了随机切分模型看起来精度很高上线后预测却崩了。原因是测试集里包含了未来信息这不是模型问题是实验设计问题。所以我的习惯是动手之前先固定一套字段约定SKU 编号、日期、销量、价格、点击量、库存所有环节都用同一套命名。宁可写代码时多改几处也不要让每个阶段各搞一套。1.3 哪种项目适合这套方案哪种不适合这个方案适合以下情况有相对稳定的电商历史数据至少覆盖几十个 SKU且每天都有销量记录。业务需要把预测结果放进 Web 系统或报表不是停留在本地实验。想做一个能体现“数据分析 后端 深度学习 可视化”的完整项目。如果只有几百条历史记录我的建议很直接先不要用 Transformer也不要直接上 Django用平均值、移动平均或者简单回归先算一遍。很多销量序列在数据量少的时候统计基线已经能接近模型效果。还有一类商品不适合做精细预测长尾商品。比如一个月只卖两三件的冷门 SKU历史销量基本都是 0 或 1模型很难从这种稀疏序列里学到规律。这时候应该做商品分层只对销量稳定、样本充足的 SKU 做复杂模型预测长尾商品用简单策略兜底。另外要特别注意如果还没有合法、稳定的数据来源不要先花时间去写页面采集脚本。合规的数据来源比模型更重要后面会专门讲。2. 数据采集与存储先让数据稳定回流再谈建模很多项目失败在建模之前不在建模本身。数据没有稳定回流或者字段口径每天变化Transformer 再强也救不回来。做电商数据预测数据采集和存储的重点不是“抓得越多越好”而是“每天能不能拿到同一套干净数据”。2.1 先想清楚数据来源是否合规电商平台的数据不是一个可以随意抓取的对象。如果自己就是商家最优路径是使用后台导出功能或官方开放接口如果是学习研究建议找公开数据集或者自己造一批带日期规律的数据。官方网站公开的数据也要先看服务条款和 robots 文件确认是否允许自动化访问。我不建议在生产环境里做逆向页面、破解验证码、伪造请求头绕过限制这类操作。这样的方案不仅不稳定而且可能带来法律风险。很多页面上的数据本身并不适合批量采集被挡掉也是正常现象。如果请求返回空或者被拒绝第一反应不应该是换更复杂的绕过手段而是检查数据源是否允许访问、请求参数是否正确、接口结构是否已经变化。合规数据处理是可以写进项目经验的保留数据来源、记录采集时间、明确授权范围。这一部分做得清楚项目才能长期运行。2.2 Python 环境准备与依赖选型如果你还没有搭好 Python 环境先把运行环境确认好。Windows、macOS、Linux 都行一般推荐 Python 3.9 以上实际用 3.10 或 3.11 遇到的兼容问题更少。编辑器用 VS Code 会比较顺手也可以直接用命令行。这里给一套通用初始化方式python -m venv venv source venv/bin/activate # Windows 下是: venv\Scripts\activate pip install --upgrade pip pip install django djangorestframework requests pandas numpy openpyxl如果你已经习惯用 uv 管理 Python 环境也可以用uv venv和uv pip install django思路一样。为方便后面训练模型再补这些依赖pip install scikit-learn torch依赖安装过程中最容易出的问题不是库装不上而是虚拟环境没有激活就执行python manage.py。只要命令提示符前面没有(venv)就先不要往下跑。2.3 一个最小采集脚本的设计思路采集脚本不要一上来就抓整个平台先把一个最小样例跑通。这里以请求一个公开 JSON 接口为例只演示结构不指向任何真实网站import requests import pandas as pd resp requests.get( https://api.example.com/public/sales, params{date: 2025-01-01}, timeout10, ) print(resp.status_code) print(resp.text[:500]) data resp.json() items data.get(items, []) df pd.DataFrame(items) print(df.head())这段代码里最重要的一步是打印状态码和响应内容。很多人把resp.json()写在请求后面却没先确认返回里到底有什么字段一旦接口字段变化整个脚本就报 KeyError。对销量预测来说采集字段建议包含这些。字段名含义为什么需要sku_id商品 SKU 编号预测的最小对象stat_date统计日期时间序列的索引sales_qty销量目标变量price售价价格变化直接影响销量click_cnt点击量需求热度信号stock_qty库存缺货会导致销量为 0如果只有销量字段模型也能跑但很难区分“没货导致没卖”和“没人买导致没卖”。这两类零值含义完全不同。2.4 清洗、补全和落库的先后顺序采集完数据之后不要直接进模型也不要急着存到一个大表里。建议先做一层原始数据落地再做清洗转换最后进入正式的日销量表。一个常见问题是“某天没有记录”到底代表什么。对于销量数据没有记录不等于销量为 0可能是当天没有营业也可能是采集任务没有跑。处理时最好先生成完整的日期序列再关联实际销量。如果确认当天真的没有销售再填 0并在备注字段里记录来源。日期口径也要统一。电商业务如果按北京时间统计就固定按北京时间自然日聚合不要在数据里混入 UTC 时间。清洗完的数据我建议至少保存两份原始采集表记录原始 JSON 或原始记录方便出问题时回溯。日销量宽表按 SKU 和日期聚合供后续特征和模型使用。这样做的原因是建模阶段会发现各种奇怪的脏数据如果原始数据已经覆盖就很难重新追溯到底是采集出了问题还是清洗逻辑出了问题。3. Django 后端把数据、特征和预测服务串起来Django 在整个项目里承担的不只是展示一个管理后台更重要的是把采集结果、特征查询、模型预测和前端展示统一到一个地方。用 Django REST Framework 暴露接口比直接让 Flask 读 CSV 更适合持续迭代。3.1 创建 Django 项目和销量模型如果你还没有跑过 Django 项目先按最标准的流程建项目和应用。django-admin startproject sales_project cd sales_project python manage.py startapp forecast创建完 app 之后第一件事是把forecast加到INSTALLED_APPS。很多人漏了这一步后面执行迁移时提示找不到表。下面是一个相对简洁的 Django 销量模型设计from django.db import models class Product(models.Model): sku_id models.CharField(max_length64, uniqueTrue) name models.CharField(max_length255, blankTrue) category models.CharField(max_length128, blankTrue) created_at models.DateTimeField(auto_now_addTrue) class DailySales(models.Model): product models.ForeignKey( Product, on_deletemodels.CASCADE, related_namesales, ) stat_date models.DateField() sales_qty models.PositiveIntegerField(default0) click_cnt models.PositiveIntegerField(default0) stock_qty models.PositiveIntegerField(default0) price models.DecimalField(max_digits12, decimal_places2, nullTrue, blankTrue) class Meta: ordering [product, stat_date] constraints [ models.UniqueConstraint( fields[product, stat_date], nameuniq_product_stat_date ) ] indexes [ models.Index(fields[product, stat_date]) ]把 Product 和 DailySales 拆成两张表是因为商品信息变化少而每日销量会持续增长。拆开后商品改名或换类目不会影响历史销量记录。字段写好后执行迁移python manage.py makemigrations forecast python manage.py migrate在实际操作里如果后续要清理脏数据删除对象用DailySales.objects.filter(product__sku_idA1001).delete()这类批量操作比用 Python 写 for 循环一条条删除要快得多。3.2 用 Django REST Framework 提供查询和预测接口Django 的模型只负责数据存储给前端用的接口需要序列化器把 ORM 对象转成 JSON。可以先写一个历史销量查询接口# forecast/serializers.py from rest_framework import serializers from .models import DailySales class DailySalesSerializer(serializers.ModelSerializer): class Meta: model DailySales fields [stat_date, sales_qty, price, click_cnt]# forecast/views.py from rest_framework.generics import ListAPIView from .models import DailySales from .serializers import DailySalesSerializer class DailySalesListView(ListAPIView): serializer_class DailySalesSerializer def get_queryset(self): sku_id self.request.query_params.get(sku_id, ) queryset DailySales.objects.select_related(product).all() if sku_id: queryset queryset.filter(product__sku_idsku_id) return queryset再配置路由from django.urls import path from .views import DailySalesListView urlpatterns [ path(api/sales/, DailySalesListView.as_view()), ]启动开发服务后就可以请求python manage.py runserver 0.0.0.0:8000 curl http://127.0.0.1:8000/api/sales/?sku_idA1001这里有几个容易忽略的点select_related(product)能减少一次查询避免每个销量记录都重新查一次商品表。查询参数名称要前后端统一比如都用sku_id不要前端传skuId后端读sku_id。日期字段默认格式是2025-01-01传到前端后可以直接作为 ECharts 的类目轴。3.3 训练任务和在线接口必须分离一个常见的错误是在 Django 请求处理函数里现场训练 Transformer。模型训练耗时通常按分钟甚至小时计算如果 HTTP 请求需要等待这么久页面必然超时服务也会被大量请求拖垮。把训练和预测分开才能让服务稳定。稳妥的做法是数据采集和清洗单独用脚本或 Django management command 运行。模型训练用后台任务方式运行例如python manage.py run_forecast。Django 接口只加载已经训练好的模型文件或者直接读取提前生成的预测结果表。如果你只是做小规模学习项目可以在 Django 进程启动时加载一次模型然后在接口里调用预测函数。但要注意进程重启后会重新加载模型第一次请求可能会变慢。更简单的方式是模型训练完后把未来 7 天预测结果写入一张ForecastResult表接口直接查这张表。这样不但查询快还方便历史回溯。4. Transformer 模型销量预测用对了才有价值Transformer 在自然语言处理里很强但不代表所有预测任务都应该首选它。做电商销量预测时真正需要关注的是数据形态、特征窗口、训练验证切分和评估指标而不是模型名称够不够新。4.1 先判断“为什么用 Transformer而不是其他模型”Transformer 的核心结构是自注意力机制。它能把序列中任意两个位置联系起来所以对周期、节假日、促销这类依赖较远位置信息的场景有一定优势。比如过去 28 天里上周二做了一次大促这周二可能也会有一波高峰注意力机制可以学会把促销日和非促销日区分开。但它的前提是数据量够大、特征表达清晰。如果历史数据只有几千条特征只有销量一个维度Transformer 很容易过拟合。我一般建议先用移动平均、Prophet 或者 LightGBM 这类基线模型跑一遍如果 Transformer 没有明显优势就不值得为了“用新模型”增加维护成本。这里要特别提醒一点不要在销量预测任务里直接拿视觉领域的 Swin Transformer。Swin Transformer 是针对图像设计的层级注意力结构处理的是像素网格和电商销量这种一维时间序列不是一回事。搜索相关教程时要注意区分。4.2 数据窗口构造和训练验证切分Transformer 处理时间序列通常是把过去 N 天作为一个窗口预测未来 K 天。假设用过去 28 天预测未来 7 天输入张量形状大致是[batch, seq_len, feature_dim] [batch, 28, 6]其中feature_dim包括销量、价格、点击量、库存、节假日标记、周几等特征。一个简化的窗口构造逻辑是这样def make_windows(df, feature_cols, past_days28, pred_days7): df df.sort_values(stat_date).reset_index(dropTrue) X, y [], [] for i in range(len(df) - past_days - pred_days 1): past df.iloc[i: i past_days][feature_cols].values future df.iloc[i past_days: i past_days pred_days][sales_qty].values X.append(past) y.append(future) return X, y这里最关键的不是代码本身而是必须按日期顺序做切分。如果用随机切分测试集里很可能出现比训练集更早或同期的数据模型等于提前看到了未来评估结果会虚高。所以验证要用时间序列切分而不是普通 K 折from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits3) for train_index, test_index in tscv.split(X): pass另外价格、销量这类数值型字段最好做归一化。销量量级差异很大时模型会倾向预测大销量商品而忽略小销量商品。按 SKU 做 min-max 缩放或对销量做log1p变换后再训练都比较常见。4.3 一个轻量 Transformer 实现片段下面给一个适合销量预测的轻量 Transformer 编码器结构不是完整训练代码重点是展示输入输出。import torch import torch.nn as nn class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len128): super().__init__() self.pe nn.Parameter(torch.zeros(1, max_len, d_model)) nn.init.trunc_normal_(self.pe, std0.02) def forward(self, x): return x self.pe[:, :x.size(1)] class SalesTransformer(nn.Module): def __init__(self, feature_dim6, d_model64, nhead4, num_layers2, pred_len7): super().__init__() self.input_proj nn.Linear(feature_dim, d_model) self.pos PositionalEncoding(d_model) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadn
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门