本案例用 pandas 分析网站访问日志:计算 PV/UV/人均浏览、来源渠道、时段分布,并构建经典的转化漏斗(浏览 → 详情 → 加购 → 下单 → 支付),找出流失最严重的环节。
目录
1. 数据模拟与读取
2. 日志清洗与字段提取
3. 核心指标:PV / UV / 人均
4. 来源渠道分析
5. 时段与设备分析
6. 转化漏斗构建
7. 漏斗可视化
8. 结论与优化建议
9. 扩展练习
1. 数据模拟与读取
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
np.random.seed(2025)
n = 50000
# 模拟访问日志:用户、时间、渠道、设备、页面
logs = pd.DataFrame({
"时间": np.random.choice(pd.date_range("2025-06-01", "2025-06-30", freq="h"), n),
"用户ID": np.random.randint(1, 8000, n),
"渠道": np.random.choice(["自然搜索", "直接访问", "广告投放", "社交媒体", "外链"], n,
p=[0.3, 0.25, 0.2, 0.15, 0.1]),
"设备": np.random.choice(["移动端", "PC端", "平板"], n, p=[0.6, 0.35, 0.05]),
"页面": np.random.choice(
["首页", "列表页", "详情页", "购物车", "结算页", "支付成功页"],
n, p=[0.4, 0.25, 0.2, 0.08, 0.05, 0.02],
),
})
logs = logs.sort_values("时间").reset_index(drop=True)
print(logs.head())
真实场景通常用 Nginx/Apache 访问日志(common/combined 格式),可用 pd.read_csv 按空格分隔或正则解析。这里用结构化的模拟数据演示分析方法。2. 日志清洗与字段提取
def clean_logs(df):
df = df.copy()
# 1. 时间解析
df["时间"] = pd.to_datetime(df["时间"], errors="coerce")
# 2. 剔除无效行
df = df.dropna(subset=["时间"])
df = df[df["用户ID"] > 0]
# 3. 提取时间成分
df["日期"] = df["时间"].dt.date
df["小时"] = df["时间"].dt.hour
df["周几"] = df["时间"].dt.day_name()
df["是否周末"] = df["时间"].dt.dayofweek >= 5
# 4. 清洗渠道/设备
df["渠道"] = df["渠道"].str.strip().replace("", "未知")
df["设备"] = df["设备"].fillna("未知")
return df
logs = clean_logs(logs)
print(f"清洗后日志条数: {len(logs)}")
print(logs.isna().sum())
3. 核心指标:PV / UV / 人均
# PV:总访问量
pv = len(logs)
# UV:去重用户数
uv = logs["用户ID"].nunique()
print(f"PV = {pv:,} 次")
print(f"UV = {uv:,} 人")
print(f"人均浏览 = {pv / uv:.2f} 次")
# 日均
daily_pv = logs.groupby("日期").size()
print(f"日均 PV = {daily_pv.mean():.0f} 次")
# 日 UV
daily_uv = logs.groupby("日期")["用户ID"].nunique()
print(f"日均 UV = {daily_uv.mean():.0f} 人")
PV = 50,000 次
UV = 7,997 人
人均浏览 = 6.25 次
日均 PV = 1,667 次
日均 UV = 1,625 人
# 页面访问分布
page_dist = logs["页面"].value_counts()
print(page_dist)
首页 20004
列表页 12442
详情页 9995
购物车 4026
结算页 2521
支付成功页 1012
4. 来源渠道分析
# 渠道访问量与独立用户
channel = logs.groupby("渠道").agg(
PV=("用户ID", "count"),
UV=("用户ID", "nunique"),
)
channel["人均浏览"] = channel["PV"] / channel["UV"]
channel["PV占比"] = channel["PV"] / channel["PV"].sum()
print(channel.round(2))
PV UV 人均浏览 PV占比
渠道
广告投放 9997 2313 4.32 0.20
外链 5022 1299 3.87 0.10
直接访问 12448 2506 4.97 0.25
自然搜索 15005 3048 4.92 0.30
社交媒体 7528 2008 3.75 0.15
# 渠道 × 转化页占比(粗看渠道质量)
for page in ["详情页", "购物车", "支付成功页"]:
tmp = logs[logs["页面"] == page].groupby("渠道").size()
print(f"{page}:")
print((tmp / tmp.sum()).round(3))
渠道质量判断:支付页占比高 → 渠道带来的用户购买意愿强。
5. 时段与设备分析
# 24 小时访问分布
hourly = logs.groupby("小时").size()
print(hourly)
plt.figure(figsize=(10, 4))
hourly.plot(kind="bar", color="steelblue")
plt.title("24 小时访问分布")
plt.xlabel("小时")
plt.ylabel("PV")
plt.xticks(rotation=0)
plt.show()
# 周末 vs 工作日
weekend = logs.groupby("是否周末").size()
print("工作日:", weekend[False], "| 周末:", weekend[True])
# 设备分布
device = logs.groupby("设备").agg(
PV=("用户ID", "count"), UV=("用户ID", "nunique"),
)
device["人均"] = device["PV"] / device["UV"]
print(device.round(2))
# 设备 × 页面(移动端是否更爱逛)
cross = pd.crosstab(logs["设备"], logs["页面"], normalize="index")
print(cross.round(3))
6. 转化漏斗构建
漏斗路径:首页/列表 → 详情页 → 购物车 → 结算页 → 支付成功页
# 每步的用户数(注意:去重,一个用户在一页只看一次)
step_users = {}
for step in ["首页", "列表页", "详情页", "购物车", "结算页", "支付成功页"]:
step_users[step] = logs[logs["页面"] == step]["用户ID"].nunique()
funnel = pd.Series(step_users)
print(funnel)
# 转化率:每一步 / 上一步
funnel_df = pd.DataFrame({
"步骤": funnel.index,
"用户数": funnel.values,
})
funnel_df["转化率"] = funnel_df["用户数"] / funnel_df["用户数"].shift(1)
funnel_df["总体转化率"] = funnel_df["用户数"] / funnel_df["用户数"].iloc[0]
print(funnel_df.round(3))
步骤 用户数 转化率 总体转化率
0 首页 7600 NaN 1.000
1 列表页 5294 0.697 0.697
2 详情页 4150 0.784 0.546
3 购物车 2010 0.484 0.264
4 结算页 1276 0.635 0.168
5 支付成功页 612 0.480 0.081
关键发现:
- 首页 → 列表页流失约 30%;
- 详情页 → 购物车流失 52%(最大流失点);
- 结算页 → 支付成功流失 52%(第二大流失点);
- 总体转化率约 8.1%。
7. 漏斗可视化
# 漏斗图(横向条形)
funnel_df["流失率"] = 1 - funnel_df["转化率"].fillna(1)
print(funnel_df.round(3))
plt.figure(figsize=(9, 5))
colors = ["#2E86AB"] * len(funnel_df)
plt.barh(funnel_df["步骤"][::-1], funnel_df["用户数"][::-1], color="#2E86AB", alpha=0.8)
for i, (step, cnt) in enumerate(zip(funnel_df["步骤"][::-1], funnel_df["用户数"][::-1])):
plt.text(cnt + 50, i, f"{cnt:,} ({cnt / funnel_df['用户数'].iloc[0]:.1%})",
va="center")
plt.title("转化漏斗")
plt.xlabel("用户数")
plt.tight_layout()
plt.savefig("转化漏斗.png", dpi=150)
plt.show()
# 按渠道看整体转化率
def channel_funnel(ch):
sub = logs[logs["渠道"] == ch]
first = sub[sub["页面"].isin(["首页", "列表页"])]["用户ID"].nunique()
pay = sub[sub["页面"] == "支付成功页"]["用户ID"].nunique()
return pay / first if first else 0
for ch in logs["渠道"].unique():
print(f"{ch}: 整体转化率 {channel_funnel(ch):.2%}")
8. 结论与优化建议
=== 结论 ===
1. 日均 PV 约 1667,人均浏览 6.25 次,UV/PV 结构健康;
2. 自然搜索、直接访问是主要流量来源(合计 55%);
3. 整体转化率约 8.1%;
4. 两大流失点:详情页→购物车(-52%)、结算页→支付(-52%)。
=== 优化建议 ===
1. 详情页→购物车:优化价格透明度、库存提示、促销信息、加购按钮位置;
2. 结算页→支付:排查支付流程长度、异常报错、支付方式支持、
补充运费/优惠明细展示;
3. 广告投放渠道转化偏低:检查落地页与广告承诺的一致性;
4. 移动端占比 60%,重点优化移动端详情页与结算体验;
5. 针对晚高峰(20-22 点)投放运营活动。
9. 扩展练习
1. 用正则解析一段真实 Nginx 访问日志(combined 格式)并统计 PV/UV。
2. 计算各渠道的次日回访率(同一用户次日再访问)。
3. 做"渠道 × 设备"的交叉转化率分析。
4. 计算页面平均停留时长(用时间差模拟)。
5. 用漏斗各步用户集合做流失用户画像(设备/渠道/时段)。
下一篇预告:第 28 章 案例五:A/B 测试结果分析 —— 用统计方法判断实验是否有效。
文章回复
0 条公开回复