# python脚本演示\`机场/代理 订阅后端\`是怎么返回流量等高级信息给clash的?

**URL:** <https://www.nodeloc.com/t/topic/69089>\
**Category:** 编程开发\
**Created:** [2025 年12 月 4 日 20:54 UTC](https://www.nodeloc.com/t/topic/69089 "2025-12-04T20:54:09Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

作者： ![linying23333](https://www.nodeloc.com/letter_avatar_proxy/v4/letter/l/f14d63/32.png) [@linying23333](https://www.nodeloc.com/u/linying23333)\
发布日期： [2025 年12 月 4 日 20:54 UTC](https://www.nodeloc.com/t/topic/69089/1 "2025-12-04T20:54:09Z")

</div>

[clash是怎么通过订阅链接获取套餐总流量数据（已使用流量）的？](https://linux.do/t/topic/262377/3)

到处翻了一下好像没什么文档讲这个(应该是开发者会自己翻别人代码吧?..索性没人做了), 有兴趣收集折腾了一下. 供其他好奇宝宝参考 🥰, 和开发?

P.S.主要这玩意不像url订阅分享链接一样, 没有一个有公信力人/团体提出统一规范. 只能纯参考着玩了

> **python代码**
>
> ```python
> import http.server
> import socketserver
> import time
> import os
> from urllib.parse import quote
> # 尝试导入qr库生成二维码在终端
> # pip install qrcode Pillow
> isPrintQRCode=False
> try:
> import qrcode
> isPrintQRCode=True
> except:
> isPrintQRCode=False
> 
> """
> 极简 机场/代理 订阅后端逻辑演示
> P.S. 部分参数来源于互联网, 未经考量仅供参考
> """
> 
> # 监听地址
> PORT = 8080
> HOST = "127.0.0.1"
> 
> # --- 配置订阅信息 ---
> # 使用量和总流量 (字节)
> UPLOAD_BYTES = 10 * 1024 * 1024 * 1024 # 10 GB
> DOWNLOAD_BYTES = 50 * 1024 * 1024 * 1024 # 50 GB
> TOTAL_BYTES = 100 * 1024 * 1024 * 1024 # 100 GB
> # 到期时间 (Unix时间戳, 设为当前时间+30天)
> EXPIRE_TIMESTAMP = int(time.time() + 30 * 24 * 3600)
> # (剩余)重置日
> RESET_TIME = 14
> # 套餐名
> PLAN_NAME = "VIP1"
> # 配置文件网站主页
> HOME_URL = "http://127.0.0.1"
> # 配置文件自动更新间隔 (小时)
> UPDATE_INTERVAL_HOURS = 12
> # 订阅配置文件名
> PROFILE_NAME = "订阅后端演示"
> # 订阅配置文件路径(允许相对路径)
> PROFILE_PATH = "sub.yaml"
> 
> # 构造 Subscription-Userinfo 头部的值
> USERINFO_HEADER_VALUE = (
> f"upload={UPLOAD_BYTES};" # 上传使用量(字节)
> f"download={DOWNLOAD_BYTES};" # 下载使用量(字节)
> f"total={TOTAL_BYTES};" # 总流量(字节)
> f"expire={EXPIRE_TIMESTAMP};" # 到期时间(Unix时间戳)
> f"reset_day={RESET_TIME};" # 非通用,(剩余)重置日
> f"auto_reset_day={RESET_TIME};" # 非通用,(剩余)重置日
> f"plan_name={quote(PLAN_NAME)};" # 非通用,套餐名(必须url参数编码)
> f"hover={quote(PLAN_NAME)};" # 非通用,套餐名(必须url参数编码)
> f"app_url={quote(HOME_URL)};" # 非通用,配置文件网站主页(必须url参数编码)
> )
> # ---------------------
> 
> class SubscriptionHandler(http.server.BaseHTTPRequestHandler):
> """
> 自定义 HTTP 请求处理器，用于模拟返回订阅信息
> """
> def do_GET(self):
> """处理所有 GET 请求"""
> print("") # 只是为了空一行
> print("=" * 40)
> # 发送 HTTP 响应状态码 (200 OK)
> self.send_response(200)
> # 设置 HTTP 响应头
> self.send_header("Content-type", "text/plain; charset=utf-8")
> # ***关键：返回高级订阅信息响应头***
> self.send_header("Content-Disposition", f'attachment;filename*=UTF-8\'\'{quote(PROFILE_NAME)}') # 订阅配置文件名(必须url参数编码)
> self.send_header("Subscription-Userinfo", USERINFO_HEADER_VALUE) # 订阅信息集
> self.send_header("Profile-Update-Interval", str(UPDATE_INTERVAL_HOURS)) # 配置文件自动更新间隔 (小时)
> self.send_header("Profile-Web-Page-Url", str(HOME_URL)) # 非通用,配置文件网站主页
> # 结束响应头
> self.end_headers()
> 
> # 发送响应体 (放订阅配置的内容)
> # 在响应体前面添加 str("#<title>{PROFILE_NAME}</title>") 以兼容clashmi客户端的只从html标题获取配置文件名的方式
> self.wfile.write(f"#<title>{PROFILE_NAME}</title>\n".encode('utf-8'))
> # 读取文件并推送二进制流
> if os.path.isfile(PROFILE_PATH):
> with open(PROFILE_PATH, "rb") as f:
> self.wfile.write(f.read())
> else:
> self.wfile.write(b"proxies:\n# profile not found") 
> print(f"=== 请求处理完成 ===")
> 
> if __name__ == " __main__":
> # 打印配置信息，方便对照
> sub_url = f"http://{HOST}:{PORT}"
> print(f"🚀 订阅演示服务器启动于 {sub_url}")
> print("演示内容:")
> print(f"* 监听地址: {HOST}:{PORT}")
> print(f"* 订阅配置文件路径: {PROFILE_PATH}")
> print(f"* 订阅配置文件名称: {PROFILE_NAME}")
> print(f"* 订阅套餐名称: {PLAN_NAME}")
> print(f"* 订阅(剩余)重置日: {RESET_TIME} 日")
> print(f"* 订阅到期时间: {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(EXPIRE_TIMESTAMP))}")
> print(f"* 订阅使用流量/总流量: 上传 {UPLOAD_BYTES / (1024**3)} GB / 下载 {DOWNLOAD_BYTES / (1024**3)} GB / {TOTAL_BYTES / (1024**3)} GB")
> print(f"* 订阅文件主页链接: {HOME_URL}")
> print("-" * 40)
> print(f"订阅后端部分响应头信息")
> print(f"Content-Disposition:\n{f'attachment;filename*=UTF-8\'\'{quote(PROFILE_NAME)}'}")
> print(f"Subscription-Userinfo:\n{USERINFO_HEADER_VALUE}")
> print("-" * 40)
> print("使用URL Schemes方式导入到客户端:")
> print(f"clash://install-config?url={quote(sub_url)}&name={quote(PROFILE_NAME)}")
> print("第三方url在线生成二维码导入:")
> print(f"https://api.qrserver.com/v1/create-qr-code/?size=400x400&data={quote(sub_url)}")
> # 如果qr库可用
> if isPrintQRCode:
> print("扫码在线导入订阅:")
> qr = qrcode.QRCode(
> version=None, # 大小自动
> error_correction=qrcode.constants.ERROR_CORRECT_L,
> # 1 像素一格，终端里刚好
> box_size=1,
> border=1,
> )
> qr.add_data(sub_url)
> qr.make(fit=True)
> # # 终端用红绿 Unicode 字符打印
> # def term_reset(): print('', end='')
> # def blk(): print('🟩', end='') # 黑块
> # def wht(): print('🟥', end='') # 白块
> # for row in qr.modules:
> # for col in row:
> # (blk if col else wht)()
> # term_reset()
> # print() # 换行
> # 终端用另一种黑白 Unicode 字符打印
> print("由于技术原因, 二维码可能需要手动鼠标选中使其发生颜色反转方可扫描. 并且在部分终端上可能发生乱码")
> print("~" * 40)
> modules = qr.modules
> for r in range(0, len(modules), 2):
> upper = modules[r]
> lower = modules[r+1] if r+1 < len(modules) else [False]*len(upper)
> line = ''
> for u, l in zip(upper, lower):
> if u and l: line += '█' # 上下都黑
> elif u and not l: line += '▀' # 上黑下白
> elif not u and l: line += '▄' # 上白下黑
> else: line += ' ' # 上下都白
> print(line)
> print("=" * 40)
>     
> # 创建 TCP 服务器
> with socketserver.TCPServer((HOST, PORT), SubscriptionHandler) as httpd:
> try:
> # 启动服务器并保持运行
> httpd.serve_forever()
> except KeyboardInterrupt:
> # 捕获Ctrl+C
> httpd.shutdown()
> print("\n👋 服务器已关闭")
> 
> # 等待用户输入
> print("\n操作完成.按回车(Enter)退出程序")
> input()
> 
> ```

~~没有什么技术力hhh, 悄摸摸扔水区就算了~~ (nl好像没水区hhh,哪里都是水区)

参考了以下链接:

> **[订阅响应头 - URL Schemes - Clash Verge Rev Docs](https://www.clashverge.dev/guide/url_schemes.html#_4)**
>
> Windows 如果无法正常使用该功能，可能是由于使用其他 Clash 代理软件（如 CFW）卸载不完全。请检查下列注册表项指向的 exe 路径是否正确。

> **[GitHub - sub-store-org/Sub-Store: Advanced Subscription Manager for QX, Loon, Surge,...](https://github.com/sub-store-org/Sub-Store)**
>
> Advanced Subscription Manager for QX, Loon, Surge, Stash, Egern and Shadowrocket!

> **[响应头 - URL Scheme | Clash for Windows](https://web.archive.org/web/20230930062020/https://docs.cfw.lbyczf.com/contents/urlscheme.html#%E5%93%8D%E5%BA%94%E5%A4%B4)**
>
> CFW 支持使用 URL Scheme 快速导入配置文件： | by Fndroid

另附使用URL Schemes方式导入到客户端的格式

```url
clash://install-config?url=<encodeURIComponent编码后的订阅url>&name=<encodeURIComponent编码后的配置文件名称>

```

---

<div class="post-metadata">

作者： ![Arvin\_Leung](https://www.nodeloc.com/user_avatar/www.nodeloc.com/arvin_leung/32/9914_2.png) [@Arvin\_Leung](https://www.nodeloc.com/u/Arvin_Leung)\
发布日期： [2025 年12 月 4 日 22:42 UTC](https://www.nodeloc.com/t/topic/69089/2 "2025-12-04T22:42:23Z")

</div>

学习了，感谢分享。

---

<div class="post-metadata">

作者： ![hynize](https://www.nodeloc.com/user_avatar/www.nodeloc.com/hynize/32/21428_2.png) [@hynize](https://www.nodeloc.com/u/hynize)\
发布日期： [2025 年12 月 5 日 01:17 UTC](https://www.nodeloc.com/t/topic/69089/3 "2025-12-05T01:17:49Z")

</div>

> [@linying23333](#):
>
> 代码
> 
> ```auto
> import http.server
> import socketserver
> import time
> import os
> from urllib.parse import quote
> 
> """
> 极简 机场/代理 订阅后端逻辑演示
> P.S. 部分参数来源于互联网, 未经考量仅供参考
> """
> 
> # 监听地址
> PORT = 8080
> HOST = "127.0.0.1"
> 
> # --- 配置订阅信息 ---
> # 使用量和总流量 (字节)
> UPLOAD_BYTES = 10 * 1024 * 1024 * 1024 # 10 GB
> DOWNLOAD_BYTES = 50 * 1024 * 1024 * 1024 # 50 GB
> TOTAL_BYTES = 100 * 1024 * 1024 * 1024 # 100 GB
> # 到期时间 (Unix时间戳, 设为当前时间+30天)
> EXPIRE_TIMESTAMP = int(time.time() + 30 * 24 * 3600)
> # (剩余)重置日
> RESET_TIME = 14
> # 套餐名
> PLAN_NAME = "VIP1"
> # 配置文件网站主页
> HOME_URL = "http://127.0.0.1"
> # 配置文件自动更新间隔 (小时)
> UPDATE_INTERVAL_HOURS = 12
> # 订阅配置文件名
> PROFILE_NAME = "订阅后端演示"
> # 订阅配置文件路径(允许相对路径)
> PROFILE_PATH = "sub.yaml"
> 
> # 构造 Subscription-Userinfo 头部的值
> USERINFO_HEADER_VALUE = (
> f"upload={UPLOAD_BYTES};" # 上传使用量(字节)
> f"download={DOWNLOAD_BYTES};" # 下载使用量(字节)
> f"total={TOTAL_BYTES};" # 总流量(字节)
> f"expire={EXPIRE_TIMESTAMP};" # 到期时间(Unix时间戳)
> f"reset_day={RESET_TIME};" # 非通用,(剩余)重置日
> f"auto_reset_day={RESET_TIME};" # 非通用,(剩余)重置日
> f"plan_name={quote(PLAN_NAME)};" # 非通用,套餐名(必须url参数编码)
> f"hover={quote(PLAN_NAME)};" # 非通用,套餐名(必须url参数编码)
> f"app_url={quote(HOME_URL)};" # 非通用,配置文件网站主页(必须url参数编码)
> )
> # ---------------------
> 
> class SubscriptionHandler(http.server.BaseHTTPRequestHandler):
> """
> 自定义 HTTP 请求处理器，用于模拟返回订阅信息
> """
> def do_GET(self):
> """处理所有 GET 请求"""
> print("") # 只是为了空一行
> print("=" * 40)
> # 发送 HTTP 响应状态码 (200 OK)
> self.send_response(200)
> # 设置 HTTP 响应头
> self.send_header("Content-type", "text/plain; charset=utf-8")
> # ***关键：返回高级订阅信息响应头***
> self.send_header("Content-Disposition", f'attachment;filename*=UTF-8\'\'{quote(PROFILE_NAME)}') # 订阅配置文件名(必须url参数编码)
> self.send_header("Subscription-Userinfo", USERINFO_HEADER_VALUE) # 订阅信息集
> self.send_header("Profile-Update-Interval", str(UPDATE_INTERVAL_HOURS)) # 配置文件自动更新间隔 (小时)
> self.send_header("Profile-Web-Page-Url", str(HOME_URL)) # 非通用,配置文件网站主页
> # 结束响应头
> self.end_headers()
> 
> # 发送响应体 (放订阅配置的内容)
> # 在响应体前面添加 str("#<title>{PROFILE_NAME}</title>") 以兼容clashmi客户端的配置名获取方式
> self.wfile.write(f"#<title>{PROFILE_NAME}</title>\n".encode('utf-8'))
> # 读取文件并推送二进制流
> if os.path.isfile(PROFILE_PATH):
> with open(PROFILE_PATH, "rb") as f:
> self.wfile.write(f.read())
> else:
> self.wfile.write(b"proxies:\n# profile not found") 
> print(f"=== 请求处理完成 ===")
> 
> if __name__ == " __main__":
> # 打印配置信息，方便对照
> print(f"🚀 订阅演示服务器启动于 http://{HOST}:{PORT}")
> print("演示内容:")
> print(f"* 监听地址: {HOST}:{PORT}")
> print(f"* 订阅配置文件路径: {PROFILE_PATH}")
> print(f"* 订阅配置文件名称: {PROFILE_NAME}")
> print(f"* 订阅套餐名称: {PLAN_NAME}")
> print(f"* 订阅(剩余)重置日: {RESET_TIME} 日")
> print(f"* 订阅到期时间: {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(EXPIRE_TIMESTAMP))}")
> print(f"* 订阅使用流量/总流量: 上传 {UPLOAD_BYTES / (1024**3)} GB / 下载 {DOWNLOAD_BYTES / (1024**3)} GB / {TOTAL_BYTES / (1024**3)} GB")
> print(f"* 订阅文件主页链接: {HOME_URL}")
> print("-" * 40)
> print(f"订阅后端部分响应头信息")
> print(f"Content-Disposition:\n{f'attachment;filename*=UTF-8\'\'{quote(PROFILE_NAME)}'}")
> print(f"Subscription-Userinfo:\n{USERINFO_HEADER_VALUE}")
> print("=" * 40)
>     
> # 创建 TCP 服务器
> with socketserver.TCPServer((HOST, PORT), SubscriptionHandler) as httpd:
> try:
> # 启动服务器并保持运行
> httpd.serve_forever()
> except KeyboardInterrupt:
> # 捕获Ctrl+C
> httpd.shutdown()
> print("\n👋 服务器已关闭")
> 
> # 等待用户输入
> print("\n操作完成.按回车(Enter)退出程序")
> input()
> 
> ```
> 
> 没有什么技术力hhh, 悄摸摸扔水区就算了
> 
> 参考了以下链接:  
> [URL Schemes - Clash Verge Rev Docs](https://www.clashverge.dev/guide/url_schemes.html#_4)

不知道订阅转换能干嘛？没成功过

---

<div class="post-metadata">

作者： ![beteC](https://www.nodeloc.com/letter_avatar_proxy/v4/letter/b/7ea924/32.png) [@beteC](https://www.nodeloc.com/u/beteC)\
发布日期： [2025 年12 月 5 日 01:18 UTC](https://www.nodeloc.com/t/topic/69089/4 "2025-12-05T01:18:13Z")

</div>

感谢分享

---

<div class="post-metadata">

作者： ![azhaoyihao](https://www.nodeloc.com/user_avatar/www.nodeloc.com/azhaoyihao/32/14_2.png) [@azhaoyihao](https://www.nodeloc.com/u/azhaoyihao)\
发布日期： [2025 年12 月 5 日 03:16 UTC](https://www.nodeloc.com/t/topic/69089/5 "2025-12-05T03:16:42Z")

</div>

感谢大佬分享分享科普……

---

<div class="post-metadata">

作者： ![linying23333](https://www.nodeloc.com/letter_avatar_proxy/v4/letter/l/f14d63/32.png) [@linying23333](https://www.nodeloc.com/u/linying23333)\
发布日期： [2025 年12 月 5 日 07:00 UTC](https://www.nodeloc.com/t/topic/69089/6 "2025-12-05T07:00:00Z")

</div>

> [@hynize](#):
>
> 不知道订阅转换能干嘛？

没什么, 因为不同人用不同的代理客户端.  
于是就出现了订阅转换, 来方便的兼容各种代理客户端使用公开协议连接代理服务器实现代理

但是他不万能啊, 各种客户端按自己的想法做客户端. 很多都互相不兼容…  
导致他不一定有用, 毕竟这个属于社区自己搞的, 有些客户端作者想改就改, 根本不考虑兼容你

nl权限怎么又bug啦!!! 我明明加了青铜权限要求啊, 怎么没了
