ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

群晖API接口获取全攻略:从DSM路径解析到Python调用实战

群晖API接口获取全攻略:从DSM路径解析到Python调用实战 群晖API接口获取这个话题我琢磨了挺久最后决定把这段时间在DSM上折腾API调用的经验整理出来。网上的教程大多只讲某个套件怎么调很少有人把里头的通用逻辑、路径规则、鉴权方式讲透。这篇文章就针对这个事从最基础的路径说到反向代理一步步走通。1. 动手前的关键认知群晖API的访问路径与登录规则第一次接触群晖API最容易懵的就是路径。群晖的API不是挂在/api/v1这种RESTful路径下而是统一走webapi目录下的脚本文件。比如在DSM 7环境下Download Station的接口是/webapi/DownloadStation/task.cgiFileStation的是/webapi/entry.cgi。听起来有点乱但底层逻辑是一致的webapi目录就是所有API的入口不同的套件往这个目录下放自己的cgi脚本。在动手写代码前先得把几个核心概念理清楚。群晖API和常见的REST API最大区别在于它不是基于HTTP动词来表达操作语义而是通过api、method、version这三个参数来定位一个具体接口。比如调用SYNO.DownloadStation.Task的list方法URL长这样http://你的群晖IP:5000/webapi/DownloadStation/task.cgi?apiSYNO.DownloadStation.Taskversion1methodlist这里api参数告诉群晖你要调哪个套件的接口method告诉它要执行什么操作version则是接口版本号。版本号这个东西很容易被忽略但它的重要性不亚于认证。群晖的接口版本迭代不向后兼容比如SYNO.FileStation.Listversion 1返回的字段结构和version 2完全不同写代码时定死了版本换版本后必须重新适配返回数据。还有一点值得注意群晖API默认走HTTP但如果你开启了HTTPS端口会变成5001而不是5000。用浏览器在套件中心登录后拿到的地址和API实际需要的地址不完全一样很多人就是栽在这里——用浏览器访问的URL去调接口总是报错实际上是要把路径改成/webapi/...。HTTP方法的选择也有讲究。大多数查询类接口list、getinfo用GET就能搞定但涉及创建任务、删除资源这类写操作必须用POST。POST请求的参数要放在请求体里并且编码为application/x-www-form-urlencoded不是JSON。这个细节困扰过不少人用requests的json参数传参死活报error101改成data就通了。注意参数编码方式很关键。群晖API对参数格式要求严格JSON格式的POST请求体它并不接受必须用标准的URL编码格式。2. API发现机制让群晖自己告诉你有哪些接口可用这条经验是我整个折腾过程中最大的收获。群晖官方文档覆盖的接口很少但系统本身提供了一个SYNO.API.Info接口可以查出一个套件到底暴露了哪些API、每个API的入口路径和最大版本号。换句话说不用满世界找文档直接向系统提问就行。调用方式非常简单在浏览器里访问http://你的群晖IP:5000/webapi/query.cgi?apiSYNO.API.Infoversion1methodqueryqueryallqueryall表示查询所有套件的API信息。返回的JSON结构很清晰每个key是一个套件名.模块名value里包含path入口脚本相对路径、minVersion、maxVersion、requestFormat这几个关键字段。比如返回里出现SYNO.DownloadStation.Task : { path : DownloadStation/task.cgi, maxVersion : 3 }就说明Download Station的任务管理接口在DownloadStation/task.cgi最高支持版本3。这个发现机制的价值在于它让你可以动态地适配不同群晖型号和DSM版本。有的套件在DSM 6上走task.cgi到了DSM 7就统一改走entry.cgi了如果代码里写死路径升级系统后必挂。更聪明的做法是启动时先调一次SYNO.API.Info把接口路径存进内存后面所有请求都从这张映射表里取地址。我在实际项目中会用一个小函数来获取API映射def get_api_info(host, port, sessionNone): import requests url fhttp://{host}:{port}/webapi/query.cgi params { api: SYNO_API_INFO if False else SYNO.API.Info, version: 1, method: query, query: all } resp requests.get(url, paramsparams, verifyFalse, sessionsession) return resp.json().get(data, {})注意queryall返回的数据量比较大如果只想查某个套件可以改成具体的套件名比如querySYNO.DownloadStation.Task响应速度更快解析也更省事。Cgi路径版本的差异也很值得关注。老版本DSM里query.cgi是查询入口但在DSM 7的部分版本上已经废弃统一改由entry.cgi接管。最稳妥的兼容方法是让它自动探测先请求query.cgi如果返回404或error100就换成entry.cgi重试。我在代码里专门写了这一段容错逻辑实测下来在DSM 6.2和DSM 7.2上都能正确工作。进群晖常用的几个套件API入口路径大致如下表套件模块示例入口路径主要用途SYNO.DownloadStation.Task/webapi/DownloadStation/task.cgi下载任务管理SYNO.DownloadStation.RSS/webapi/DownloadStation/RSS.cgiRSS订阅下载SYNO.FileStation.List/webapi/entry.cgi文件浏览、共享管理SYNO.FileStation.Upload/webapi/entry.cgi文件上传SYNO.VideoStation.Video/webapi/entry.cgi视频库查询SYNO.AudioStation.Audio/webapi/entry.cgi音频库查询SYNO.SurveillanceStation.Camera/webapi/entry.cgi监控摄像头操作行文至此你可能会问知道接口路径有什么实际用用处大了去了。比如你想写个自动化脚本定期把Download Station里的已完成任务移到指定共享文件夹没有API mapping知识这一步根本迈不出去。拿到路径之后配合接下来要讲的认证方式就能完全操作群晖。3. 认证与sid会话管理最容易被忽略的坑群晖API的认证机制说简单也简单登录接口拿到一个会话IDsid后续请求带上这个sid就表示你是已登录用户。但实际实现中坑比想象中的多。登录接口是SYNO.API.Auth请求路径通常是/webapi/auth.cgi参数如下apiSYNO.API.Auth version6 methodlogin account你的用户名 passwd你的密码 sessionDownloadStation formatsidformatsid这个参数特别重要没写它的话返回的会是session对象而不是纯净的sid字符串。很多初学者的代码就在这里出问题返回数据解析不到sid后面全乱套。session参数的值是影响sid作用范围的关键。它决定了你登录的是哪个套件。常用值有Core核心系统管理、FileStation文件管理、DownloadStation下载管理、VideoStation视频管理等。有一点容易被忽略不同session之间sid不通用。用FileStation登录拿到的sid去请求Download Station接口会返回error105权限不足看起来像是登录失败了其实只是session不匹配。我自己的做法是如果一个自动化任务只操作文件就用FileStation登录如果同时操作文件和下载任务就分别做两次登录拿到两个sid各用各的。虽然麻烦点但从群晖的设计逻辑来看这是最规范的做法。登录成功后返回的JSON{ success: true, data: { sid: A1B2C3D4E5F6, synotoken: xxxxxxxxxxxxxxxx } }注意这个synotoken。DSM 7之后部分管理类接口除了要带sid还要额外带SynoToken参数否则会被拒。实测中Download Station和FileStation的普通接口只要sid就能过但涉及系统设置、用户管理这类SYNO.Core接口必须加上SynoToken。所以登录响应里的字段最好全部保留不要只取sid。保持会话的方法很简单每次请求把_sid作为参数或cookie带上即可。群晖对GET和POST请求都接受_sid参数同时也会检查cookie。我习惯先把登录返回的synotoken放进cookie再用_sid参数做二次保险这样兼容性最好。Session超时的问题也要注意。默认情况下群晖的sid在5-30分钟内没有活动就会失效。如果你写的是一个长任务脚本且中间间隔较长最好在每次请求前判断错误码是否是106会话超时如果是自动重新登录。这一招能让代码真正“无人值守”。双因子认证OTP本身是安全利器但对脚本调用来说是个障碍。如果你的群晖账号开了两步验证直接密码登录会返回错误。解决办法是登录参数中增加otp_code字段params { api: SYNO.API.Auth, version: 6, method: login, account: myuser, passwd: mypassword, session: DownloadStation, format: sid, otp_code: 123456 }这个otp_code是验证App里显示的动态口令每次都要填当前值没法永久的常驻脚本。我的经验是如果需要长期运行的API脚本建议单独建一个不开启两步验证的普通用户只给它必须的目录和套件权限这样既不影响主账号安全又能稳定调用。这也是为什么后面要专门讲权限配置的原因。对了密码里的特殊字符一定要做URL编码。如果密码里有、#、这些直接放进URL会被拆碎requests库不会自动帮你编码。用urllib.parse.quote_plus处理一下才稳妥。4. 实操链路用Python Download Station API拉取种子任务前面讲了一堆理论现在拿一个最常见的场景把整条链路穿起来登录Download Station获取全部任务列表筛选出出错的任务再新增一个下载链接。这段代码我自己在DSM 7.2上跑过每一步的返回都验证了。先设定目标。Download Station是这个套件管理下载任务的核心模块SYNO.DownloadStation.Task就是它的API最大版本在6-7之间不同DSM版本有差异用前面讲到的SYNO.API.Info查询最准确。这里直接用version1兼容性最好返回的字段结构化程度高够用。首先写登录函数。打开一个requests.Session()用它来保持cookies并设置verifyFalse关闭TLS验证群晖默认自签名证书会导致证书校验失败import requests import urllib.parse import json class SynoAPI: def __init__(self, host, port, username, password, session_nameDownloadStation): self.base fhttp://{host}:{port}/webapi self.session requests.Session() self.session.verify False self.sid None self.session_name session_name self.username username self.password password def login(self, otp_codeNone): params { api: SYNO.API.Auth, version: 6, method: login, account: self.username, passwd: self.password, session: self.session_name, format: sid } if otp_code: params[otp_code] otp_code url f{self.base}/auth.cgi resp self.session.get(url, paramsparams) data resp.json() if data.get(success): self.sid data[data][sid] return self.sid else: raise Exception(f登录失败: {data.get(error)}) def list_tasks(self, limit100): params { api: SYNO.DownloadStation.Task, version: 1, method: list, _sid: self.sid, limit: limit } url f{self.base}/DownloadStation/task.cgi resp self.session.get(url, paramsparams) return resp.json() def create_task(self, uri, usernameNone, passwordNone): params { api: SYNO.DownloadStation.Task, version: 1, method: create, _sid: self.sid, uri: urllib.parse.quote_plus(uri) } if username and password: params[username] username params[password] password url f{self.base}/DownloadStation/task.cgi resp self.session.post(url, dataparams) return resp.json() def logout(self): params { api: SYNO.API.Auth, version: 6, method: logout, session: self.session_name, _sid: self.sid } url f{self.base}/auth.cgi try: return self.session.get(url, paramsparams).json() except Exception: pass注意几个细节点。创建任务时用POST参数放到请求体里uri字段必须URL编码因为种子的magnet链接里带着、?这些保留字符不编码的话链接会被截断下载任务创建后必然是错的。我一开始没编码创建出来的任务永远卡在“等待中”排查了很久才发现是URI解析问题。接着写主流程api SynoAPI(192.168.1.100, 5000, mynasuser, mypassword) api.login() # 拉取任务列表 tasks api.list_tasks(limit50) if not tasks.get(success): print(获取任务列表失败:, tasks.get(error)) else: total tasks[data][total] print(f当前共 {total} 个下载任务) for task in tasks[data][tasks]: print(f- {task[title]} | 状态: {task[status]} | 进度: {task[additional][transfer][size_downloaded]}/{task[additional][transfer][size_total]}) # 新增一个种子 result api.create_task(magnet:?xturn:btih:...) print(创建结果:, result)这个脚本的实际输出类似当前共 12 个下载任务 - 某电影1080p | 状态: downloading | 进度: 2359296/10485760 - 某软件ISO | 状态: waiting | 进度: 305664/1024MB - ... 创建结果: {success: True}跑通之后你就有了一块“遥控器”任何语言、任何设备只要能发HTTP请求就能把下载任务推到NAS上。我在家里的智能家居平台上挂了这么一个脚本手机语音说一句“下载xxx”后端调这个API直接把磁力链接丢给Download Station整个流程非常顺。脚本里别忘了在结束时调用api.logout()释放会话资源。大量长期不用的sid会占住群晖的连接池尤其当你频繁测试的时候会出现莫名其妙的登录失败。每次跑完都登出这是对NAS最基本的尊重。5. 反向代理与权限配置从能调到调得稳能本地调通接口只是第一步。多数人很快就发现很多场景需要从外网或者内网的其他设备访问群晖API这就涉及网络层配置。我强烈建议用群晖自带的“登录门户-高级设置-反向代理”功能来暴露API而不是直接把5000端口暴露出去那样安全性太差。反向代理配置要特别注意一个细节Host头。群晖的登录门户里如果用反代域名访问DSM会校验Host头。如果请求的Host头不是DSM的IP或域名登录接口会返回error105或直接跳到强制登录界面。配置反向代理时必须把proxy_set_header Host $host;加上否则API请求大概率失败。Nginx反代配置的参考写法server { listen 443 ssl; server_name nas.example.com; ssl_certificate /etc/ssl/certs/your_cert.pem; ssl_certificate_key /etc/ssl/private/your_key.key; location /webapi/ { proxy_pass http://192.168.1.100:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location / { proxy_pass http://192.168.1.100:5000; proxy_set_header Host $host; } }如果是纯内网环境懒得配Nginx也可以直接SSH端口转发ssh -L 5000:192.168.1.100:5000 userrouter然后把脚本的请求地址改成http://127.0.0.1:5000/webapi/...这样就把远程API映射到本地调试起来非常方便也能避开公网上各种扫描。再说API权限。一个安全可靠的生产级配置绝不是用管理员账号去调API。管理员权限的sid泄漏就等于整个NAS裸奔。我的建议是专门创建一个API专用用户权限按最小化原则设置。以FileStation API为例假设只需要访问/volume1/downloads目录就只给这个用户该目录的读写权限其他目录全部拒绝。权限配置路径控制面板用户与群组新建用户勾选“允许访问共享文件夹”时只勾选需要的目录并在“应用程序”选项卡里只勾选对应的套件。这样即使API的sid被截获攻击者能触及的范围也被限制在特定目录。如果你担心公开API的安全问题不妨把失败次数监控起来。群晖自带fail2ban套件可以设置“网络防护”把连续多次登录失败的IP自动拉黑。实测下来非常有效公网上的暴力破解脚本基本都会在几次尝试后被封掉。最后再提一下双因子认证。如果你在生产环境中调用API我上面说过的“专用账号不开OTP”方案虽然方便但有妥协。更严谨的做法是开OTP脚本每次运行时从密码管理器或自动脚本获取当前的otp_code。群晖的动态口令算法走的是标准TOTP用pyotp库就能生成import pyotp otp pyotp.TOTP(你的base32密钥).now() api.login(otp_codeotp)这样既保留了OTP安全等级又不影响脚本自动化只是需要在环境变量里妥善保管密钥。我自己就是走这条路线的登录一次sid有效期内的操作都安全过期后再动态获取新的otp。写到这里群晖API的里里外外基本覆盖了一遍。从路径发现、接口探测到登录鉴权、会话管理再到实际脚本和反向代理安全把这些逻辑打通之后再往上做告警、自动化、集成心里就有底了。整个系统看似封闭但只要顺着SYNO.API.Info这条线索往下摸所有套件都能自助探索清楚。这点我觉得比任何官方文档都更值得投入时间去掌握。
返回列表