news 2026/9/5 4:28:11

用Python复盘网约车跑单数据:空驶率、时段流水与接单策略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python复盘网约车跑单数据:空驶率、时段流水与接单策略

网约车司机每天跑单,看起来是“会开车就行”的体力活,但真正拉开收入差距的,往往是几个数据问题:早高峰该去哪个区域等单?午休时段是回家休息还是去机场排队?空驶返程的油钱到底吃掉多少流水?这些问题如果只凭感觉回答,很容易被直觉误导。我见过不少司机师傅,一天跑下来流水看着不低,但扣除油费、电费、平台抽成和车辆损耗之后,时薪甚至不如普通兼职。

“啊僵跑网约车的一天”这个选题,如果只写成司机流水账,信息量太薄;但如果把它变成一个数据复盘场景,价值就完全不同了。这篇文章要做的,就是把“跑网约车的一天”当作一个数据采集、清洗、分析和决策的过程,用 Python 把一天的行车轨迹、订单记录、等待时间和成本结构串起来,让网约车司机和数据分析学习者都能从中拿到可复用的思路。你可以把啊僵当成一个普通网约车司机,也可以把啊僵当成一个装了 GPS 记录仪的数据采样节点——区别只在于你从哪个角度看这份数据。

接下来,我们会从网约车司机的真实决策痛点出发,逐步拆解如何用 Python 复盘一天跑单数据,包括数据怎么来、怎么清洗、怎么算空驶率、怎么画出热力图和时序图,以及怎么把分析结果转化成第二天的跑单策略。

1. 这篇文章真正要解决的问题

网约车司机一天的工作,本质上是一个动态决策问题:在什么时间、去什么位置、等待什么样的订单、接受还是拒绝、跑完一单之后原地等待还是空驶去下一个热点。这个决策做得好不好,直接决定一天的收入和疲劳程度。

但在实际操作中,很多司机的决策方式是这样的:

  • 早高峰从家出门,先打开司机端看一眼热力图,哪个区域颜色深就往哪开。
  • 接到一单,送完乘客之后,如果原地等了几分钟没派单,就开始焦虑,然后随便选个方向开。
  • 午休时间舍不得收车,继续在市中心绕圈,结果绕了半小时就接了一单起步价。
  • 晚上收车时看一眼总流水,觉得还不错,但完全没算过空驶里程和油耗。

这样的跑法不是完全没道理,但问题是:所有判断都基于当下感受,而不是历史数据和结构化的复盘。你不知道自己一天到底空驶了多少公里,不知道哪个时段每小时的净收入最高,不知道哪个区域虽然单子多但平均客单价低,不知道等待时间到底吃掉多少成本。

所以这篇文章真正要解决的是三个问题:

  1. 怎么把一天跑单的过程变成结构化数据。不管你是手动记录还是用 GPS 轨迹,数据是分析的前提。
  2. 怎么用 Python 做一次完整的跑单数据复盘。包括订单流水表分析、空驶率计算、时段维度汇总、区域热力图可视化。
  3. 怎么把分析结果转化成实际的跑单策略。比如明天早上应该几点出车、优先去哪个区域、午休时段是否应该收车。

这个问题对两类人都有价值:

  • 网约车司机,尤其是刚入行的司机。很多人开了三个月车,对自己跑的每条街道都熟悉,但对“什么时候去哪里”没有数据概念。一份简单的复盘脚本,能帮你看清自己的时间花在哪、钱赚在哪。
  • 数据分析学习者。跑单数据非常适合做练手项目,它包含时间序列、地理位置、分类变量、成本计算,数据量不大但维度丰富,比单纯用 iris 数据集练手有意思得多。

你能从这篇文章里拿走的东西也很明确:一套可以直接运行的 Python 分析脚本,几张能说明问题的图表,以及一个可以长期复用的“跑单复盘”思路。

2. 跑网约车的一天,先拆成哪些核心概念

在写代码之前,先把“跑网约车的一天”这个概念拆成数据维度。只有把业务语言翻译成数据语言,后面写代码才不会乱。

2.1 订单流水的两个口径:流水与净收入

司机端显示的“流水”是乘客支付的总金额。但流水不是司机真正拿到手的钱。网约车平台通常会按一定比例抽成,不同城市、不同订单类型,抽成比例和计费规则会有差异。此外,司机还要承担油费、电费、车辆折旧和平台信息服务费。

从数据分析角度,建议至少维护两个字段:

  • total_amount:订单总金额,即流水。
  • estimated_income:司机预估到手收入(平台会显示)。

如果平台没有直接提供到手金额,可以根据自己所在城市的抽成比例粗略估算,但在分析时要备注“估算是基于固定抽成比例”,避免后续误以为这是精确数据。

2.2 空驶率:最容易忽略的成本指标

空驶率 = 空驶里程 ÷ 总行驶里程。

如果一天总行驶里程是 200 公里,其中空驶 60 公里,空驶率就是 30%。这个指标非常关键,因为空驶意味着你在烧油、耗电,但没有产生收入。

在实际跑单中,空驶主要发生在两种场景:一是送完乘客之后去往下一个接单点的距离,二是主动巡游找单的距离。通过 GPS 轨迹数据,我们可以比较准确地还原这两类空驶行为。

2.3 高峰与平峰:按小时聚合订单数据

网约车订单有非常明显的时间规律。早高峰集中在 7:00-9:00,晚高峰集中在 17:00-20:00,午休和下午是平峰。不同时段的订单量、客单价、平均等待时间都不一样。

做数据分析时,要把订单按小时分桶,然后计算每个小时的:

  • 订单数
  • 总流水
  • 平均客单价
  • 平均接单等待时间
  • 平均完成时间

这样你就能回答一个问题:我一天里哪个小时最值钱?是早高峰的第一单,还是午休时段的某一单?当你知道某段时间每小时的收入低于平均水平时,合理的决策可能是收车休息,而不是继续在路上绕。

2.4 区域热力:用地图理解订单密度

司机端的热力图是平台基于实时需求生成的,但那是“当下”的信息。如果要做长期复盘,更好的做法是把自己的历史订单点和空驶轨迹点全部落到地图上,看自己在哪些区域接单多、在哪些区域空驶多。

这里不需要用到特别复杂的地理信息系统。只要把订单起点、终点的经纬度记录下来,用散点图或密度图的方式画在地图上,就能大致看出自己的业务范围。

2.5 成本维度:油电费怎么分摊到每一单

一天的固定成本不会因为你不跑就不存在,但如果要做边际分析,至少要把直接成本算清楚:

  • 每公里油耗成本或电耗成本。
  • 每单的平均接驾距离。
  • 每单的平均完成距离。

比如你的车每公里成本是 0.5 元,一天跑了 200 公里,直接成本就是 100 元。如果你一天流水是 500 元,到手估计是 420 元(假设抽成 16%),那净收入大约是 320 元,时薪要看实际出车时长。如果不算空驶率,你根本不知道那 200 公里里有多少是在白跑。

3. 数据从哪来:跑单数据采集的几种方式

做数据分析的第一步是拿到数据。网约车司机一天的数据来源有三种方式,从易到难分别是:司机端账单手动导出、GPS 轨迹记录、手工流水账。

3.1 方式一:司机端账单导出

很多网约车平台的司机端 App 都提供了账单或行程记录功能。你可以按天查看每一笔订单的起始时间、起点、终点、金额、里程等信息。虽然不同平台导出的字段不一样,但通常具备以下核心字段:

  • 订单开始时间/结束时间
  • 起点位置
  • 终点位置
  • 订单金额
  • 里程(估算)
  • 乘客是否取消

这种方式的好处是数据权威、准确,缺点是很多平台没有提供一键导出 CSV 的功能,可能需要手动复制或者使用抓包工具(不建议轻易尝试非官方接口,存在合规风险)。最稳妥的办法是:每天收车后花两分钟,把行程记录里的关键字段录入到 Excel 或表格工具中。

3.2 方式二:GPS 轨迹记录

如果想分析空驶路线,光有订单记录还不够,因为订单记录只告诉你“在哪接单、在哪下车”,不告诉你“下车之后到下一单上车点之间你绕了多远”。

要解决这个问题,可以用手机上的 GPS 记录 App,比如“GPS Logger”这类工具,或者使用可穿戴设备。出车前打开记录,收车后停止,导出 GPX 或 KML 轨迹文件。这样你就有了一份连续的轨迹数据,可以在后续分析中识别停靠点、计算每段空驶距离。

不过要提醒的是:这类 App 会比较耗电,建议接上充电线,并且不要让手机在跑单过程中锁屏导致定位中断。

3.3 方式三:手工流水账

如果前两种方式都觉得麻烦,还可以退一步,只记录最核心的字段:接单时间、送完时间、订单金额、起点区域、终点区域。不需要精确的经纬度,只要区域名称和大致距离就够了。

手工账的好处是门槛最低,适合没有数据基础的人;缺点是工作量大,而且位置信息不精确。为了减轻记录负担,可以按“时段”记账,每个时段写一行汇总,而不是每单一行。

3.4 本文使用的示例数据说明

为了避免涉及具体平台和真实司机隐私,这篇文章后面使用的所有示例数据都是构造的演示数据。数据结构和字段设计尽量贴近真实场景,但具体数值不代表任何真实司机的一天。

我会按以下结构组织一个最小数据集:

  • order_time:订单开始时间
  • pickup_area:起点区域
  • dropoff_area:终点区域
  • amount:订单金额(流水)
  • income:预估到手金额
  • pickup_wait_min:从上一单结束到接到本单的等待时间
  • total_km:本单预估公里数
  • empty_km:上一单下车点开往本单上车点之间的空驶距离(估算)

这种结构既能模拟真实场景,又方便做聚合分析。

4. 环境准备:跑通网约车数据分析需要哪些工具

开始写代码之前,先准备好环境。整个分析过程不涉及复杂的大数据框架,一台普通笔记本电脑就够用。

4.1 Python 版本与依赖库

建议使用 Python 3.9 以上版本,但不是硬性要求,3.7 以上大多数代码也能跑。需要用到的库如下:

  • pandas:数据处理和分析,核心库。
  • matplotlib:图表绘制,画时间序列图和柱状图。
  • seaborn:基于 matplotlib 的高级可视化库,画分布图更方便。
  • folium:交互式地图可视化,用于把订单点和空驶轨迹画到地图上。
  • gpxpy:如果涉及 GPX 轨迹文件解析,会用到。

安装命令如下:

pip install pandas matplotlib seaborn folium gpxpy

如果你使用的是 Anaconda 环境,大部分库已经预装,只需要额外安装foliumgpxpy

conda install -c conda-forge folium pip install gpxpy

4.2 目录结构建议

为了让工程清晰,建议按下面的目录组织文件:

ride-analysis/ ├── data/ │ ├── orders_20250108.csv │ └── track_20250108.gpx ├── output/ │ ├── empty_rate.png │ ├── hourly_revenue.png │ └── order_map.html ├── analysis.py └── README.md

data目录放原始数据,output目录放分析结果,analysis.py是主脚本。如果后续要每天复盘,只需要替换日期对应的数据文件,不需要改代码逻辑。

4.3 没有真实数据时怎么调试

如果你手头没有真实跑单数据,可以在本地构造一份模拟数据来测试脚本。后面提供的代码中,会展示如何用随机数生成一份结构合理的示例订单表。这样你可以先跑通整个流程,再开始记录自己的真实数据。

5. 网约车跑单数据完整示例代码实现

现在进入核心部分:写代码。我会按照数据处理流程逐步展开,最后汇总成一个完整的 Python 脚本。

5.1 构造演示订单数据

先构造一份模拟数据。这份数据包含 36 笔订单,分布在早上 6 点到晚上 23 点之间,模拟一个比较常规的跑单节奏。

# 文件路径:ride-analysis/generate_demo_data.py import pandas as pd import numpy as np np.random.seed(42) order_time = pd.date_range("2025-01-08 06:10:00", periods=36, freq="35min") areas = ["科技园", "火车站", "新城区", "老城区", "大学城", "机场"] pickup_areas = np.random.choice(areas, size=36, p=[0.25, 0.15, 0.2, 0.2, 0.15, 0.05]) dropoff_areas = np.random.choice(areas, size=36) # 模拟金额:机场单更贵,其他区域单在 10-40 元之间 amounts = [] for p, d in zip(pickup_areas, dropoff_areas): if "机场" in p or "机场" in d: amounts.append(round(np.random.uniform(60, 120), 2)) else: amounts.append(round(np.random.uniform(10, 45), 2)) income_rates = np.random.uniform(0.78, 0.85, size=36) incomes = [round(a * r, 2) for a, r in zip(amounts, income_rates)] # 等待时间:早高峰与晚高峰较短,平峰较长 def gen_wait(i): hour = order_time[i].hour if 7 <= hour <= 9 or 17 <= hour <= 20: return int(np.random.uniform(2, 8)) return int(np.random.uniform(8, 20)) wait_minutes = [gen_wait(i) for i in range(36)] # 总计里程与空驶里程 total_km = [round(np.random.uniform(4, 18), 1) for _ in range(36)] empty_km = [round(t * np.random.uniform(0.1, 0.4), 1) for t in total_km] df = pd.DataFrame({ "order_time": order_time, "pickup_area": pickup_areas, "dropoff_area": dropoff_areas, "amount": amounts, "income": incomes, "pickup_wait_min": wait_minutes, "total_km": total_km, "empty_km": empty_km, }) df.to_csv("data/orders_demo.csv", index=False, encoding="utf-8-sig") print(df.head())

这段代码的逻辑不复杂:生成一个时间序列作为订单时间,随机分配起点和终点区域,根据区域类型生成不同量级的金额,再根据时间段生成符合高峰特征的等待时间。

说明一点:这里的金额、里程、等待时间都是随机生成的,目的是演示脚本流程。你真正使用时,把data/orders_demo.csv替换成自己的真实数据即可,字段名保持一致就行。

5.2 读取数据并做基础清洗

拿到订单数据后,先做基础清洗。这一步在真实场景中非常重要,因为手动记录的数据很容易出现缺失值、格式不一致、重复行等问题。

# 文件路径:ride-analysis/clean_data.py import pandas as pd df = pd.read_csv("data/orders_demo.csv", parse_dates=["order_time"]) # 1. 删除空值和重复行 df = df.dropna(subset=["order_time", "amount"]) df = df.drop_duplicates(subset=["order_time", "pickup_area", "amount"]) # 2. 将时间字段转换为 datetime 类型(如果读取时未解析) df["order_time"] = pd.to_datetime(df["order_time"]) # 3. 增加 hour 字段,方便按小时聚合 df["hour"] = df["order_time"].dt.hour # 4. 增加 duration 字段:模拟本单完成时间 # 假设每单平均耗时 20-35 分钟 import numpy as np np.random.seed(10) df["duration_min"] = np.random.randint(20, 35, size=len(df)) # 5. 增加空驶率字段 df["empty_ratio"] = df["empty_km"] / df["total_km"] print(df.dtypes) print(df.head())

清洗完成之后,你的数据框应该包含以下字段:

字段含义类型
order_time订单开始时间datetime
pickup_area起点区域string
dropoff_area终点区域string
amount订单流水float
income预估到手金额float
pickup_wait_min接单等待时间int
total_km订单总里程float
empty_km空驶里程float
empty_ratio空驶率float
hour订单小时int
duration_min预计完成时长int

5.3 计算赚不赚钱:流水、成本与净收入

接下来计算一天的总账。这是司机师傅最关心的问题,也是数据分析最有说服力的部分。

# 文件路径:ride-analysis/daily_summary.py import pandas as pd df = pd.read_csv("data/orders_demo.csv", parse_dates=["order_time"]) df["hour"] = df["order_time"].dt.hour # 总流水与总到手收入 total_amount = df["amount"].sum() total_income = df["income"].sum() # 总行驶里程,包括订单里程和空驶里程 total_km = df["total_km"].sum() total_empty_km = df["empty_km"].sum() # 平均每公里成本假设:纯电车约 0.2 元/公里,混动车约 0.5 元/公里 cost_per_km = 0.4 # 根据自己车型调整 total_cost = (total_km + total_empty_km) * cost_per_km # 净收入 = 到手流水 - 直接成本 net_income = total_income - total_cost # 工作时长:从第一单到最后一单 + 中途休息时间 work_hours = (df["order_time"].max() - df["order_time"].min()).seconds / 3600 print(f"总订单数: {len(df)}") print(f"总流水: {total_amount:.2f} 元") print(f"预计到手: {total_income:.2f} 元") print(f"总行驶里程: {total_km + total_empty_km:.1f} 公里") print(f"空驶里程: {total_empty_km:.1f} 公里") print(f"空驶率: {total_empty_km / (total_km + total_empty_km) * 100:.1f}%") print(f"直接成本: {total_cost:.2f} 元") print(f"净收入: {net_income:.2f} 元") print(f"毛时长: {work_hours:.1f} 小时") print(f"平均每小时到手: {total_income / work_hours:.2f} 元/小时")

输出结果大致是这样的结构:

总订单数: 36 总流水: 1068.53 元 预计到手: 871.92 元 总行驶里程: 412.3 公里 空驶里程: 101.7 公里 空驶率: 24.7% 直接成本: 164.92 元 净收入: 707.00 元

从这份结果可以明显看到,空驶率 24.7% 意味着四分之一的里程是在没有收入的情况下跑出来的。如果能把空驶率降低到 15%,假设总里程不变,那么有效里程就会增加,理论上能带来更多接单机会。

5.4 按小时聚合:找到一天里的赚钱时段

总账只能给一个整体感觉,真正能指导行动的是时间维度分析。接下来按小时做聚合。

# 文件路径:ride-analysis/hourly_analysis.py import pandas as pd import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei"] matplotlib.rcParams["axes.unicode_minus"] = False df = pd.read_csv("data/orders_demo.csv", parse_dates=["order_time"]) df["hour"] = df["order_time"].dt.hour hourly = df.groupby("hour").agg( order_count=("amount", "count"), total_amount=("amount", "sum"), avg_amount=("amount", "mean"), avg_wait=("pickup_wait_min", "mean"), total_km=("total_km", "sum"), total_empty_km=("empty_km", "sum"), ).reset_index() # 计算每小时空驶率 hourly["empty_rate"] = hourly["total_empty_km"] / (hourly["total_empty_km"] + hourly["total_km"]) print(hourly.sort_values("total_amount", ascending=False)) # 绘制每小时流水柱状图 fig, ax1 = plt.subplots(figsize=(12, 5)) ax1.bar(hourly["hour"], hourly["total_amount"], color="#4C72B0", label="每小时流水") ax1.set_xlabel("小时") ax1.set_ylabel("流水金额(元)") ax1.set_xticks(range(6, 24)) ax2 = ax1.twinx() ax2.plot(hourly["hour"], hourly["order_count"], color="#C44E52", marker="o", label="订单数") ax2.set_ylabel("订单数") plt.title("按小时统计:流水与订单数") plt.tight_layout() plt.savefig("output/hourly_revenue.png", dpi=150) plt.show()

这个图表能很直观地看出收入高峰在哪几个钟头。如果你发现某个时段订单数很多但平均客单价很低,可能说明这个区域有大量短途单,需要结合平均等待时间来判断是否值得继续在那里等单。

5.5 用地图看位置:订单起终点和空驶路径

订单数据里有区域名称,但如果能把位置画到地图上,会更有空间感。下面用folium做一个简单的订单热力地图。为了演示,我们用经纬度模拟点。

# 文件路径:ride-analysis/map_visualization.py import pandas as pd import folium from folium.plugins import HeatMap # 模拟起终点经纬度。实际使用时可以从 GPS 轨迹或地图 API 获取。 # 这里以城市中心为原点做随机偏移,仅演示画图思路。 np.random.seed(42) center_lat, center_lon = 22.5431, 114.0579 df = pd.read_csv("data/orders_demo.csv") lat_offset = np.random.uniform(-0.05, 0.05, size=len(df)) lon_offset = np.random.uniform(-0.05, 0.05, size=len(df)) df["pickup_lat"] = center_lat + lat_offset df["pickup_lon"] = center_lon + lon_offset # 创建地图 m = folium.Map(location=[center_lat, center_lon], zoom_start=13) # 把起点作为热力点 heat_data = [[row["pickup_lat"], row["pickup_lon"]] for _, row in df.iterrows()] HeatMap(heat_data).add_to(m) # 保存为 HTML 文件 m.save("output/order_map.html") print("地图已保存到 output/order_map.html")

用浏览器打开生成的order_map.html,可以看到一个可交互的热力图。颜色越深的地方,说明你的接单起点越集中。如果你长期在这个区域跑单,这个图能帮你识别自己的“主战场”。

5.6 完整代码串起来

上面几个脚本是分步演示,实际使用时可以合成一个主脚本。下面给一个整合后的最小版本,方便直接复制运行。

# 文件路径:ride-analysis/analysis.py import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei"] matplotlib.rcParams["axes.unicode_minus"] = False def load_and_clean_data(path): df = pd.read_csv(path, parse_dates=["order_time"]) df = df.dropna(subset=["order_time", "amount"]) df = df.drop_duplicates(subset=["order_time", "pickup_area", "amount"]) df["hour"] = df["order_time"].dt.hour return df def daily_summary(df, cost_per_km=0.4): total_amount = df["amount"].sum() total_income = df["income"].sum() total_km = df["total_km"].sum() total_empty_km = df["empty_km"].sum() total_cost = (total_km + total_empty_km) * cost_per_km net_income = total_income - total_cost work_hours = (df["order_time"].max() - df["order_time"].min()).seconds / 3600 return { "订单数": len(df), "总流水": round(total_amount, 2), "预计到手": round(total_income, 2), "总里程": round(total_km + total_empty_km, 1), "空驶里程": round(total_empty_km, 1), "空驶率": f"{total_empty_km / (total_km + total_empty_km) * 100:.1f}%", "直接成本": round(total_cost, 2), "净收入": round(net_income, 2), "毛时长": f"{work_hours:.1f}小时", } def hourly_analysis(df): hourly = df.groupby("hour").agg( order_count=("amount", "count"), total_amount=("amount", "sum"), avg_amount=("amount", "mean"), avg_wait=("pickup_wait_min", "mean"), total_km=("total_km", "sum"), total_empty_km=("empty_km", "sum"), ).reset_index() hourly["empty_rate"] = hourly["total_empty_km"] / (hourly["total_empty_km"] + hourly["total_km"]) return hourly def plot_hourly(hourly): fig, ax1 = plt.subplots(figsize=(12, 5)) ax1.bar(hourly["hour"], hourly["total_amount"], color="#4C72B0", label="每小时流水") ax1.set_xlabel("小时") ax1.set_ylabel("流水金额(元)") ax1.set_xticks(range(0, 24)) ax2 = ax1.twinx() ax2.plot(hourly["hour"], hourly["order_count"], color="#C44E52", marker="o", label="订单数") ax2.set_ylabel("订单数") plt.title("按小时统计:流水与订单数") plt.tight_layout() plt.savefig("output/hourly_revenue.png", dpi=150) if __name__ == "__main__": df = load_and_clean_data("data/orders_demo.csv") summary = daily_summary(df) for k, v in summary.items(): print(f"{k}: {v}") hourly = hourly_analysis(df) plot_hourly(hourly) print("\n按小时流水降序:") print(hourly.sort_values("total_amount", ascending=False).head())

运行方式:

cd ride-analysis python analysis.py

5.7 如果还想更深入:接入 GPX 轨迹分析

订单数据只能告诉我们接单点和下车点,GPS 轨迹则能还原两单之间的空驶路径。如果你导出了 GPX 文件,可以用gpxpy解析:

# 文件路径:ride-analysis/parse_gpx.py import gpxpy with open("data/track_20250108.gpx", "r", encoding="utf-8") as f: gpx = gpxpy.parse(f) points = [] for track in gpx.tracks: for segment in track.segments: for point in segment.points: points.append({ "lat": point.latitude, "lon": point.longitude, "time": point.time, "ele": point.elevation, }) print(f"轨迹点数: {len(points)}") print(points[:5])

拿到点列表后,可以计算相邻点之间的距离(用 haversine 公式),累加得到单次行程的总距离,再结合订单时间戳,识别哪些段属于空驶。

6. 运行结果与效果验证

跑完analysis.py后,你应该会看到类似下面的输出(数据是随机生成的,所以数值每次可能不同):

订单数: 36 总流水: 1068.53 元 预计到手: 871.92 元 总里程: 412.3 公里 空驶里程: 101.7 公里 空驶率: 24.7% 直接成本: 164.92 元 净收入: 707.00 元 毛时长: 16.9小时

这里有几点判断标准:

  1. 空驶率是否低于 20%。如果空驶率长期超过 30%,基本可以判断接单策略存在明显问题。可能是高峰时段去了错误区域,也可能是接单后没有规划好下一单的等待位置。
  2. 毛时长与有效时长是否有很大差距。毛时长是从第一单到最后一单的时间,不包含中途休息。如果你记录了自己的实际休息时间,可以计算“有效出车时长”,这个数字更能反映真实工作量。
  3. 每小时流水是否波动剧烈。如果某个小时的流水是周边时段的两倍以上,说明你确实抓住了当天的黄金时段,后续要优先保住这些时段。

如果运行报错,优先检查三个方面:文件路径是否正确;CSV 文件编码是否为 UTF-8;字段名是否和代码里写的一致。尤其是orders_demo.csv如果从 Excel 另存,建议重新导出时选择 CSV UTF-8 格式。

7. 常见问题与排查思路

在实际使用这套分析方法时,会遇到不少问题。这里列几个最常见的。

问题现象可能原因排查方式解决方案
CSV 读取后中文乱码文件编码不是 UTF-8用文本编辑器查看文件编码另存为 UTF-8 或使用encoding="gbk"重新读取
时间字段变成字符串,按小时聚合失败读取时没有指定parse_dates打印df.dtypes检查类型读取时加parse_dates=["order_time"],或手动pd.to_datetime()
图表中文显示为方块系统中文字体缺失或未配置看控制台是否有字体警告设置plt.rcParams["font.sans-serif"] = ["SimHei"]或指定系统已有中文字体
运行时报FileNotFoundError路径不对检查当前工作目录使用绝对路径,或确保在项目根目录下运行脚本
空驶率计算异常,出现负数空驶字段有缺失或录入错误查看empty_km是否为负数或空值清洗时过滤empty_km < 0,缺失值用 0 填充
热力图没有显示任何点经纬度数据为空检查pickup_latpickup_lon确保坐标点生成逻辑正确,地图中心点设置合理
地图加载慢或空白网络问题,folium 需要加载在线瓦片确认网络能访问外部地图资源换一个地图 tile、或使用本地离线瓦片(不推荐,设置较复杂)
分析结果与司机端账单不一致字段口径不同,比如total_km是表显里程而不是平台里程抽样对比平台数据与实际数据在分析时对字段口径做解释,避免混用来源不同的数据

下面展开讲几个高频坑。

7.1 CSV 编码问题

很多司机师傅习惯用 Excel 录入数据,然后“另存为 CSV”。Excel 默认保存的 CSV 是 GBK/ANSI 编码,而 Python 的pandas默认按 UTF-8 读取,于是中文区域名就会变成乱码。

解决办法:在读文件时指定编码:

df = pd.read_csv("data/orders_demo.csv", encoding="gbk")

或者更稳一点,写一个自动尝试编码的函数:

def read_csv_auto(path): try: return pd.read_csv(path, encoding="utf-8-sig") except UnicodeDecodeError: return pd.read_csv(path, encoding="gbk")

建议在录入数据时统一使用 CSV UTF-8 格式(在 Excel 里选择“CSV UTF-8(逗号分隔)”),这样后续不会出乱码。

7.2 时间字段解析失败

订单时间如果写成2025/1/8 6:10202501080610pandas不一定能自动识别。稳妥的做法是读取后统一转换:

df["order_time"] = pd.to_datetime(df["order_time"], format="%Y-%m-%d %H:%M:%S")

如果你的格式不是这个,可能需要调整format参数。更省事的办法是让pandas自动推断,但自动推断偶尔会出错,尤其当月和日顺序不一致时。

7.3 空驶数据不准

GPS 轨迹还原空驶距离是最准确的,但如果你没有 GPS 轨迹,只能用估算值。估算要注意:empty_km是指上一单下车点到下一单上车点之间的驾驶距离,而不是同一单内部的距离。很多人会把这两个概念搞混,导致空驶率虚高。

8. 最佳实践与工程建议

有了数据分析和代码之后,更重要的其实是把它用起来。下面给几条可以从明天开始执行的建议。

8.1 设计一张能长期维护的订单记录表

不管是用 Excel 还是 Google Sheets,建议固定表头,字段不要随意增删。这里有一个推荐的表头设计:

日期订单时间起点区域终点区域流水到手金额等待分钟订单公里空驶公里备注
2025-01-0806:10科技园火车站32.5027.30512.32.1早高峰
2025-01-0806:45火车站大学城28.9024.50310.10.8

固定表头的好处是能直接复用 Python 脚本,不用每次改字段名。如果哪天想增加字段,比如“天气”“是否拥堵”“订单类型”,直接在最后追加列即可。

8.2 把分析嵌入每日收车流程

最有效的用法是每天收车后跑一次脚本,花五分钟看三个指标:

  • 今日空驶率是多少,相比昨天提高了还是下降了。
  • 今天哪两个小时收入最高,明天是否能在那个时段多安排时间。
  • 今天有没有哪个区域等了超过 15 分钟还没派单,明天是否应该避开。

这三个问题不需要复杂图表就能回答。关键是形成习惯,让数据帮助你做第二天的决策,而不是睡前才想起来看一天的总数。

8.3 区分相关性,避免过度解读

如果发现“雨天流水更高”,这可能是因为雨天需求增加、司机出车数量也减少,但不要简单归因为“雨天必须出车”。如果发现“某区域订单多但客单价低”,有可能是该区域以短途拼车单为主,虽然订单数量好看,但每小时收入不一定高。

数据能帮你发现问题,但不能替你解释问题。解释需要结合你对城市、时段、拥堵状况的认知。分析脚本输出的是一个观察结果,你得自己补充业务背景。

8.4 注意隐私与合规

网约车订单数据涉及乘客位置和司机行踪,属于敏感数据。做数据分析时要注意:

  • 不要把包含真实地址、订单号、乘客手机号的数据上传到公开仓库或在线分析平台。
  • 区域字段尽量用“科技园”“大学城”这类聚合名称,不要保留精确门牌号。
  • 如果要在博客或GitHub上展示示例代码,务必使用脱敏后的模拟数据,不要使用自己或他人的真实订单记录。
  • 不要尝试抓取非官方接口来获取实时派单、乘客信息、平台抽成比例等数据,这种行为有合规风险。

8.5 为更复杂的分析留好接口

如果后续想分析得更深入,可以在现有数据表基础上增加以下字段:

  • order_type:快车、专车、拼车等。
  • weather:天气状况。
  • traffic_level:拥堵等级(1-5)。
  • is_rush_hour:是否高峰时段。
  • second_order:本单是否是上一单结束后的连续单(无空驶)。

有了这些字段,就可以做更多维度的拆解,比如“下雨天高峰期在科技园接单的时薪是多少”。但建议先跑通基础分析,再逐步扩充,不要一开始就把表头设计得非常复杂,那样录入成本太高,反而坚持不下去。

8.6 成本参数要根据车型定期校准

analysis.py里把每公里成本写成了固定值0.4,但这个值需要根据自己的车型和使用场景调整。纯电动车在夜间充电和白天快充的价格差很多,油车的油耗也受堵车影响很大。

建议每个月校准一次参数:用当月总油费(或电费)除以总行驶里程,得到实际平均每公里成本,然后把脚本里的cost_per_km改成这个实测值。这样分析出来的净收入才更贴近真实情况。

9. 总结与后续学习方向

跑网约车这件事,从表面看是体力劳动,但从数据角度切入,会发现它其实是一个小型的动态决策优化问题:你要决定什么时候出车、去哪里等单、接单后如何选择下一单的位置、什么时候应该收车休息。这些问题很难靠直觉稳定地做对,但可以通过数据复盘逐步逼近最优解。

这篇文章从一个普通的网约车司机视角出发,演示了如何用 Python 做一天的跑单数据复盘。核心内容包括:用订单记录表和 GPS 轨迹采集数据,用pandas做清洗和聚合,计算空驶率、净收入和小时流水,用柱状图看收入时段分布,用热力图看接单区域分布。整个过程没有用到复杂的大数据框架,一个普通笔记本就能跑完,代码量也不大。

如果你准备开始实践,建议不要追求一步到位。先手动记录一周的订单数据,跑通基础统计;第二周再引入 GPS 轨迹,把空驶率算准;第三周开始用分析结果调整跑单策略,对比调整前后的小时收入变化。这个循序渐进的路径,比一开始就搭完整分析环境更容易坚持。

下一步还可以继续深入的方向包括:用机器学习预测某个时段、某个区域的下单概率;用路径规划算法优化空驶巡游路线;把一周七天的数据放在一起比较星期几对收入的影响;或者把多辆车的数据整合起来做一个小型车队运营分析。无论往哪个方向走,底层都是同样的数据思维:先把业务过程结构化,再用工具把它算清楚。

最后提醒一句:跑单数据的价值在于长期积累。单看一天的数据容易受偶然因素影响,坚持记录几周之后,才能看到真正稳定的规律。建议收藏这篇文章,等积累到一周真实数据后再回来对照实践。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/4 21:51:32

网约车司机工作日志数据采集与复盘系统搭建

“啊僵跑网约车的一天”这个标题&#xff0c;听起来像随手拍的 Vlog&#xff0c;但放在技术博客里&#xff0c;它可以是一个非常完整的实践项目&#xff1a;把司机从出车到收车的全天行为&#xff0c;拆成接单、路线、等待、收入、驾驶习惯、车辆状态几个维度&#xff0c;再做数…

作者头像 李华
网站建设 2026/9/3 19:30:55

LUT与PIP结合:可复现的调色对比流程实战

在视频后期处理里&#xff0c;LUT&#xff08;Look-Up Table&#xff0c;颜色查找表&#xff09;和 PIP&#xff08;Picture-in-Picture&#xff0c;画中画&#xff09;通常被当作两个独立功能。前者负责把颜色从一套规则映射到另一套规则&#xff0c;后者负责在画面角落叠加一…

作者头像 李华
网站建设 2026/9/4 7:29:45

2022小米秋招测试开发笔试复盘:考点拆解与答题思路

我当年在准备测试开发岗位的校招时&#xff0c;最大的感受是&#xff1a;网上关于测试开发笔试的真题复盘少得可怜&#xff0c;尤其是大厂真题&#xff0c;基本都是零散的题目拼接&#xff0c;很少有人从整套卷子的角度讲清楚“这题为什么这么出、答到什么程度能过”。今天拿“…

作者头像 李华
网站建设 2026/9/2 20:01:13

基于Python的财务风险预警系统构建:从数据到模型的完整实践

在实际的数据科学与人工智能课程中&#xff0c;期末实践报告往往是学生将理论知识转化为具体项目能力的第一次系统性尝试。对于会计、金融等非纯技术背景的班级而言&#xff0c;这份报告的核心挑战在于如何将数据科学&#xff08;Data Science&#xff09;与人工智能&#xff0…

作者头像 李华
网站建设 2026/9/3 20:15:57

嵌入式Linux摄像头采集终端开发实战:V4L2+LCD显示全流程

大家好&#xff0c;我是你们的老朋友。最近在整理嵌入式相关项目资料时&#xff0c;发现很多同学对“摄像头采集终端”这个方向既感兴趣又觉得无从下手。一方面&#xff0c;网上关于 V4L2、framebuffer、图像采集的资料非常零散&#xff0c;很多文章只讲某个函数怎么用&#xf…

作者头像 李华