Zabbix API 批量获取所有主机与监控项值实战

发布时间:2026/10/1 6:52:23
Zabbix API 批量获取所有主机与监控项值实战
1. 为什么放着现成的界面不用非要走 API做运维监控这行的几乎都遇到过这么一个需求领导要一份全公司所有服务器当前 CPU、内存、磁盘的最新值的表格或者要把 Zabbix 的监控数据同步到自己的资产管理系统、告警平台、数据大屏里去。这时候如果你还在 Web 界面里一个个点主机、一个个复制粘贴那就太亏了。Zabbix 从很早就提供了完整的API接口只要你拿到主机清单和对应的监控项值剩下的就是写几行脚本的事了。这篇内容要聊的就是这件事怎么用Zabbix API一次性把所有主机以及它们下面的监控项的值全捞出来。我会从需求拆解、接口选型、参数含义、Python 落地脚本一直到批量拉数据时怎么不把 Zabbix Server 拖垮一条线讲透。适合两类人看一类是刚接触 Zabbix、想从点点点进化到写脚本的运维新人另一类是已经用过 API 但只写过零散调用、想把它做成稳定生产工具的老手。中间涉及到的每一个参数、每一个坑我都会说清楚它为什么这么设计而不是甩一段代码让你自己猜。先说结论整套流程的核心链路其实只有四步——认证拿到凭证 → host.get 拿主机列表 → item.get 拿监控项和最新值 → 需要历史曲线再上 history.get。听起来简单但真到生产环境版本差异、参数结构、value_type 类型换算、分页限流随便一个都能让你卡半天。下面一个个拆。1.1 一个真实场景资产报表为什么要自动化我之前在一家做电商的公司待过机房加上云上实例Zabbix 里纳管的主机数量在两千台上下监控项数量是百万级别。业务侧每周一早上要一份核心服务主机当前资源水位的报表刚开始是两个同事轮班每人半天时间在界面上导出、整理、核对出错的概率还不低因为主机名字一旦有变动人工那一版就对不上了。后来我把这个流程用 API 重写了一遍。逻辑很朴素主机名是唯一且稳定的主体主机 IDhostid是内部主键监控项通过 hostid 关联最新值直接从 item.get 的 lastvalue 字段拿一次请求可以把一批主机的所有监控项都带回来。整份报表从半天人工变成了跑一次脚本三分钟出结果而且因为数据源是同一条链路再也不会出现某台主机的名字在报表里和监控系统里对不上这种低级问题。这就是 API 存在的意义——它把 Zabbix 从一个给人看的界面变成了一个给程序读的数据源。你把它理解成一个结构化的数据库查询入口就行只不过查询语句换成了 JSON。1.2 为什么是这四步而不是别的组合很多人第一反应是去翻数据库直接连 Zabbix 的 MySQL 查 hosts 表和 history 表。我劝你别这么干原因有三个。第一数据库结构是内部实现Zabbix 大版本升级时表结构可能会变你今天写的 SQL 明天可能就报错API 是官方承诺稳定的对外契约跨版本基本兼容。第二history 表是按 value_type 分成 history、history_uint、history_str、history_text、history_log 好几张表的你自己去拼这几张表的逻辑等于把 API 已经封装好的活重做一遍。第三直连数据库意味着你要把库的账号密码散落在各个脚本里安全上是个隐患——API 至少能发 Token能按角色收权限。所以我把选型钉死在 API 上。而 API 的调用链路之所以是认证 → 主机 → 监控项 → 历史这个顺序是因为它们之间是标准的父子依赖关系没有 hostid 你就查不到 item没有 itemid 你就查不到 history。唯一可以跳过的是最后一步——如果你只要当前值item.get 一次就够历史数据那条尾巴可以砍掉。这也是我后面会重点讲的一个性能取舍点。1.3 认证方式怎么选user.login 还是 API Token这是第一个必须做对的选择。Zabbix 有两种认证方式用哪个取决于你的版本和场景。方式一user.login 拿临时会话 token。你发一个 user.login 请求把用户名密码传过去服务端返回一串 auth 字符串。这串东西就是你后续所有请求的通行证。老版本5.4 之前是把它放在请求体的auth字段里新版本逐步转向放在 HTTP 头Authorization: Bearer token里。这种方式的好处是密码不落盘token 是临时的坏处是每次跑脚本都要多一次握手请求。方式二API Token。Zabbix 5.4 开始支持在前端用户设置 → API tokens里生成一个长期 Token生成后只显示一次抄下来存好。之后请求直接带Authorization: Bearer token不用再登录。这是我在生产环境里推荐的方式因为脚本里不需要出现任何账号密码Token 也可以随时在前端吊销。对比项user.login 会话API Token适用版本全版本5.4 及以上是否暴露账号密码请求体里带完全不涉及Token 生命周期会话级有超时手动创建可设过期请求次数多一次登录握手直接调用权限控制跟随用户角色跟随用户角色前端可吊销需登出可单独删除注意API Token 只在创建时完整显示一次页面刷新后就再也看不到明文了。丢了只能删掉重建所以生成那一刻一定要复制到你的密码管理工具里。还有一个细节容易被忽略Token 绑定的是创建它的那个用户所以这个用户的用户类型User / Admin / Super Admin和所属用户组对主机的可见范围直接决定了你的 API 能看到哪些主机。如果你发现脚本拉回来的主机数量比界面上少八成不是接口的问题是这个 Token 对应的用户没权限看那么多主机。2. 动手之前先把这几个概念对齐正式写请求之前有几个概念必须先弄清楚否则后面你会被返回值里的字段名搞晕。这些概念不复杂但它们是理解整条链路的基础我尽量用大白话讲。2.1 host、item、history 三者到底是什么关系你可以把 Zabbix 想象成一个巨大的表格管理系统。host主机就是被监控的对象一台服务器、一个交换机、一个 URL 探针都算。它有 hostid 这个内部编号还有 host技术名通常是主机名或 IP和 name显示名可以写中文两个名字字段。区分这两点很重要很多脚本出错就是因为拿 name 去匹配但 name 是给人看的、可以重复、可以随时改真正稳的是 hostid。item监控项是挂在主机下面的具体采集指标比如CPU 使用率根分区剩余空间网卡入流量。每个 item 有自己的 itemid、key_就是那个像system.cpu.util[,idle]的键值、value_type数据类型、units单位以及 lastvalue、lastclock、prevvalue 这几个跟最新值有关的字段。history历史数据是 item 在时间轴上采集到的每一个点。它是按 itemid 和时间戳存的一次请求只能查一个 value_type 的数据这点后面会详细说。三者是严格的层级关系一个 host 下面有 N 个 item一个 item 下面有 M 条 history 记录。你的查询也是沿着这个树从上往下走的。2.2 value_type 是整条链路里最容易踩的坑Zabbix 内部把监控项的数据类型用数字编码一共五种value_type含义存储表典型监控项0浮点数historyCPU 使用率、负载1字符型history_str字符串状态值2日志型history_log日志文件内容3无符号整数history_uint网卡流量、计数类4文本型history_text大段文本输出为什么要专门列这个表因为history.get 请求里的history参数必须和 item 的 value_type 一致。你拿着一个 value_type3 的网卡流量项去请求 history0 的数据接口不会报错它会老老实实返回一个空数组。这种不报错但没数据的问题最折磨人我当年就在这上面浪费过一整个下午。所以正确的姿势是先用 item.get 把每个 item 的 value_type 拿回来按类型分桶再分别去请求对应的 history 表。这也是我在脚本里一定会加一步分组的原因。2.3 请求长什么样JSON-RPC 2.0 的固定骨架Zabbix API 走的是 JSON-RPC 2.0 协议所有请求都发到同一个地址http://你的服务器地址/zabbix/api_jsonrpc.php用 POST 方法Content-Type 是application/json。请求体的骨架永远是这几样东西{ jsonrpc: 2.0, method: 方法名, params: { 参数键: 参数值 }, id: 1 }method是你要调的方法比如host.getparams是具体参数id是请求标识你发什么它就原样返回什么用来自查。如果用的是 API Token 认证认证信息放在 HTTP 头里而不是请求体里这一点和很多人的直觉相反我第一次用的时候就习惯性地往 body 里塞 auth结果一直认证失败。返回值统一是result字段装着数据出错时是error字段装着错误码和消息。整个结构非常规整适合程序解析。3. 核心实操从零把数据捞出来这一节是重点我会按真实操作的顺序把每一步的请求、参数含义、返回结果都摆出来。你可以直接照着改地址和 Token 就能跑。3.1 第一步拿到通行证如果用账号密码方式请求是这样{ jsonrpc: 2.0, method: user.login, params: { username: Admin, password: 你的密码 }, id: 1 }返回{ jsonrpc: 2.0, result: 0424bd59b807674191e7d77572075f33, id: 1 }那个 32 位字符串就是 auth token。后续请求在 body 里加auth: 0424bd59...即可。如果用 API Token这一步直接跳过每个请求加头-H Authorization: Bearer 你的Token我个人的习惯是永远用 Token因为在 CI/CD 或者定时任务里跑脚本时密码泄露的风险比 Token 大得多而且 Token 能按用途分开创建出问题好定位。3.2 第二步host.get 把所有主机拉回来这一步的目标是拿到一份干净的主机清单重点是 hostid 和主机名。{ jsonrpc: 2.0, method: host.get, params: { output: [hostid, host, name, status], selectInterfaces: [interfaceid, ip, port, type, available], selectGroups: [groupid, name], filter: { status: 0 }, sortfield: host, sortorder: ASC }, id: 2 }几个参数值得展开说output决定返回哪些字段。千万不要写output: extend那会把主机对象的全部字段都返回字段又多又杂几百台主机下来响应体能有几 MB白白浪费带宽和解析时间。明确列出你要的字段是最省事的优化。selectInterfaces是把主机的网络接口信息一起带出来。运维报表里经常要写 IP有了这个就不用再多发一次请求。selectGroups同理带上主机组信息方便按业务线分类。filter和search的区别很多人分不清。filter是精确匹配{status: 0}表示只要启用状态的主机status1 是被禁用/未监控的。search是模糊匹配比如{host: web}会匹配所有主机名里含 web 的主机。我一般用 filter 圈定范围用 search 做调试时的快速定位。返回结果大致长这样{ result: [ { hostid: 10084, host: web-node-01, name: Web节点1, status: 0, interfaces: [ { interfaceid: 1, ip: 10.0.0.11, port: 10050, type: 1, available: 1 } ], groups: [ { groupid: 2, name: Web集群 } ] } ] }注意返回的 hostid、status 这些看起来是数字的字段类型都是字符串。这是 Zabbix API 的一个历史遗留设计你在做数值比较或者排序的时候一定要先转换否则会出现 100 99 这种字符串比较的错误结果。3.3 第三步item.get 按主机取监控项和最新值拿到 hostid 列表之后把它们塞进 item.get 的hostids参数一次请求就能把这一批主机的监控项全带回来。{ jsonrpc: 2.0, method: item.get, params: { output: [itemid, name, key_, lastvalue, lastclock, prevvalue, units, value_type, state], hostids: [10084, 10085, 10086], filter: { status: 0, state: 0 }, sortfield: name }, id: 3 }这里的参数含义要拆开讲hostids传一个数组这是提升效率的关键。你完全可以把几百个 hostid 一次性传进去而不是循环发几百次请求。Zabbix 服务端会一次性把关联的 item 都查出来网络往返次数直接降到 1。output里的lastvalue、lastclock、prevvalue是我们最关心的三个字段。lastvalue是最后一次采集到的值lastclock是那个值对应的时间戳prevvalue是上一次的值。有了前后两个值你甚至可以做简单的环比判断。filter里我加了state: 0含义是只要正常支持的监控项把state1不支持通常是因为 key 写错了或者依赖的 agent 没起来的排除掉。生产环境里不支持项往往不少不排除的话你的报表会混进一堆无意义的行。同理status: 0排除掉人为禁用的项。返回{ result: [ { itemid: 23298, name: CPU idle time, key_: system.cpu.util[,idle], lastvalue: 93.4567, lastclock: 1732000000, prevvalue: 92.1234, units: %, value_type: 0, state: 0 } ] }有个细节lastvalue虽然是数值但它是字符串返回的而且浮点数会带上原始精度。你如果在做数值运算记得float()一下如果只是展示直接原样输出反而更安全因为某些大整数转 float 会丢精度。3.4 第四步history.get 拉历史数据value_type 必须对齐只有需要过去一段时间的曲线时才走这步。请求长这样{ jsonrpc: 2.0, method: history.get, params: { output: extend, history: 0, itemids: [23298], time_from: 1731900000, time_till: 1732000000, sortfield: clock, sortorder: DESC, limit: 500 }, id: 4 }重点参数history就是前面说的 value_type 编码必须和你要查的 item 的 value_type 相同。浮点项传 0整数项传 3别搞混。time_from和time_till是 Unix 时间戳的秒数注意不是毫秒。从 Zabbix 5.x 之后实际上支持到纳秒精度的格式但传秒级整数兼容性最好。sortfield配合sortorder可以用来取最近 N 条比如DESClimit: 100就是最近 100 个点这在看最新趋势时很实用。这里必须提醒一个查不到数据的经典原因历史数据是有保存期限的。Zabbix 默认只保留一段时间的明细历史比如 7 天或 30 天超过之后明细会被清理只留下按小时聚合的趋势数据trends。你要查三个月前的数据却用 history.get返回空数组是正常的这时候应该改用trend.get参数结构基本一样只是返回的是 avg、min、max、count 这些聚合值。3.5 把它们串起来一份能直接用的 Python 脚本光看请求没有感觉我把整条链路写成一个脚本。用了 requests 库逻辑是登录 → 拿主机 → 拿监控项 → 输出表格。import requests import json import time API_URL http://zabbix.example.com/api_jsonrpc.php TOKEN 你的API_TOKEN HEADERS { Content-Type: application/json-rpc, Authorization: fBearer {TOKEN} } def call(method, params, req_id1): payload { jsonrpc: 2.0, method: method, params: params, id: req_id } resp requests.post(API_URL, headersHEADERS, datajson.dumps(payload), timeout30) resp.raise_for_status() data resp.json() if error in data: raise RuntimeError(f{data[error][code]}: {data[error][data]}) return data[result] # 1. 拿所有启用主机 hosts call(host.get, { output: [hostid, host, name], filter: {status: 0}, sortfield: host }, 1) host_ids [h[hostid] for h in hosts] host_map {h[hostid]: h[host] for h in hosts} print(f共获取主机 {len(host_ids)} 台) # 2. 批量拿监控项分批每批 100 台避免请求体过大 BATCH 100 all_items [] for i in range(0, len(host_ids), BATCH): chunk host_ids[i:i BATCH] items call(item.get, { output: [itemid, hostid, name, key_, lastvalue, lastclock, units, value_type], hostids: chunk, filter: {status: 0, state: 0}, sortfield: name }, 2) all_items.extend(items) print(f已处理主机 {i len(chunk)}/{len(host_ids)}) time.sleep(0.2) # 轻微限速别把服务端打满 # 3. 按主机和 key 归集输出最新值 result {} for it in all_items: hid it[hostid] result.setdefault(host_map.get(hid, hid), []).append({ key: it[key_], name: it[name], value: it[lastvalue], units: it[units], clock: it[lastclock] }) # 4. 打印示例 for hostname, items in list(result.items())[:3]: print(f\n {hostname} ) for it in items[:10]: print(f {it[name]:30} {it[value]} {it[units]}) # 5. 落盘 with open(zabbix_latest.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(\n结果已写入 zabbix_latest.json)这段脚本里有三个我特意加进去的处理都是踩过坑之后补的分批请求避免一次传几千个 hostid 导致请求体过大或服务端超时、轻微 sleep给服务端喘口气、先建 host_map因为 item.get 返回里只有 hostid没有主机名不建映射你拿到的就是一堆数字。4. 批量拉数怎么不把 Zabbix 拖垮脚本能跑通是一回事能不能在生产里长期稳定跑是另一回事。两千台主机、百万监控项你一个脚本如果写得糙是真的能把 Zabbix Server 的数据库 CPU 顶到 90% 以上的。这一节讲的都是规模上去之后才暴露出来的问题。4.1 分页、limit 和批大小的取值经验第一个原则永远不要指望一次请求拿到所有数据。Zabbix 虽然没有硬性的分页限制但一次返回几万条记录响应体可能上百 MB解析就够你慢的服务端内存也会抖一下。我的取值经验是这样的场景建议批量说明host.getlimit 500 或不分页主机数量通常有限几千台一次也能接受item.get每批 50~100 个 hostiditem 数量是主机的几十倍必须分批history.get优先用 limit 限制点数单 item 一次最多取几千个点trend.get每批 200 个 itemid趋势数据行数少可以放宽limit参数和分批是两个不同层面的控制。limit是限制这次最多返回多少条记录分批是把要查的对象切成几组两者要配合用。只设 limit 不切对象你会漏数据只切对象不设 limit某台主机监控项特别多的时候还是会爆。4.2 当前值用 lastvalue别用 history 绕远路这是最容易被忽略的一个性能决策。很多人想拿当前值习惯性地去 history.get 查最近一分钟的数据然后取最后一条。这么做完全没必要因为item.get 的 lastvalue 字段本身就是缓存好的最新值一次请求就能拿到成本几乎为零。history.get 是有代价的它要扫时间范围、要排序、要按 value_type 选表数据量大时很慢。所以我给的建议很直接——做资产报表、实时看板这类只要当前值的需求一律用 item.get 的 lastvalue只有真正需要时间序列曲线、需要做趋势分析时才动 history.get 或 trend.get。4.3 并发不是越高越好我见过有人为了提速开 50 个线程同时打 API。结果就是 Zabbix 的 Web 前端和数据库连接池被打满监控界面都打不开了告警延迟也跟着上来。这属于典型的为了省自己三分钟把整个监控系统拖下水。我的做法是单线程串行 短睡眠或者最多开 3~5 个线程并且在每次请求之间 sleep 0.2 秒。听起来慢但你要的是稳定不是炫技。如果你的数据量实在大更好的思路是让 Zabbix 自己做计算用计算型监控项或者聚合监控项把要的值在服务端先算好你只取结果这样能省掉大量原始数据传输。还有一个技巧值得分享给脚本加缓存。主机列表这种一小时才变一次的数据没必要每分钟都重新拉。把 host.get 的结果缓存到本地文件设置个十分钟有效期你的脚本请求量立刻就降下来了对服务端也友好。5. 报错排查速查表API 调不通的时候不要瞎试Zabbix 的报错信息其实挺清晰的。我把常见的问题整理成表方便你对着排查。5.1 认证和权限类问题现象报错信息原因与处理认证失败No permissions to referred objectToken 对应用户没权限看这些主机检查用户组权限登录失败Login name or password is incorrect账号密码错或该用户被禁用旧 Token 失效Session terminated, re-login会话超时改用 API Token 或重新登录头认证无效Not authorised头格式错确认是Bearer token中间有空格5.2 参数结构和匹配类问题现象报错信息原因与处理参数类型错Invalid params比如 hostids 传了字符串而非数组检查 JSON 结构请求体解析失败HTTP 400JSON 格式不合法或者 Content-Type 没设置对方法名拼错Method not found确认方法名小写带点如 host.getschema 校验失败类似 400 invalid schema 的提示参数结构不符合接口定义逐字段比对文档注意不要多传未知字段最后一行那个schema 校验的报错其实在很多平台的 API 里都会出现——本质是服务端在入口处对你的请求结构做了一次严格校验字段类型、字段名、必填项只要有一处对不上就直接打回。这类问题的通用排查方法是把请求体打印出来和官方文档的示例逐字段对比十有八九是多了一个字段、少了一个字段或者数组写成了对象。5.3 数据为空和数据对不上的问题这类问题最隐蔽因为接口不报错。返回空数组。先看 value_type 对不对history.get 最常见的原因再看时间范围是不是落在保留期内最后看 item 是不是被禁用。三步走下来基本能定位。主机数量比界面少。八成是 Token 用户的可见范围问题比较一下同一用户在界面右上方能选到的主机组就知道了。数值精度不对。lastvalue 是字符串浮点精度可能和你预期的不同尤其是内存、磁盘这类大数值用 float 转换时注意 IEEE 754 的精度边界。时间戳对不上。Zabbix 返回的 clock 是 Unix 秒如果你本地用的是毫秒时间戳记得除 1000。6. 几个只有真跑过才会知道的坑最后一节聊点经验性的东西这些在官方文档里基本找不到但每一条都是实打实踩出来的。6.1 文本型和日志型监控项的取法完全不同前面说 value_type 有五种实操里最特殊的是文本型4和日志型2。它们不参与趋势聚合也不会出现在 trends 里只能用 history.get 查明细而且一次能返回的量有限。如果你试图对一个大段文本的监控项做趋势查询会直接拿到空结果。我的做法是文本类监控项一律只在 item.get 里看 lastvalue不碰历史。如果确实需要追踪文本变化历史那要考虑把它拆成更小的结构化监控项或者用 Zabbix 的日志监控功能配合告警而不是硬拉历史。6.2 定期同步任务里的 Token 过期和静默失败定时任务最怕的不是报错而是静默失败。脚本跑完退出码是 0你以为成功了其实 API 返回的是空结果因为 Token 在前一天过期了。我的处理是加两道保险第一脚本里对每一项 API 返回都做非空检查空结果直接抛异常退出让调度系统能感知到失败第二在脚本末尾输出一条处理主机数 / 监控项数的汇总日志任何一次数字骤降都是信号。这两条听着简单但省了我无数次被追着问报表怎么是空的的尴尬。6.3 版本差异7.0 之后的一些变化Zabbix 迭代挺快的不同版本在 API 上有些细微差别。我目前的经验是认证头方式在 5.4 全面支持filter里用status过滤主机的能力一直稳定而 6.0 之后对部分返回字段做了调整尤其是和主机可用性、接口状态相关的字段。你如果是从老版本迁过来的脚本跑之前先拿一台测试机验证一遍别直接上生产。顺便提一句很多人问过监控项到底有多少种、怎么知道该取哪些。我的建议是先想清楚报表要什么再回头找对应的 key。比如你要 CPU就找system.cpu.util开头的要内存找vm.memory.utilization或者vm.memory.size[pavailable]要磁盘剩余找vfs.fs.size[/,pfree]。用 item.get 的时候可以配合search参数按 key 模糊过滤比如search: {key_: system.cpu}命中范围一下子就收窄了。我个人在实际操作中的体会是Zabbix API 这套东西难的不是接口本身而是你要先把手头这个需求翻译成主机 → 监控项 → 值这条清晰的数据路径。路径想清楚了剩下的就是几个固定请求来回拼装。真正拉开差距的是对批量、限流、类型这几个细节的把握——它们是让脚本从能跑变成敢长期跑的分水岭。后续如果数据量继续涨可以考虑把这套逻辑做成一个常驻服务把结果先落到一个中间库前端报表直接读中间库这样既保护了 Zabbix报表响应也快得多。