自制Natapp自动化工具:告别手动配置的烦恼
最近项目需要频繁使用内网穿透,每次用Natapp都得重复一套流程:登录账号 → 购买免费隧道 → 复制token → 配置客户端 → 启动服务。做多了真的很烦,于是花了个周末时间写了个自动化脚本。
现在只需要一行命令就能搞定所有操作,还给它做了个看起来不错的终端界面。分享一下实现过程,说不定对有类似需求的朋友有帮助。
功能特性
这个工具主要实现了以下功能:
- 自动登录 - 不用每次手动输入账号密码
- 自动购买隧道 - 自动申请免费隧道,获取token
- 智能端口管理 - 支持命令行指定端口,自动修改现有隧道配置
- 可视化界面 - 使用Rich库做了个好看的终端界面
- 实时监控 - 显示系统状态和隧道信息
- 一键启动 - 整个流程一行命令搞定
使用效果
原本需要手动操作3-5分钟的事情,现在10秒钟自动完成。界面看起来也比原来的黑窗口专业多了。
效果展示

相比原生的命令行工具,这个界面看起来确实更直观一些,能够清楚地看到隧道状态、系统信息等。
实现思路
项目结构
1 2 3 4 5 6
| nat_app/ ├── auto_natapp.py # 核心自动化逻辑 (723行) ├── full_auto_natapp.py # 主程序+UI界面 (517行) ├── natapp # natapp官方客户端 ├── config.ini # 自动生成的配置文件 └── README.md # 说明文档
|
核心模块
- auto_natapp.py - 负责网站自动化操作,包括登录、购买隧道、token提取等
- full_auto_natapp.py - 主程序入口,负责UI展示和用户交互
- natapp客户端 - 官方提供的隧道客户端程序
- config.ini - 自动生成的natapp配置文件
技术实现
自动登录模块
登录这块踩了个小坑,natapp的表单字段用的是login而不是常见的username,刚开始一直登录失败:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| def login(self, username: str, password: str) -> bool: """登录natapp账户""" try: login_page_url = f"{self.base_url}/login" response = self.session.get(login_page_url) import re csrf_token = None csrf_match = re.search(r'name=["\']_token["\'] value=["\']([^"\']+)["\']', response.text) if csrf_match: csrf_token = csrf_match.group(1) logger.info(f"🔑 获取到CSRF token: {csrf_token[:20]}...") login_data = { 'login': username, 'password': password, 'agree': '1', } if csrf_token: login_data['_token'] = csrf_token response = self.session.post(login_url, data=login_data, headers=headers, allow_redirects=True) success_indicators = [ 'dashboard' in response.url.lower(), '用户中心' in response.text, 'logout' in response.text.lower(), ] return any(success_indicators) except Exception as e: logger.error(f"登录过程出错: {e}") return False
|
关键提示
Natapp的登录表单使用的是login字段而不是常见的username字段,这是很多自动化脚本失败的原因。
自动购买隧道
购买隧道这块相对复杂一些,需要处理表单提交和CSRF验证。核心思路就是模拟浏览器操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| def buy_free_tunnel(self, local_port: int = DEFAULT_PORT, tunnel_name: str = DEFAULT_TUNNEL_NAME) -> Optional[str]: """自动购买免费隧道""" try: buy_url = f"{self.base_url}/tunnel/buy/free" response = self.session.get(buy_url) csrf_token = None csrf_match = re.search(r'name=["\']_token["\'] value=["\']([^"\']+)["\']', response.text) if csrf_match: csrf_token = csrf_match.group(1) buy_data = { 'name': tunnel_name, 'local_port': str(local_port), 'protocol': 'http', } if csrf_token: buy_data['_token'] = csrf_token buy_response = self.session.post(buy_url, data=buy_data, headers=headers, allow_redirects=True) time.sleep(3) return self._get_tunnel_token_from_api() except Exception as e: logger.error(f"购买免费隧道失败: {e}") return None
|
智能端口管理
端口管理算是我比较满意的一个功能,可以直接通过命令行参数修改现有隧道的端口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| def edit_tunnel_port(self, tunnel_id: str, new_port: int) -> bool: """修改隧道的本地端口""" try: edit_url = f"{self.base_url}/tunnel/edit/{tunnel_id}" edit_response = self.session.get(edit_url) current_data = {} name_match = re.search(r'name=["\']name["\'] value=["\']([^"\']*)["\']', edit_response.text) if name_match: current_data['name'] = name_match.group(1) protocol_match = re.search(r'<option[^>]*selected[^>]*value=["\']([^"\']+)["\']', edit_response.text) if protocol_match: current_data['protocol'] = protocol_match.group(1) update_data = { 'name': current_data['name'], 'local_port': str(new_port), 'protocol': current_data['protocol'], } update_response = self.session.post(edit_url, data=update_data, headers=headers, allow_redirects=True) return update_response.status_code == 200 except Exception as e: logger.error(f"修改隧道端口失败: {e}") return False
|
终端界面优化
既然都做自动化了,界面当然也不能太寒酸。用Rich库做了个看起来还不错的终端界面:
品牌标题设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| def main(port=None): console = Console() start_time = datetime.datetime.now() console.clear() header_text = Text() header_text.append("🚀 ", style="bold red") header_text.append("LEION", style="bold white on blue") header_text.append(" ", style="") header_text.append("NATAPP", style="bold white on green") header_text.append(" ", style="") header_text.append("AUTOMATION", style="bold white on magenta") header_text.append(" ⚡", style="bold yellow") version_text = Text() version_text.append("v2.0.0", style="dim cyan") version_text.append(" • ", style="dim white") version_text.append(f"Started at {start_time.strftime('%H:%M:%S')}", style="dim cyan") copyright_text = Text() copyright_text.append("© 2025 Leion - All Rights Reserved", style="dim yellow") copyright_text.append(" • ", style="dim white") copyright_text.append("Powered by Rich Terminal UI", style="dim green") title_panel = Panel( Align.center(title_content), box=box.DOUBLE_EDGE, style="bright_blue", padding=(1, 3), title="[bold white]🎯 Leion's Professional Tunnel Manager[/bold white]", title_align="center" ) console.print(title_panel)
|

系统信息展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def get_system_info(): try: cpu_percent = psutil.cpu_percent(interval=1) memory = psutil.virtual_memory() return { 'platform': platform.system(), 'cpu_percent': cpu_percent, 'memory_percent': memory.percent, 'python_version': platform.python_version() } except: return None
config_tree = Tree("📊 [bold blue]System Configuration[/bold blue]") config_tree.add(f"[cyan]Account:[/cyan] [green]{username[:3]}***[/green]") config_tree.add(f"[cyan]Target Port:[/cyan] [bright_yellow]{local_port}[/bright_yellow]") config_tree.add(f"[cyan]Mode:[/cyan] [bright_magenta]Leion Auto Deploy[/bright_magenta]") config_tree.add(f"[cyan]Author:[/cyan] [bright_blue]Leion Charles[/bright_blue]")
system_tree = Tree("🖥️ [bold green]System Status[/bold green]") system_tree.add(f"[cyan]Platform:[/cyan] [white]{sys_info['platform']}[/white]") system_tree.add(f"[cyan]CPU Usage:[/cyan] [yellow]{sys_info['cpu_percent']:.1f}%[/yellow]") system_tree.add(f"[cyan]Memory:[/cyan] [yellow]{sys_info['memory_percent']:.1f}%[/yellow]")
trees = Columns([config_tree, system_tree], equal=True, expand=True) console.print(trees)
|
隧道状态面板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| dashboard = Table( show_header=True, header_style="bold white on blue", box=box.DOUBLE_EDGE, title="[bold white]🌐 ENTERPRISE TUNNEL DASHBOARD[/bold white]", title_style="bold green on black", border_style="bright_green", padding=(1, 2), expand=True ) dashboard.add_column("Metric", style="bold cyan", width=20) dashboard.add_column("Value", style="bold white", width=35) dashboard.add_column("Status", style="bold green", width=15)
dashboard.add_row("🌍 Public Endpoint", f"[link={tunnel_url}]{tunnel_url}[/link]", "🟢 ONLINE") dashboard.add_row("📍 Local Port", f"[bright_yellow]{local_port}[/bright_yellow]", "🟢 BOUND") dashboard.add_row("📊 Connection", "[bright_green]● ESTABLISHED[/bright_green]", "🟢 ACTIVE") dashboard.add_row("⚡ Service Mode", "[bright_magenta]Leion Professional[/bright_magenta]", "🟢 ENABLED") dashboard.add_row("🕐 Deploy Time", f"[cyan]{elapsed_seconds:.2f}s[/cyan]", "🟢 FAST") dashboard.add_row("🔐 Encryption", "[green]TLS 1.3 Enabled[/green]", "🟢 SECURE")
console.print(dashboard)
|

配置系统
为了方便别人使用,把所有需要修改的个人信息都放在了文件开头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
NATAPP_USERNAME = "**********" NATAPP_PASSWORD = "**********"
NATAPP_CLIENT_PATH = "/Users/leion/Charles/nat_app/natapp" CONFIG_FILE_PATH = "/Users/leion/Charles/nat_app/config.ini"
DEFAULT_PORT = 3000 DEFAULT_TUNNEL_NAME = "我的免费隧道"
|
配置文件生成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def update_config(self, authtoken: str, local_port: int = DEFAULT_PORT) -> bool: """更新配置文件""" try: config_content = f"""#将本文件放置于natapp同级目录 程序将读取 [default] 段 #在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置 #命令行参数 -config= 可以指定任意config.ini文件 [default] authtoken={authtoken} #对应一条隧道的authtoken clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空, log=stdout #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none loglevel=INFO #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空 """ with open(self.config_path, 'w', encoding='utf-8') as f: f.write(config_content) return True except Exception as e: logger.error(f"更新配置文件时出错: {e}") return False
|
使用方法
环境准备
- 环境准备
1 2 3 4 5
| pip3 install rich psutil requests
|
- 配置个人信息
1 2 3
| NATAPP_USERNAME = "你的用户名" NATAPP_PASSWORD = "你的密码"
|
- 设置全局别名
1 2 3 4 5
| alias natapp='python3 /path/to/your/full_auto_natapp.py'
source ~/.zshrc
|
具体使用
1 2 3 4 5 6 7 8 9 10
| natapp
natapp -p 8080 natapp -p 3000 natapp -p 9000
natapp test
|
技术细节
主要用到的技术
- Python 3.13+ - 语言基础
- Rich库 - 终端界面美化,这个库真的很好用
- Requests - HTTP请求,爬虫必备
- Psutil - 系统监控信息获取
- 正则表达式 - 从HTML中提取数据
- Subprocess - 调用natapp客户端
一些解决的问题
CSRF Token处理
- 这个花了不少时间调试,natapp的token提取有好几种格式
- 写了多个正则表达式来适配不同页面
- 加了重试机制防止偶然失败
表单分析
- 不同页面的表单字段名不太一样,需要动态识别
- 登录页面用的是
login字段而不是username,这个坑了我好久
- 加了各种错误检测和恢复逻辑
实时监控
- 用psutil获取系统资源使用情况
- 监控隧道连接状态
- 在界面上实时展示各种指标
界面设计
- Rich库的布局系统还挺强大的
- 各种颜色和样式搭配了很久才满意
- 加了动态进度条和状态指示
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| success_indicators = [ response.url != login_page_url and 'login' not in response.url.lower(), 'dashboard' in response.url.lower(), '用户中心' in response.text, 'logout' in response.text.lower(), ]
if any(success_indicators): logger.info("✅ 登录成功") return True
error_indicators = [ '用户名或密码错误' in response.text, '登录失败' in response.text, '验证码' in response.text, ]
if any(error_indicators): logger.error("❌ 登录失败") return False
|
性能优化
启动速度优化
- 并行进度条 - 多任务同时执行
- 智能缓存 - 减少重复请求
- 快速响应 - 优化UI渲染
连接稳定性
- Session复用 - 保持连接状态
- 自动重连 - 网络异常恢复
- 超时控制 - 避免长时间等待
效果对比
功能对比
| 功能 |
手动操作 |
自动化工具 |
| 登录账户 |
需要打开浏览器手动输入 |
✅ 全自动 |
| 购买隧道 |
需要填写表单提交 |
✅ 全自动 |
| 配置客户端 |
需要复制token手动配置 |
✅ 全自动 |
| 端口修改 |
需要访问网页手动修改 |
✅ 命令行一键修改 |
| 状态监控 |
无法直观查看 |
✅ 实时仪表板 |
使用体验
【图片说明:请截取完整的使用流程,从启动到隧道建立成功的全过程】
- 启动界面 - 华丽的品牌标题和系统信息
- 执行过程 - 清晰的进度指示和状态反馈
- 成功界面 - 详细的隧道信息和监控数据
- 控制面板 - 专业的操作指导和状态展示
后续计划
功能扩展
界面改进
总结
写这个工具其实就是因为懒,每次用Natapp都要重复那一套流程实在太烦了。本来想着随便写个脚本凑合用,结果越写越兴奋,最后搞成了这样。
主要收获
- 自动化 - 彻底解放双手,10秒钟搞定以前3-5分钟的操作
- 界面 - Rich库确实好用,做出来的效果比预期好很多
- 稳定性 - 各种边界情况都考虑到了,基本不会出错
- 实用性 - 每天都在用,确实提高了不少效率
技术方面的体会
- Web自动化比想象中复杂,各种token和表单验证要处理好
- 终端UI设计也是门学问,用户体验很重要
- 错误处理和边界情况处理占了很大一部分工作量
- 代码组织和配置管理对后期维护很关键
总的来说,这个小工具虽然功能不复杂,但做得还算用心。如果你也经常用Natapp,不妨试试这个自动化工具,应该能省不少时间。
相关资源
项目代码都在本地,暂时没上传到GitHub。如果有需要的话可以考虑开源出来。
使用建议
这个工具主要是为了提高开发效率,如果你也有类似的重复性操作需求,不妨尝试写个自动化脚本。Python + Rich的组合确实挺好用的。
写这篇文章主要是记录一下开发过程,顺便分享给可能用得上的朋友。代码不算复杂,但确实解决了实际问题,这就够了。