简介:这份资源是面向电商数据分析初学者与从业者的B2C平台数据集,来源于国内某知名电商网站,可用于用户行为研究、推荐系统实验与平台运营分析。压缩包共30个文件,以csv、sql、xml三类数据文件为主,分别对应结构化数据表、数据库导入脚本与半结构化记录,另附dmp数据泵文件、pdf说明文档和xls样例表,整体约14.83MB,便于快速导入本地环境复现分析流程。数据集覆盖用户浏览与购买记录、商品分类与价格库存、订单交易与支付方式、客户人口统计特征、评价反馈及流量来源等维度,可支撑关联规则、聚类、分类与预测建模等任务。目前已有1590人学习下载,适合作为课程设计、毕业项目或数据挖掘练手的实战素材,帮助读者从数据清洗、缺失值处理到业务洞察形成完整链路,理解电商场景下的用户留存与市场趋势分析。
1. 拿到一份 B2C 电商数据集,先别急着往模型里灌
一份命名混乱的国内某B2C电子商务网站的数据集.rar摆在面前,解压后大概率是订单流水、用户行为日志、商品维表这几类 CSV 或 SQL 导出文件。很多人第一反应是pd.read_csv然后直接丢进推荐或风控模型,结果跑出来的 AUC 惨不忍睹,回头查半天才发现字段里混着脱敏占位符、时间戳单位不统一、还有大量爬虫产生的脏会话。B2C 电商数据集的核心价值不在“大”,而在“行为链路完整”——从曝光、点击、加购、下单到支付,每一环的转化都能被还原,才谈得上做召回、排序、用户分层或流失预警。这篇文章面向的是手里已经拿到一份电商数据集、想把它真正用起来的算法和数据分析同学,我会按“先摸清结构、再清洗对齐、然后跑通一个最小可用模型、最后讲清楚哪些坑会让整条链路翻车”的顺序讲。适合谁?适合做推荐系统、用户增长、交易风控,以及需要拿真实行为数据做特征工程的人。不适合只想找现成 Kaggle 竞赛 baseline 的人,因为这类国内 B2C 数据集的结构往往比公开数据集更“野”。
2. 拆开压缩包先做三件事:字段测绘、粒度确认、时间轴对齐
2.1 用脚本把每个表的字段类型和空值率一次性打出来
拿到数据集最忌讳凭文件名猜内容。我一般会先写一个测绘脚本,把目录下所有 CSV/Excel 的字段名、dtype、非空计数、唯一值数量、前 3 行样例全部输出到一份报告里。这样做的目的是快速判断哪些表是事实表(订单、行为日志),哪些是维表(用户、商品、类目),以及哪些字段是脱敏后不可用的。
import pandas as pd import os from pathlib import Path def profile_dataset(root_dir): report = [] for file in Path(root_dir).rglob("*"): if file.suffix.lower() not in [".csv", ".xlsx", ".txt"]: continue try: # 大文件先读前 5000 行做类型推断,避免内存爆掉 df = pd.read_csv(file, nrows=5000, encoding="utf-8", on_bad_lines="skip") except UnicodeDecodeError: df = pd.read_csv(file, nrows=5000, encoding="gbk", on_bad_lines="skip") except Exception as e: report.append({"file": str(file), "error": str(e)}) continue for col in df.columns: report.append({ "file": file.name, "column": col, "dtype": str(df[col].dtype), "non_null": df[col].notna().sum(), "null_rate": round(df[col].isna().mean(), 4), "nunique": df[col].nunique(), "sample": str(df[col].dropna().head(3).tolist())[:120] }) return pd.DataFrame(report) profile = profile_dataset("./dataset") profile.to_csv("field_profile.csv", index=False, encoding="utf-8-sig") print(profile.groupby("file")["column"].count())这段脚本的关键参数有三个:nrows=5000控制内存占用,on_bad_lines="skip"防止个别脏行中断整个读取,encoding先试 utf-8 再回退 gbk 是国内数据集的常见编码现实。输出报告里重点看三列:null_rate超过 0.6 的字段基本可以放弃,nunique等于 1 的字段是常量列,sample里出现***、null、-1这类占位符的要单独标记。做完这一步,你手里应该有一张“哪些字段能用、哪些字段要修、哪些字段直接扔”的清单。
2.2 确认行为表的粒度:一行是一次曝光还是一次点击
B2C 数据集最容易翻车的地方是粒度混淆。行为日志表里,一行可能代表一次商品曝光(impression),也可能代表一次点击(click),还可能代表一次会话(session)聚合。如果不确认清楚,后面算 CTR 时分子分母全错。判断方法很直接:看时间戳字段的间隔分布,以及同一用户同一商品在短时间窗口内是否重复出现。
# 假设行为表有 user_id, item_id, event_type, ts 四列 behavior = pd.read_csv("./dataset/behavior_log.csv", parse_dates=["ts"]) # 看 event_type 的分布 print(behavior["event_type"].value_counts()) # 看同一 user-item 对在 1 秒内的重复次数 dup = behavior.groupby(["user_id", "item_id", "ts"]).size() print("同一秒内重复记录占比:", (dup > 1).mean()) # 看时间戳最小间隔 behavior = behavior.sort_values("ts") gap = behavior["ts"].diff().dt.total_seconds().dropna() print(gap.describe())如果event_type只有click一种,说明曝光数据缺失,你无法算真实 CTR,只能算点击量。如果同一秒内重复记录占比很高,说明数据可能是曝光日志且未去重。如果时间戳最小间隔是 0 秒且大量集中,说明是批量导入时打的时间戳,不具备行为时序意义。这些结论直接决定你后面能不能做序列建模。
2.3 把用户、商品、订单三张表的时间轴对齐到同一时区
国内电商数据集常见的时间字段有order_time、create_time、pay_time、event_time,格式可能是2023-01-01 12:00:00、1672531200(秒级时间戳)、1672531200000(毫秒级时间戳)混用。更隐蔽的坑是部分表用 UTC 存储,部分表用北京时间,直接 join 会导致订单和行为的先后关系错乱。
def normalize_time(series, unit="auto"): if pd.api.types.is_numeric_dtype(series): # 判断秒级还是毫秒级:大于 1e12 一般是毫秒 if series.max() > 1e12: return pd.to_datetime(series, unit="ms") return pd.to_datetime(series, unit="s") return pd.to_datetime(series) orders["order_time"] = normalize_time(orders["order_time"]) behavior["event_time"] = normalize_time(behavior["event_time"]) # 统一转成北京时间(假设原始有 UTC) orders["order_time"] = orders["order_time"].dt.tz_localize("UTC").dt.tz_convert("Asia/Shanghai")参数说明:unit="auto"的判断逻辑是先看数值量级,秒级时间戳约 1.6e9,毫秒级约 1.6e12,用 1e12 做分界是经验值。tz_localize和tz_convert的顺序不能反,否则会报错。对齐之后,用orders.merge(behavior, on="user_id")再过滤event_time < order_time,才能得到“下单前行为”这个正确的前置窗口。
3. 清洗与特征化:把原始日志变成模型能吃的宽表
3.1 处理脱敏字段和缺失值的四条规则
国内 B2C 数据集为了合规,用户 ID、商品 ID 常被哈希或替换成自增编号,手机号、地址等字段直接置空。这不影响协同过滤,但影响基于内容的特征。我一般按四条规则处理:第一,纯 ID 类字段保留原值,不做任何编码转换,因为哈希后的 ID 本身已经是离散标识;第二,数值型缺失用-1填充并额外加一个is_missing标志列,比填均值更安全;第三,类别型缺失统一填"__MISSING__"作为一个独立类别;第四,文本型字段如果缺失率超过 80%,直接整列删除,不要试图用模型补全。
def clean_features(df): for col in df.columns: if df[col].dtype == "object": df[col] = df[col].fillna("__MISSING__") elif pd.api.types.is_numeric_dtype(df[col]): df[f"{col}_is_missing"] = df[col].isna().astype(int) df[col] = df[col].fillna(-1) return df这段逻辑的核心是“缺失本身也是信息”。在风控场景里,一个用户没有填写年龄,可能比填了 25 岁更有区分度。加is_missing列的成本很低,但经常能带来几个千分点的 AUC 提升。
3.2 构造 RFM 和行为序列两类基础特征
RFM(Recency、Frequency、Monetary)是电商数据集最通用的用户特征,但很多人算错 Recency 的基准点。正确做法是以“数据集内最大时间”为基准,而不是当前系统时间,否则离线训练和线上推理的基准不一致。
snapshot_date = orders["order_time"].max() rfm = orders.groupby("user_id").agg( recency=("order_time", lambda x: (snapshot_date - x.max()).days), frequency=("order_id", "nunique"), monetary=("amount", "sum") ).reset_index() # 行为序列特征:统计用户最近 7 天的点击、加购、下单次数 behavior["date"] = behavior["event_time"].dt.date recent = behavior[behavior["event_time"] >= snapshot_date - pd.Timedelta(days=7)] seq_feat = recent.groupby(["user_id", "event_type"]).size().unstack(fill_value=0) seq_feat.columns = [f"recent7_{c}" for c in seq_feat.columns]参数说明:snapshot_date必须从数据里取,不能写死。pd.Timedelta(days=7)的窗口长度可以根据业务调整,快消品用 7 天,耐用品用 30 天。unstack(fill_value=0)保证没有行为的用户也有 0 值,而不是 NaN。
3.3 用 LightGBM 跑一个最小可用版本验证数据质量
特征宽表建好后,不要急着上深度学习。先用 LightGBM 跑一个二分类任务(比如预测用户未来 7 天是否下单),如果 AUC 连 0.6 都不到,说明数据清洗或特征构造有严重问题,换模型也没用。
import lightgbm as lgb from sklearn.model_selection import train_test_split from sklearn.metrics import roc_auc_score X = wide_table.drop(columns=["user_id", "label"]) y = wide_table["label"] X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) model = lgb.LGBMClassifier( n_estimators=500, learning_rate=0.05, max_depth=6, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42 ) model.fit(X_train, y_train, eval_set=[(X_val, y_val)], eval_metric="auc", callbacks=[lgb.early_stopping(50)]) pred = model.predict_proba(X_val)[:, 1] print("Validation AUC:", roc_auc_score(y_val, pred))关键参数:early_stopping(50)防止过拟合,num_leaves=31控制树复杂度,subsample和colsample_bytree做行采样和列采样。如果 AUC 在 0.7 到 0.8 之间,说明数据质量合格,可以进入调参和特征迭代阶段。如果低于 0.65,优先检查标签泄漏(比如特征里混入了未来信息)和时间对齐错误。
4. 避坑与排查:B2C 数据集最常见的五类翻车现场
4.1 标签泄漏:用下单后的行为预测下单
现象:离线 AUC 高达 0.95,上线后效果暴跌。原因:特征构造时把pay_time之后的行为也算进了窗口,或者把订单金额直接作为特征去预测是否下单。解决:所有特征的时间戳必须严格小于标签事件的时间戳,用event_time < label_time做硬过滤,并在特征表里保留一列feature_cutoff_time用于审计。
4.2 用户 ID 跨表不一致
现象:用户表有 10 万用户,行为表只有 8 万,订单表只有 5 万,join 后大量丢失。原因:不同表的用户 ID 生成规则不同,有的是注册 ID,有的是设备 ID,有的是哈希后的 ID。解决:先做 ID 映射表,用手机号或邮箱等稳定标识做桥接;如果没有稳定标识,只能以行为表的 ID 为主键,接受用户表覆盖不全的现实。
4.3 时间戳单位混用导致序列错乱
现象:行为序列模型训练时 loss 不下降,或者推荐结果明显违背时序。原因:部分表用秒级时间戳,部分用毫秒级,直接比较大小会把 2023 年的记录排到 2021 年前面。解决:统一用pd.to_datetime转换后检查min()和max()是否在合理范围内,毫秒级时间戳转完后年份如果是 1970 年附近,说明单位判断错了。
4.4 爬虫流量污染行为数据
现象:某些商品的点击量异常高但转化率为零,或者同一用户 ID 在 1 秒内产生上百次点击。原因:数据集里混入了爬虫或压测流量。解决:按用户维度统计点击频率,超过阈值(比如每秒 10 次)的会话标记为异常并剔除;同时检查user_agent字段(如果有)是否包含 bot 关键词。
4.5 类别特征基数爆炸
现象:LightGBM 训练时内存溢出,或者类别特征编码后维度上万。原因:商品 ID、店铺 ID 这类高基数类别直接做 one-hot。解决:高基数类别用目标编码(target encoding)或频率编码替代 one-hot,LightGBM 原生支持categorical_feature参数,但基数超过 1000 时建议先做频次过滤,把出现次数少于 10 次的类别归为"__RARE__"。
5. 从离线验证到线上可用:一个可复现的评估习惯
5.1 用时间切分代替随机切分做验证
电商数据集的分布随时间漂移,随机切分会让验证集里混入未来信息,导致 AUC 虚高。我一般用“前 80% 时间做训练,后 20% 时间做验证”的切分方式,并且验证集和训练集之间留 1 天的 gap,模拟线上“用历史预测未来”的真实场景。
cutoff = orders["order_time"].quantile(0.8) train = wide_table[wide_table["snapshot_time"] < cutoff] val = wide_table[wide_table["snapshot_time"] >= cutoff + pd.Timedelta(days=1)]参数说明:quantile(0.8)按时间分位切分,比固定日期更适应数据量变化。gap设为 1 天是为了避免特征窗口和标签窗口重叠。如果数据量足够,可以做多轮滚动验证,每轮往后推 7 天,看 AUC 的方差是否稳定。
5.2 监控特征分布漂移的三个指标
上线后模型效果下降,八成是特征分布变了。不需要复杂的监控系统,先盯三个指标:特征均值偏移(PSI)、空值率变化、类别特征的新增类别占比。PSI 超过 0.2 就告警,空值率突然升高说明上游数据管道有问题,新增类别占比超过 5% 说明业务侧有新品或新活动。
| 指标 | 计算方式 | 告警阈值 | 常见原因 |
|---|---|---|---|
| PSI | 分箱后计算分布差异 | > 0.2 | 促销活动、季节变化 |
| 空值率 | 当前空值数 / 总数 | 环比上升 50% | 上游埋点故障 |
| 新增类别占比 | 新类别样本数 / 总样本数 | > 5% | 新品上架、类目调整 |
5.3 一个我踩过的坑:别用全量数据做目标编码
目标编码(target encoding)在电商数据集里很好用,但必须只在训练集上拟合编码映射,再应用到验证集和测试集。我有一次偷懒在全量数据上算了商品的平均转化率,结果验证 AUC 比真实线上高出 8 个点,上线后直接翻车。正确做法是用KFold在训练集内部做交叉编码,或者用平滑后的贝叶斯编码。这个教训让我后来养成了一个习惯:任何涉及标签的统计量,先问一句“这个数字在预测时能不能拿到”,拿不到就老老实实做交叉。
希望帮到你。
本文还有配套的精品资源,点击获取