尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用Python实现网约车订单记账与数据分析工具

用Python实现网约车订单记账与数据分析工具 近来不少技术社区都在讨论工程人转型的话题。有人提到一位工程人面对行业波动与债务压力选择开网约车过渡仍然笑对人生。这类故事让我忍不住想一个更具体的问题开网约车同样需要“数据化运营”。每天跑了多少单、哪个时段出来跑更划算、每公里成本是多少、平台抽成后到手多少如果只用脑子去记很难做出准确判断但把这些流水整理成结构化数据用代码去做统计和可视化就能直观看到自己的运营效率。本文不评价任何人的人生选择只借这个场景从技术角度完整带大家实现一套“网约车司机记账与订单分析工具”。你可以把它看作一个 Python 数据分析入门项目也可以把它扩展成自己日常使用的效率工具。就算你并不跑网约车这套工具的写法、表结构设计、统计口径和可视化思路也同样适用于外卖配送、自由职业工时记录、副业收入统计等场景。1. 背景与核心概念1.1 为什么网约车司机也需要数据分析网约车司机的收入并不是“跑单越多赚得越多”这么简单。每一笔订单要扣掉平台抽成、油费或电费、车辆折旧还要考虑空驶里程和等待时间。如果只关心每天流水总额很容易忽略一个事实某些时段看起来流水高但扣掉成本和空驶时间后每小时实际收入可能并不理想。数据化运营可以帮助司机回答几个核心问题一天里面哪个时段接单效率最高某个区域出发的订单平均金额和平均里程是否合理平台抽成占流水的比例大概是多少扣除油费和电费后每天净收入是多少一周七天里哪天适合多跑、哪天适合休息这些问题听起来不复杂但如果靠手工记和口算很难坚持。最合理的做法是把每一笔订单记录到本地数据库里用 Python 写一批统计函数再通过图表展示结果。这就是本文要实现的工具。1.2 工具的目标与功能拆解这个小工具命名为ride_analyzer整体目标非常明确记录订单、分析订单、输出报表和图表。功能拆分如下功能模块说明订单录入支持手动录入订单也支持从 CSV 批量导入日报表统计某一天的总流水、订单数、平台抽成、油费、净收入时段分析将订单按早高峰、午间、晚高峰、夜间等时段分组统计周趋势统计连续一周的每日收入和订单量变化可视化绘制周收入趋势图和时段收入柱状图这个工具不依赖外部数据库服务使用 Python 内置的 SQLite 即可方便个人在笔记本上运行。1.3 技术选型说明技术选型上我刻意选择了一组偏入门、易安装、跨平台稳定的组合Python 3.9语法简洁数据分析生态成熟。SQLitePython 自带sqlite3模块不需要单独安装数据库服务数据落在一个.db文件里方便备份。pandas适合做二维表格统计和聚合分析。matplotlib生成趋势图和柱状图方便把统计结果可视化。如果你对数据库不熟悉也不用担心。本文会从建表开始讲SQL 语句也保持简单重点在理解字段和统计口径。2. 环境准备与项目结构2.1 环境依赖与安装本文示例以 Python 3.9 环境为例具体版本需要根据你的机器情况调整。建议先创建一个独立的虚拟环境避免影响系统全局 Python。python -m venv venv source venv/bin/activate # Windows 使用venv\Scripts\activate激活虚拟环境后安装依赖pip install pandas matplotlibsqlite3属于 Python 标准库不需要额外安装。安装完成后可以用下面命令确认依赖版本pip show pandas matplotlib这里不写死任何具体版本号因为不同操作系统的 Python 版本差异较大只要 pandas 是 1.x 或 2.x、matplotlib 是 3.x 均可运行。2.2 项目文件结构为了保持代码清晰建议按下面的目录结构组织项目ride_analyzer/ ├── ride_analyzer.py # 核心模块数据库、录入、统计、可视化 ├── main.py # 命令行入口 ├── seed_demo.py # 生成演示数据 └── requirements.txt # 依赖清单如果你的项目目录名已经存在可以直接在项目目录内新建文件。下面我从ride_analyzer.py开始编写。3. 数据库设计与核心接口3.1 订单表字段设计订单表是整套工具的核心。字段设计得是否合理直接决定后续统计的复杂度。我的设计如下字段名类型说明idINTEGER主键自增order_dateTEXT订单日期格式YYYY-MM-DDstart_timeTEXT接单时间格式HH:MMend_timeTEXT完成时间格式HH:MMoriginTEXT出发地destinationTEXT目的地mileage_kmREAL行驶里程单位公里duration_minINTEGER订单耗时单位分钟order_amountREAL订单金额单位元platform_feeREAL平台抽成或信息费单位元fuel_costREAL本单对应油费或电费估算单位元order_typeTEXT订单类型默认“快车”日期和时间单独存成字符串主要是为了方便查询和展示。金额统一使用浮点数在展示时再保留两位小数。如果你需要更精确的金额计算生产环境中可以考虑使用Decimal但个人分析场景浮点数足够。3.2 数据库初始化在ride_analyzer.py中先完成数据库连接和建表逻辑。# 文件路径ride_analyzer/ride_analyzer.py import os import sqlite3 from datetime import datetime, timedelta DB_PATH os.path.join(os.path.dirname(__file__), ride_data.db) def get_connection(): 获取数据库连接并开启行字典访问模式 conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): 初始化数据库表重复执行不会清空数据 with get_connection() as conn: conn.execute( CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, order_date TEXT NOT NULL, start_time TEXT NOT NULL, end_time TEXT NOT NULL, origin TEXT DEFAULT , destination TEXT DEFAULT , mileage_km REAL DEFAULT 0, duration_min INTEGER DEFAULT 0, order_amount REAL DEFAULT 0, platform_fee REAL DEFAULT 0, fuel_cost REAL DEFAULT 0, order_type TEXT DEFAULT 快车 ) )这里的关键点是CREATE TABLE IF NOT EXISTS即使多次运行初始化命令也不会把已有数据清空。get_connection中设置了row_factory sqlite3.Row这样查询到的每一行都可以通过字段名访问例如row[order_amount]比下标访问更直观。3.3 订单录入函数接下来实现订单录入。录入时需要注意字段类型日期和时间用字符串里程和金额用浮点数耗时用整数。为了兼容后续 CSV 导入我把录入函数设计成接收完整的一行参数。def insert_order(order_date, start_time, end_time, origin, destination, mileage_km, duration_min, order_amount, platform_fee, fuel_cost, order_type快车): 插入一条订单记录。 返回值是插入记录的自增 id失败时返回 None。 with get_connection() as conn: cursor conn.execute( INSERT INTO orders ( order_date, start_time, end_time, origin, destination, mileage_km, duration_min, order_amount, platform_fee, fuel_cost, order_type ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , ( order_date, start_time, end_time, origin, destination, mileage_km, duration_min, order_amount, platform_fee, fuel_cost, order_type, ), ) return cursor.lastrowid使用?占位符拼接 SQL可以避免 SQL 注入问题。虽然这是本地个人工具但养成参数化查询的习惯没有坏处。插入后函数返回自增 id便于调用方确认写入成功。3.4 CSV 批量导入手动一条条录入比较慢。如果司机已经有平台导出的订单流水可以先整理成 CSV再批量导入。CSV 文件建议使用以下列顺序order_date,start_time,end_time,origin,destination,mileage_km,duration_min,order_amount,platform_fee,fuel_cost,order_type 2025-01-06,07:10,07:45,小区东门,软件园,16.5,35,32.5,3.5,4.2,快车导入代码如下import csv def import_csv(file_path): 从 CSV 文件批量导入订单返回成功导入的行数 count 0 with open(file_path, r, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: try: insert_order( order_daterow[order_date], start_timerow[start_time], end_timerow[end_time], originrow.get(origin, ), destinationrow.get(destination, ), mileage_kmfloat(row[mileage_km]), duration_minint(row[duration_min]), order_amountfloat(row[order_amount]), platform_feefloat(row[platform_fee]), fuel_costfloat(row[fuel_cost]), order_typerow.get(order_type, 快车), ) count 1 except Exception as e: print(f导入失败第 {count 1} 行数据{row}错误{e}) return countencodingutf-8-sig可以自动处理 Excel 导出的 CSV 带 BOM 头的情况避免第一列列名出现\ufeff前缀。4. 核心统计功能实现有了订单数据下一步就是分析。统计功能的代码同样写在ride_analyzer.py中。4.1 日报表统计日报表的核心指标包括总流水、订单数、平台抽成、油费、净收入、总里程和总耗时。净收入的计算方式是净收入 订单总金额 - 平台抽成 - 油费/电费实现代码如下def daily_report(date_str): 统计某一天的订单汇总数据返回字典或 None with get_connection() as conn: rows conn.execute( SELECT * FROM orders WHERE order_date ?, (date_str,), ).fetchall() if not rows: return None total_amount sum(row[order_amount] for row in rows) total_fee sum(row[platform_fee] for row in rows) total_fuel sum(row[fuel_cost] for row in rows) total_km sum(row[mileage_km] for row in rows) total_min sum(row[duration_min] for row in rows) net_income total_amount - total_fee - total_fuel return { date: date_str, order_count: len(rows), total_amount: round(total_amount, 2), platform_fee: round(total_fee, 2), fuel_cost: round(total_fuel, 2), net_income: round(net_income, 2), total_km: round(total_km, 2), total_min: total_min, avg_amount: round(total_amount / len(rows), 2), income_per_km: round(net_income / total_km, 2) if total_km else 0, income_per_hour: round(net_income / (total_min / 60), 2) if total_min else 0, }这里增加了两个很有参考价值的指标每公里净收入和每小时净收入。每公里净收入能反映接单路线是不是顺路每小时净收入则反映时间利用效率。读者在分析自己数据时应该优先关注这两个指标而不是只看流水。4.2 时段特征分析不同时段的订单分布差异很大。我先定义一个时段切分函数再按时间段聚合统计。def time_bucket(start_time): 根据开始时间返回时段名称 hour int(start_time.split(:)[0]) if 6 hour 9: return 早高峰(06-09) elif 9 hour 12: return 上午(09-12) elif 12 hour 14: return 午间(12-14) elif 14 hour 17: return 下午(14-17) elif 17 hour 20: return 晚高峰(17-20) else: return 夜间(20-24) def time_period_report(date_str): 统计某一天不同时段的订单情况 with get_connection() as conn: rows conn.execute( SELECT * FROM orders WHERE order_date ?, (date_str,), ).fetchall() if not rows: return None stats {} for row in rows: bucket time_bucket(row[start_time]) if bucket not in stats: stats[bucket] { count: 0, amount: 0, km: 0, min: 0, fuel: 0, } stats[bucket][count] 1 stats[bucket][amount] row[order_amount] stats[bucket][km] row[mileage_km] stats[bucket][min] row[duration_min] stats[bucket][fuel] row[fuel_cost] return stats这个函数返回一个字典键是时段名称值是该时段的聚合结果。有了这个数据就可以判断晚高峰虽然订单金额高但如果堵车导致耗时过长实际每小时的净收入未必理想。4.3 周维度趋势分析日报表只能看单天连续一周数据才能看出规律。我写一个按日期遍历的函数def weekly_report(start_date, days7): 统计从 start_date 开始连续 days 天的每日汇总 result [] start datetime.strptime(start_date, %Y-%m-%d) for i in range(days): day (start timedelta(daysi)).strftime(%Y-%m-%d) info daily_report(day) if info: result.append(info) else: result.append({ date: day, order_count: 0, total_amount: 0, net_income: 0, total_km: 0, }) return result如果某一天没有数据我会返回一个全 0 的占位记录这样绘图时日期轴可以保持连续不会出现空白断裂。5. 数据可视化把数字变成图表代码统计输出的数字最终要让司机一眼看懂趋势就需要可视化。这里使用 matplotlib 画两张图周收入趋势图和时段收入柱状图。5.1 中文字体设置matplotlib 默认不支持中文需要对字体做设置。不同操作系统字体不同这里给出一个兼容写法import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [ SimHei, # Windows 常见中文字体 PingFang SC, # macOS 常见中文字体 Noto Sans CJK SC, # Linux 常见中文字体 ] plt.rcParams[axes.unicode_minus] False这段配置不保证在所有机器上都能立即生效。如果图表里的中文仍然显示为方块可以用以下代码列出系统可用字体选择支持中文的字体名填入import matplotlib.font_manager as fm fonts [f.name for f in fm.fontManager.ttflist] print(sorted(set(fonts)))5.2 绘制周收入趋势图def plot_weekly_trend(start_date, days7, save_pathNone): 绘制连续多天的流水与净收入趋势图 data weekly_report(start_date, days) dates [d[date][5:] for d in data] amounts [d[total_amount] for d in data] nets [d[net_income] for d in data] plt.figure(figsize(10, 5)) plt.plot(dates, amounts, markero, label总流水, linewidth2) plt.plot(dates, nets, markers, label净收入, linewidth2) plt.xlabel(日期) plt.ylabel(金额元) plt.title(网约车每日收入趋势) plt.legend() plt.grid(True, linestyle--, alpha0.5) plt.tight_layout() if save_path: plt.savefig(save_path, dpi150) print(f图片已保存到{save_path}) else: plt.show()save_path参数可以让你把图保存为 PNG 文件方便发到手机上看或者直接备份。如果不传则调用plt.show()弹窗展示。5.3 绘制时段收入柱状图时段分析的结果更合适用柱状图呈现def plot_time_period(date_str, save_pathNone): 绘制某一天的时段订单量柱状图 stats time_period_report(date_str) if not stats: print(f{date_str} 没有订单数据) return periods list(stats.keys()) counts [stats[p][count] for p in periods] amounts [stats[p][amount] for p in periods] fig, ax1 plt.subplots(figsize(10, 5)) ax1.bar(periods, counts, color#4C72B0, alpha0.8, label订单数) ax1.set_ylabel(订单数) ax2 ax1.twinx() ax2.plot(periods, amounts, color#C44E52, markero, label订单金额) ax2.set_ylabel(订单金额元) plt.title(f{date_str} 各时段订单量与金额对比) plt.tight_layout() if save_path: plt.savefig(save_path, dpi150) print(f图片已保存到{save_path}) else: plt.show()这里使用了双 Y 轴左边是订单量柱状图右边是订单金额折线图。它的好处是能同时看到“这个时段接了几单”和“这个时段赚了多少钱”。6. 命令行主程序与运行验证函数写完之后我们要把这些功能串成一个命令行工具让它支持类似python main.py daily --date 2025-01-06这样的命令。6.1 命令行入口# 文件路径ride_analyzer/main.py import argparse import ride_analyzer def main(): parser argparse.ArgumentParser( description网约车司机记账与订单分析工具 ) subparsers parser.add_subparsers(destcommand) subparsers.add_parser(init, help初始化数据库) add_parser subparsers.add_parser(add, help添加一条订单) add_parser.add_argument(--date, requiredTrue) add_parser.add_argument(--start, requiredTrue) add_parser.add_argument(--end, requiredTrue) add_parser.add_argument(--origin, default) add_parser.add_argument(--destination, default) add_parser.add_argument(--km, typefloat, requiredTrue) add_parser.add_argument(--min, typeint, requiredTrue) add_parser.add_argument(--amount, typefloat, requiredTrue) add_parser.add_argument(--fee, typefloat, default0) add_parser.add_argument(--fuel, typefloat, default0) daily_parser subparsers.add_parser(daily, help查看日报表) daily_parser.add_argument(--date, requiredTrue) period_parser subparsers.add_parser(period, help查看时段分析) period_parser.add_argument(--date, requiredTrue) trend_parser subparsers.add_parser(trend, help查看周趋势) trend_parser.add_argument(--start, requiredTrue) trend_parser.add_argument(--days, typeint, default7) trend_parser.add_argument(--save, defaultNone) import_parser subparsers.add_parser(import, help从 CSV 导入订单) import_parser.add_argument(--file, requiredTrue) args parser.parse_args() if args.command init: ride_analyzer.init_db() print(数据库初始化完成) elif args.command add: ride_analyzer.init_db() order_id ride_analyzer.insert_order( order_dateargs.date, start_timeargs.start, end_timeargs.end, originargs.origin, destinationargs.destination, mileage_kmargs.km, duration_minargs.min, order_amountargs.amount, platform_feeargs.fee, fuel_costargs.fuel, ) print(f订单已添加ID {order_id}) elif args.command daily: info ride_analyzer.daily_report(args.date) if not info: print(f{args.date} 没有订单数据) return print( * 42) print(f日报表{info[date]}) print( * 42) print(f订单数{info[order_count]}) print(f总流水{info[total_amount]} 元) print(f平台抽成{info[platform_fee]} 元) print(f油费/电费{info[fuel_cost]} 元) print(f净收入{info[net_income]} 元) print(f总里程{info[total_km]} 公里) print(f平均每单金额{info[avg_amount]} 元) print(f每公里净收入{info[income_per_km]} 元) print(f每小时净收入{info[income_per_hour]} 元) elif args.command period: stats ride_analyzer.time_period_report(args.date) if not stats: print(f{args.date} 没有订单数据) return for period, info in stats.items(): print(f{period}: 订单数 {info[count]}, f金额 {info[amount]:.2f} 元, f里程 {info[km]:.1f} 公里, f耗时 {info[min]} 分钟) elif args.command trend: ride_analyzer.plot_weekly_trend( args.start, daysargs.days, save_pathargs.save ) elif args.command import: count ride_analyzer.import_csv(args.file) print(f成功导入 {count} 条订单) else: parser.print_help() if __name__ __main__: main()这里把所有功能都暴露成子命令。命令行参数与函数参数一一对应读者在运行时只需要注意参数名不需要修改代码。6.2 生成演示数据为了方便验证我提供一个生成演示数据的小脚本自动往数据库里写入 7 天订单。实际使用时你可以不运行这段脚本直接用自己的真实数据。# 文件路径ride_analyzer/seed_demo.py import ride_analyzer DEMO_ORDERS [ (2025-01-06, 07:10, 07:45, 小区东门, 软件园, 16.5, 35, 32.5, 3.5, 4.2), (2025-01-06, 08:05, 08:40, 软件园, 科技园, 12.0, 35, 28.0, 3.0, 3.1), (2025-01-06, 18:20, 19:05, 科技园, 火车站, 18.8, 45, 45.0, 4.5, 4.8), (2025-01-06, 19:20, 19:50, 火车站, 老城区, 14.2, 30, 35.0, 3.8, 3.6), (2025-01-07, 07:20, 07:55, 小区东门, 金融中心, 15.0, 35, 33.0, 3.5, 4.0), (2025-01-07, 18:00, 18:50, 金融中心, 滨江公园, 20.0, 50, 52.0, 5.2, 5.5), (2025-01-08, 06:50, 07:30, 小区东门, 火车南站, 22.0, 40, 48.0, 5.0, 6.0), (2025-01-08, 09:10, 09:40, 火车南站, 大学城, 13.0, 30, 30.0, 3.0, 3.4), ] def run(): ride_analyzer.init_db() for item in DEMO_ORDERS: ride_analyzer.insert_order(*item) print(f演示数据写入完成共 {len(DEMO_ORDERS)} 条) if __name__ __main__: run()这个脚本演示的是简化录入流程。真实使用中insert_order的参数可能与平台导出的流水列不完全一致需要你自己做一下字段映射但函数设计思路是一致的。6.3 运行效果验证在项目目录下依次执行python main.py init python seed_demo.py python main.py daily --date 2025-01-06预期会看到类似下面的输出 日报表2025-01-06 订单数4 总流水140.5 元 平台抽成14.8 元 油费/电费15.7 元 净收入110.0 元 总里程61.5 公里 平均每单金额35.12 元 每公里净收入1.79 元 每小时净收入45.52 元再执行时段分析python main.py period --date 2025-01-06输出大致如下早高峰(06-09): 订单数 2, 金额 60.50 元, 里程 28.5 公里, 耗时 70 分钟 晚高峰(17-20): 订单数 2, 金额 80.00 元, 里程 33.0 公里, 耗时 75 分钟这样就能很直观地看到当天虽然晚高峰订单金额更高但耗时也更长每小时效率不一定超过早高峰。执行周趋势图python main.py trend --start 2025-01-06 --days 7 --save trend.png运行后项目目录下会生成trend.png图片可以看到每天流水和净收入的波动曲线。7. 常见问题与排查思路在实际运行过程中最容易遇到以下几类问题。我整理成表格方便定位。问题现象常见原因解决思路初始化数据库后查询不到数据数据库文件路径不一致检查DB_PATH是否指向同一个文件确认是否运行过seed_demo.py中文在图表中变成方块系统缺少中文字体检查 matplotlib 可用字体并设置plt.rcParams[font.sans-serif]CSV 导入报错KeyErrorCSV 列名与代码不一致用文本编辑器打开 CSV确认表头是否有 BOM 或列名拼写错误运行时提示ModuleNotFoundError: No module named pandas虚拟环境未安装依赖在项目虚拟环境执行pip install pandas matplotlib日期明明有数据日报表却返回None日期字符串格式不一致统一使用YYYY-MM-DD格式检查是否有空格同一时间执行多个脚本导致数据库锁SQLite 写并发冲突尽量避免多个进程同时写入统计脚本和录入脚本串行执行如果遇到数据库文件被占用可以先关闭所有 Python 进程再删除ride_data.db并重新初始化。注意删除数据库会丢失全部数据执行前务必确认不需要保留。8. 最佳实践与工程建议这个工具虽然以个人使用为主但放到工程视角依然可以提炼出一些通用经验。第一数据字段要在最前面规划好。订单表一旦跑了一段时间再增加字段就需要处理存量数据。建议在项目最开始就把“如果以后想分析每个订单的等待时间”“如果以后想接入导航距离”这些可能性考虑进去尽量多留几个可空字段。第二所有金额字段在录入时最好统一保留原始流水金额而不是提前计算后存入。因为运营规则可能变化平台抽成比例也可能调整。保留原始字段统计时再计算才能应对规则变化。第三录入数据要做边界校验。例如订单金额不能为负数里程不能为负数。如果脚本直接导入遇到脏数据会污染统计结果。可以在insert_order中加入简单判断不符合条件就拒绝写入。第四备份数据库。SQLite 只有一个.db文件备份非常方便。建议每天跑完后复制一份到网盘或者移动硬盘保存文件时带日期后缀。第五隐私保护。订单数据虽然属于个人但里面包含出发地、目的地、时间等敏感信息。项目文件不要随意共享更不要上传到公开仓库。如果要做技术演示最好使用脱敏后的模拟数据。第六不要过度设计。很多人看到这样的统计分析项目容易想一步到位加 Web 页面、加用户登录、加部署。实际上个人效率工具最重要的价值是“快速用起来”。先用命令行数据积累一段时间后再根据真实需求决定是否做 Web 化。9. 总结与下一步学习方向本文从一个真实的现实场景出发完整实现了一套“网约车司机记账与订单分析工具”。我们使用 SQLite 设计并创建了订单表实现了订单录入、CSV 导入、日报表、时段分析和周趋势统计并基于 matplotlib 完成可视化。整个项目不依赖复杂的第三方服务适合个人在真实场景中快速落地。如果你对数据分析感兴趣下一步可以继续做几件事把订单数据接入地图 API分析出发区域的热力分布判断哪些区域更容易接到优质订单。用 pandas 的groupby做更细致的多维度汇总比如不同订单类型的收入差异。把命令行工具改造成 Flask Web 应用在手机上可视化查看统计数据。积累更多历史数据后尝试用简单的线性回归或时间序列模型预测未来一周每天的流水区间。回到开头的那个话题。生活中难免会遇到意想不到的困难但掌握一门能解决实际问题的技术哪怕它只是帮自己把一笔笔订单记录清楚、算明白每一公里的真实收入也是在为自己积累底气。希望这篇教程不仅能让你学会 Python 数据分析的基础流程也能给你一些“用技术应对变化”的启发。动手写一写跑一跑你会收获比读文章更多的东西。
返回列表