
简介这份PDF面向希望通过日志分析掌握DeepSeek API运行状态的开发者适合具备基础Python能力、正在搭建监控体系的入门及进阶人群。文档从API监控基础概念讲起依次覆盖DeepSeek调用日志获取、数据清洗与预处理、关键指标提取以及基于Echarts的可视化看板搭建内容包含详细目录与章节结构方便按需查阅。资源共1个PDF文件压缩包大小1.89MB排版完整、图表清晰适合直接阅读与技术实践对照。目前已有115人学习下载。读者通过该文档可获得从日志采集、指标计算到看板部署的完整思路包括响应时间、错误率等指标分析以及本地或云服务器部署与持续监控的落地方法对构建稳定的API监控体系具有实用参考价值。1. 零基础搭 DeepSeek API 监控看板先行别等问题找上你接入 DeepSeek API 的第一周我收到一条用户反馈“你们的问答接口怎么卡了十几秒”我马上去管理后台查调用量正常、状态码也都是 200完全不知道问题出在哪。后来把调用日志拉下来一看某个请求里 max_tokens 设得过大长文本生成把响应时间顶到了 8 秒以上平均响应时间早就悄悄翻倍了。这次复盘让我把整个排查过程整理成一套可复现的监控方案从 DeepSeek 管理平台或日志接口拿调用日志做清洗和特征提取算出响应时间、错误率、调用频率、吞吐量四个核心指标再用 Flask ECharts 拼出一页可视化看板。适合刚接触 API 开发、有 Python 基础、想知道“接口到底跑得怎么样”的人跟着做大约半天能上线第一版看板。2. 日志从哪拿管理平台导出与日志接口两条路径先落盘再谈分析2.1 日志字段地图先知道手里有什么再做监控拿到 DeepSeek 调用日志的第一步不是急着写清洗代码而是先认清日志里有哪些字段、每个字段能支撑什么指标。我见过不少同事把日志当黑匣子直接拿 response_time 去算平均结果字段名称对不上跑出来的数字全是空列表。常见做法是先把一条日志打印出来对照字段表确认一遍。字段含义清洗后类型request_id单次请求唯一标识去重首选stringrequest_time请求发起时间datetimemodel调用的模型名stringprompt_tokens / completion_tokens输入输出 token 数可用于成本估算intresponse_time服务端响应耗时float单位秒status_codeHTTP 状态码200 为成功int有一点容易被忽略DeepSeek 是按 token 计费的服务日志里一定会有 token 字段。做监控时就算现在不关心成本也建议把 token 字段保留下来后面做资源规划会用到。我在清洗时习惯把 token 字段原样留到最后不参与任何聚合但也不删。2.2 路径一管理平台导出 CSV/JSON 的操作要点DeepSeek 管理后台一般提供日志查看和导出功能。操作上注意三件事第一导出前先选对时间范围很多平台默认只展示最近几小时你直接点导出拿到的可能只是一小段数据第二记录数特别多时平台会分页导出注意是不是每一页都下载完了第三导出格式按需选后续要做时间序列分析的话JSON 比 CSV 更不容易丢类型信息。拿到导出文件后先写一个能同时读 JSON 和 CSV 的加载函数省得来回改import json import csv from pathlib import Path def load_logs(path: str): p Path(path) if p.suffix.lower() .json: with open(p, r, encodingutf-8) as f: return json.load(f) with open(p, r, encodingutf-8, newline) as f: return list(csv.DictReader(f)) # 使用示例 raw_logs load_logs(deepseek_logs.json)json.load 会把整个文件一次读进内存。对每日几万条的调用量没有问题但如果积压了几十万条建议分片读取或者直接走下一小节的日志接口按页拉取。这里我一般还会加一句打印读完先print(raw_logs[:2])确认拿到的是列表而不是嵌套字典很多新手在这步就把类型搞错了。2.3 路径二日志接口拉取定时增量着跑管理平台手动导出只适合偶尔看一眼做持续监控还是得靠日志接口。常见的做法是提供一个带时间范围和分页参数的 GET 接口用 requests 定时拉取import requests API_KEY your_api_key LOG_URL https://api.deepseek.com/v1/logs def fetch_logs(start: str, end: str, page: int 1): resp requests.get( LOG_URL, headers{Authorization: fBearer {API_KEY}}, params{ start_time: start, end_time: end, page: page, page_size: 500, }, timeout30, ) resp.raise_for_status() return resp.json()page_size 我习惯压到 500。太大容易触发平台限流太小又浪费请求次数。真正的接口路径、分页参数名要以你拿到的服务商文档为准不同租户的接口细节会有差异。返回结构里重点看两个东西records 列表和表示是否还有下一页的字段。有些平台会用 total_count 让你自己算页数有些会给 next_cursor写法略有不同。增量拉取落盘时文件名的查询区间一定要写清楚from datetime import datetime, timedelta from pathlib import Path LOGS_DIR Path(logs) LOGS_DIR.mkdir(exist_okTrue) def pull_interval(days: int 3): now datetime.utcnow() start now - timedelta(daysdays) all_records [] page 1 while True: data fetch_logs(start.isoformat(), now.isoformat(), page) records data.get(records, []) if not records: break all_records.extend(records) if len(records) 500: break page 1 out LOGS_DIR / fdeepseek_logs_{start:%Y%m%d%H%M}_{now:%Y%m%d%H%M}.json out.write_text(json.dumps(all_records, ensure_asciiFalse, indent2), encodingutf-8)时间统一用 UTC ISO8601 格式传参文件名带上查询区间的起止时间。这个文件名在后面核对数据空洞时是后悔药如果发现某两批数据之间时间不连续看文件名就知道是哪次任务漏跑了。2.4 落盘先存 JSON/CSV再考虑 SQLite很多教程一上来就让建 SQLite但对于监控看板这种场景我更建议先落 JSON 文件跑通流程后再换数据库。原因是前期清洗逻辑变化频繁JSON 文件可以直接用 Python 脚本重读不用维护表结构。等看板稳定下来需要增量更新、并发查询时再迁到 SQLite 也不迟。落盘时保留原始日志全字段不要在拉取阶段就裁剪字段。清洗和特征提取是下一章的事拉取阶段少做一步后面要补字段就不用重新拉数据。提示如果你拿到的日志里没有 request_id去重时大概率要退而求其次用“时间 模型 token”三元组。这个细节会在避坑章展开。3. 把脏日志变干净清洗顺序、特征提取与指标口径一次说清3.1 脏数据四类缺失、错值、重复、字段类型DeepSeek 调用日志的脏数据我归纳下来基本是四类。第一字段缺失例如请求报错时 response_time 为空第二字段错误时间字符串格式不统一有的带毫秒、有的是2025-03-11 10:00:00第三重复记录SDK 超时重试会产生相同业务参数的日志第四类型问题response_time 被写成字符串 “0.832”直接求平均会翻车。清洗顺序有讲究我的习惯是先去重再纠错、最后处理缺失。先用原始字段值做过去重错误数据和填充值就不会干扰去重逻辑。如果反过来先填缺失值可能把两条本来该合并的重复记录错判成不同记录。3.2 清洗主流程一份可按字段配置的脚本下面这段把去重、时间校验、类型转换串成一条主流程from datetime import datetime def clean_logs(records: list[dict]) - list[dict]: # 1. 去重有 request_id 用 request_id没有则用三元组 seen set() unique [] for r in records: key r.get(request_id) or ( r.get(request_time), r.get(model), r.get(prompt_tokens), ) if key in seen: continue seen.add(key) unique.append(r) # 2. 时间校验格式不对的记录直接剔除 cleaned [] for r in unique: t r.get(request_time) if not t: continue try: datetime.strptime(t, %Y-%m-%d %H:%M:%S) cleaned.append(r) except ValueError: continue # 3. 类型转换response_time 统一转 float for r in cleaned: try: r[response_time] float(r[response_time]) except (TypeError, ValueError): r[response_time] None return cleaned去重时 request_id 优先级最高因为它是单次请求的唯一标识SDK 重试产生的重复记录也会带不同 request_id。注意三元组去重在“同一秒内同参数并发”的场景下会误删干净数据所以有条件拿 request_id 就别用三元组。时间校验失败的直接剔除这是最保守的做法一条缺失请求时间的日志既不能进调用频率也不能进时间序列分析留着只会污染指标。response_time 转 float 失败时置为 None保留记录但不参与平均计算这样错误率统计仍然有效。3.3 特征提取从时间字符串里拆出小时与星期指标聚合是按时间桶做的所以要把 request_time 拆成可排序的特征字段。拆出来的特征主要服务两个场景按小时看调用波动、按星期判断是否是业务高峰。代码很简单def extract_time_features(r: dict) - dict: dt datetime.strptime(r[request_time], %Y-%m-%d %H:%M:%S) r[hour] dt.hour r[weekday] dt.weekday() # 周一为 0周日为 6 r[is_weekend] 1 if dt.weekday() 5 else 0 return r # 对干净日志逐条执行 cleaned_logs [extract_time_features(r) for r in cleaned_logs]weekday 的计数从周一开始很多习惯周日为一周之首的同事在这里踩过坑。is_weekend 字段是为“工作时段 / 非工作时段”对比预留的后面做异常检测时如果某天晚 10 点调用量突然暴涨这个字段能帮你快速判断是不是异常行为。3.4 指标口径要先定死错误率、吞吐量的分母与窗口清洗做完下一步是定指标口径。这步不写代码但比代码更重要。口径不一致看板上的数字就没有公信力。我用的口径如下指标计算口径窗口平均响应时间所有含 response_time 的记录求均值空值不计入按小时聚合错误率非 200 状态码的请求数 / 总请求数按小时聚合调用频率每小时的请求总数按小时聚合吞吐量单位时间窗口内成功返回的请求数按分钟或小时错误率这里有个争议点429 限流到底算不算错误我的处理是算入错误率但在明细里单独拆出一列“限流计数”。因为从用户视角看429 就是请求失败但从服务商视角看429 说明你调用姿势有问题。把它混在错误率里看不见细节单列一列才能判断是该扩配额还是该降并发。吞吐量窗口用分钟会比小时更灵敏适合观察实时压力但看板默认展示小时级聚合分钟级留给交互钻取。4. Flask ECharts 搭看板一页展示四个指标数据接口是关键4.1 技术选型为什么这题选 ECharts 而不是 Tableau可视化技术有好几个可选ECharts、Highcharts、Plotly、Tableau。Tableau 功能强但太“重”适合做深度分析报表不适合嵌入到运维门户里给开发小伙伴日常盯一眼。Plotly 的 Python 接口做得舒服但交互性能在大数据量下不如 ECharts 顺滑。Highcharts 商业授权要花钱个人用没事放到企业内部就麻烦。最后落回 ECharts免费、中文文档全、折线柱状图开箱即用浏览器端的缩放和悬停提示都做得比较好。还有一个真实原因是团队技术栈。如果团队里没有前端同事ECharts 是纯前端库你只要会写一个 HTML 文件就能把后端算好的指标数据渲染出来。这是零基础场景下最友好的路线。4.2 后端一个 Flask 接口把指标按时间窗吐给前端看板的“心脏”不在图表在数据接口。前端图表画得再漂亮接口返回的数据结构不干净也没用。我先用 Flask 写一个最简接口从清洗落库的数据里按小时聚合from flask import Flask, jsonify import sqlite3 app Flask(__name__) app.route(/api/metrics) def metrics(): conn sqlite3.connect(deepseek_logs.db) rows conn.execute( SELECT hour, COUNT(*) AS call_count, AVG(response_time) AS avg_response_time, SUM(CASE WHEN status_code 200 THEN 0 ELSE 1 END) * 1.0 / COUNT(*) AS error_rate FROM deepseek_logs GROUP BY hour ORDER BY hour ).fetchall() conn.close() return jsonify({ timestamps: [r[0] for r in rows], call_count: [r[1] for r in rows], avg_response_time: [round(r[2] or 0, 3) for r in rows], error_rate: [round(r[3] or 0, 4) for r in rows], })SQL 里的CASE WHEN写法把“是否错误”的判断下推到数据库比在 Python 里逐条判断更省事。AVG(response_time)会自动跳过 NULL这正是我们对缺失响应时间的处理策略。注意一个坑小时序列如果不连续折线图会出现断裂。新手阶段我建议在 Python 里补零不要急着写递归 CTE先让看板能看优化后面再说。4.3 前端ECharts 折线图和柱状图的最小例子前端页面保持极简。一个 HTML 文件引入 ECharts拉接口画图!DOCTYPE html html langzh-CN head meta charsetutf-8 script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script /head body div idchart stylewidth: 100%; height: 400px;/div script fetch(/api/metrics) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(chart)); chart.setOption({ xAxis: { type: category, data: data.timestamps }, yAxis: { type: value }, series: [{ type: line, data: data.avg_response_time }], tooltip: { trigger: axis } }); }); /script /body /html这段代码里没有多余的东西。trigger: axis让鼠标悬停在图上时横向显示该时间点的全部数值。生产环境我一般把 echarts.min.js 下载到本地 static 目录再引用避免页面加载依赖外部网络。CDN 引入适合本地快速验证上服务器前一定要换掉。4.4 布局与交互一屏看全四个指标一页只画一条折线太浪费看板价值布局上做成 2×2 的网格左上调用频率柱状图、右上平均响应时间折线图、左下错误率柱状图、右下吞吐量面积图。每个图表容器固定高度用 CSS Grid 或 Flexbox 排布div classgrid styledisplay: grid; grid-template-columns: 1fr 1fr; gap: 12px; div idchart-frequency styleheight: 320px;/div div idchart-latency styleheight: 320px;/div div idchart-error styleheight: 320px;/div div idchart-throughput styleheight: 320px;/div /div交互上两个功能最有价值dataZoom 拖拽缩放用来圈选异常时间段悬停提示展示该小时桶的明细例如错误数、429 数量、平均 tokens。钻取功能可以做成点击柱状图弹出最近 5 分钟明细列表前期不做也不影响核心使用可以先留着。4.5 数据传递先落一份静态 JSON 再读没有 Flask 也能看如果你完全没写过 Flask还有一个更快的验证路径把聚合结果写成静态 JSON 文件前端直接 fetch 这个文件不需要后端接口。做法就是把 4.2 的 SQL 查询结果导出成metrics.json前端把接口地址换成文件名即可。缺点是不能实时更新得定期重跑脚本优点是文件能独立存下来任何时候双击 index.html 都能看到当时的结果。先跑通这条路径再回来封装 Flask是我建议给零基础读者的路径。5. 避坑记DeepSeek 日志监控最容易翻车的五个环节5.1 响应时间大量为空错误请求不计时导致指标失真现象看板上平均响应时间突然降到接近 0但业务方反馈接口明明很慢。原因很多日志只给成功请求记 response_time超时或报错的请求这个字段是空的。如果你写AVG(response_time)时不看状态码空值被自动跳过平均自然被“成功且快”的样本拉低和用户体感正好相反。解决统计平均响应时间前先按状态码分组成功请求和失败请求分开看或者至少先确认 response_time 的缺失率。我在清洗脚本里加了一行统计缺失率的逻辑超过 20% 就告警提醒数据源有问题。5.2 429 和 400 混进错误率告警阈值永远定不对现象错误率看起来不到 1%但用户持续遇到限流一问客服才知道是 429 被吞在“非 200”这个大筐里看不出来。原因错误率口径只区分 200 和非 200把 429、400、5xx 全混在一起。429 是限流、400 是参数或 context 超限、5xx 才是服务端故障三种问题的处理方式完全不同。解决清洗时增加一个 error_type 字段按状态码映射成rate_limit、bad_request、server_error、other四类错误率看板至少拆三根柱。另一件血泪经验DeepSeek 的 400 错误里最常见的不是参数格式而是 context 超限这类错误要单独统计出来看是否由请求体过大导致。5.3 重复日志翻倍SDK 重试机制把调用频率顶爆现象某小时调用频率冲上 1200 次但管理后台显示实际请求只有 600 次。原因SDK 在超时后会自动重试日志系统把每次重试都记为一条新记录。如果只用“时间 参数”做去重同一秒内同参数的重试会被误判为不同请求。解决优先用 request_id 去重拿不到 request_id 时用“时间 model prompt_tokens completion_tokens”四元组虽然不能 100% 消灭误判但能把重复率压到可接受范围。我在清洗脚本里特意保留了一个去除去重前的总数统计方便和表观调用量对账。5.4 时间桶与时区错位图表上下午的曲线整体平移现象调用频率的波峰明明出现在北京时间下午 3 点折线图上却显示在晚上 7 点。原因日志接口返回的时间是 UTC直接按字符串里的小时数值聚合没做时区转换。UTC 下午 3 点恰好是北京时间晚上 11 点聚合结果整体偏移。解决在特征提取阶段统一转成目标时区我用的方式是datetime.strptime(..., %Y-%m-%d %H:%M:%S)后手动timedelta(hours8)并把时区信息写进字段名hour_local避免后面忘了到底是哪个时区。5.5 导出日志缺页手动导出只拿到第一页就停止现象日志导出后清洗发现最近两小时的数据全都缺失看板上出现一个大空洞。原因管理平台导出大范围日志时默认分页手动点击导出只下载了第一页后续页的数据根本没拿下来。我一开始也踩过这个坑后来在拉取脚本里加了一个数据对账步骤把导出的首尾时间和管理后台显示的记录区间核对区间不吻合就重拉。对账逻辑就是第 2 章pull_interval里那个文件名起止时间和实际数据的首条末条时间做差值差值超过一个窗口就告警。缺页这种问题肉眼很难发现必须靠程序校验。6. 上线与自检一小时验证看板数据闭合的实操流程看板本地跑通之后上线时最容易忽略的是“数据是否闭合”。所谓闭合就是看板上每个数字都能追溯回原始日志经得起抽样核验。我上线前会强制走一遍自检流程第一步是确认部署形态内网环境直接 gunicorn 起一个服务外网环境加 Nginx 反代项目文件丢到服务器固定目录后启动即可。Gunicorn 命令很直接gunicorn -w 2 -b 127.0.0.1:8000 app:appNginx 配置核心是把/代理到本地 8000 端口静态文件由 Nginx 直接返回请求再转发给 Flask。上线后按下面这张表逐项核对检查项操作方法判定标准接口存活curl http://127.0.0.1:8000/api/metrics返回合法 JSON不是 502数据新鲜度看返回 timestamps 最后一位与当前时间差差小于 2 小时抽样准确性随机挑 3 个小时桶手工数原始日志记录数与 call_count 一致图表渲染浏览器打开页面检查四个图表均非空无空白容器报错自检里最有价值的是“抽样手工重算”挑一个非高峰小时的桶从原始日志里数出该小时的请求数、失败的条数对比看板上的柱状图。我经历过一次看板显示错误率 2%、手工一数是 20% 的情况原因就是清洗时把带异常状态码的记录全滤掉了接口却没告诉你这个过滤逻辑。数据闭合的核心不是“图好看”而是“每个数字都能从日志里重新算出来”。从那以后我每次上线看板都强制走一遍这三步curl 数据窗口、抽样重算、看板核对。希望帮到你。本文还有配套的精品资源点击获取