Python telnet登录网络设备:自动化运维入门第一课 做网络工程师这些年有个感受越来越明显如果还在靠手工一条条敲命令维护设备效率真的会被自动化工具甩开一大截。今天这篇就聊聊Python远程登录设备的第一课——telnet。很多人一听“用Python管理网络设备”就觉得门槛高其实telnet这关打通了后面做批量巡检、自动备份配置、割接前批量下发命令全都顺理成章。这篇文章适合两类人一是刚接触网络自动化的网工二是想把手头重复登录设备的工作交给脚本的运维同学。我会从实验环境搭建开始把登录时序、核心代码、排错技巧一条条讲透保证你跟着敲完就能用。1. 为什么要用Python登录网络设备telnet为什么是第一课1.1 日常巡检和配置的重复劳动脚本能省多少事我见过太多同行每天上班先手动登录几台设备敲一遍display version、display current-configuration再把日志复制到文档里。一台两台还能忍设备一多光登录就耗掉半小时更别提还要逐条核对配置差异。这类工作有一个共同特点重复、机械、有固定流程。而Python最擅长的恰恰就是处理这类“重复且有规律”的事。用脚本登录设备本质上是把“人敲键盘”的过程变成“程序自动读写”的过程。你只需要把登录、发命令、读回显这套动作写成脚本剩下的就是循环执行。1.2 相比SSHtelnet做入门实验的优势在哪很多新手一上来就问现在设备都支持SSH了为什么还要学telnet我的回答是正因为telnet足够“原始”它才是理解网络自动化的最佳入口。telnet协议很直白传输的是明文文本交互逻辑就是“客户端发字符串设备回字符串”。你不用先搞懂加密握手、密钥交换这些概念就能看到完整的对话过程。而且Python标准库自带telnetlib不需要安装任何第三方包打开编辑器就能写。相比之下SSH涉及密钥认证、算法协商虽然安全性更高但对初学者来说干扰项太多。当然生产环境我强烈建议用SSH这点后面专门讲。作为入门实验telnet可以把注意力完全集中在“如何与设备对话”这件事上。1.3 实验环境搭建模拟器、设备IP和Python版本做这类实验千万别直接在现网设备上试。我的习惯是用模拟器起一台虚拟设备比如常见的图形化网络仿真工具里加一台路由器或交换机设置好管理地址开启telnet服务用户名密码设成实验账号。网络拓扑可以很简答一台PC也就是你的电脑一台设备两者能互通即可。假设设备管理地址是192.0.2.1PC是192.0.2.100。注意192.0.2.0/24这段地址是专门留作文档示例用的不会跟真实网络冲突非常适合写教程。Python版本建议用3.10或3.11。为什么特意提这个因为Python 3.13起标准库里的telnetlib被移除了。如果你装的是最新版Python直接import telnetlib会报错这个坑我放到后面“常见问题”详细说。现在先假设你用的是3.10或3.11。2. 开始写脚本前先搞懂设备交互的三个底层问题2.1 设备CLI的本质是一场字符串对话网络设备的命令行界面看起来挺复杂但抽象到底层就是一场一问一答的字符串对话。你登录设备时设备会先问“Username:”你输入用户名再问“Password:”你输入密码密码验证通过后设备给你一个提示符比如“”或“#”表示“我准备好了你说吧”。你敲一条命令设备执行完回显结果再给你一个提示符。所以用Python控制设备本质上就是三件事读取设备发来的字符串等待提示符、等待回显发送你构造的字符串用户名、密码、命令循环“发送-等待-读取”这个过程直到完成所有操作理解了这一点你就不会觉得读代码有多难了。2.2 字节与文本encode和decode绕不过去的坎telnet传输的原始数据是字节流不是我们平时看到的字符串。简单说你在Python里写admin是一个字符串但在网络上传输时它得先编码成字节比如badmin设备返回的数据在Python里拿到的也是字节比如bUsername:。这就涉及两个方法encode把字符串转成字节比如admin.encode(ascii)decode把字节转成字符串比如badmin.decode(ascii)实际操作中用户名密码、命令这些内容基本都是ASCII字符所以用ascii编码就够了。但设备回显里如果是中文就需要注意解码方式常见的是utf-8或gbk具体看设备配置。我在代码里一般用errorsignore来避免解码异常导致程序崩溃。2.3 登录时序拆解把“人机对话”翻译成代码人登录设备的顺序是连上设备看见“Username:”输入用户名看见“Password:”输入密码进入命令行界面。脚本要做的也是一模一样的事只是把“看见”变成read_until把“输入”变成write。这里最关键的是“等待”。你不能脚本一运行就把用户名密码全部发出去因为设备还没准备好。正确做法是建立TCP连接调用read_until等设备发来“Username:”这个关键字调用write发送用户名后面补一个换行符再调用read_until等设备发来“Password:”调用write发送密码加换行符调用read_until等设备进入命令行提示符每一步等待都很重要。设备处理需要时间网络传输需要时间脚本如果不等直接发命令就会丢失或者被设备当成乱码忽略掉。3. 最小可用的telnet脚本从0到1一步步写出来3.1 第一步建立TCP连接并设置超时Python的telnetlib库封装了telnet协议核心类是Telnet。创建连接非常简单import telnetlib HOST 192.0.2.1 USER admin PASSWORD 123456 tn telnetlib.Telnet(HOST, timeout10)这里timeout10表示连接和后续读操作的最长等待时间是10秒。这个参数非常重要如果设备不可达没有超时的话脚本会一直挂在那里看起来就像“死机”了。3.2 第二步等待提示符发送用户名和密码连接建立后设备会推送登录提示。我们用read_until方法等待关键字出现tn.read_until(bUsername:, timeout5) tn.write(USER.encode(ascii) b\n) tn.read_until(bPassword:, timeout5) tn.write(PASSWORD.encode(ascii) b\n)注意几个细节。read_until返回的是字节参数也是字节要用bUsername:这种写法。write发送的字节后面要加换行符模拟你按回车。换行符我习惯用b\n但有些老设备只认b\r\n如果发现命令不执行把这个换成b\r\n试试。密码发送后不需要等待设备回显密码内容因为设备本来就不回显密码。直接进入下一步tn.read_until(b, timeout5)这里匹配的是用户视图提示符。不同设备提示符可能不一样有的是有的是[主机名]你需要以实际设备为准。建议用设备提示符里比较独特的结尾关键字避免误匹配。3.3 第三步发送命令并读取回显登录成功后就可以发命令了。我常用的模式是发送命令等待1-2秒然后把缓冲区里的所有内容读出来。import time tn.write(bdisplay version\n) time.sleep(1) output tn.read_very_eager().decode(ascii, errorsignore) print(output)这里time.sleep(1)是给设备留出处理命令并回显的时间。read_very_eager会把缓冲区里暂时能读到的内容全部取出来不会等待。这种“先睡再读”的方式在实战中用得非常多比死等提示符更简单可靠。3.4 完整示例代码整合起来一个最小可用的telnet登录脚本就是这样的import telnetlib import time HOST 192.0.2.1 USER admin PASSWORD 123456 def main(): tn telnetlib.Telnet(HOST, timeout10) tn.read_until(bUsername:, timeout5) tn.write(USER.encode(ascii) b\n) tn.read_until(bPassword:, timeout5) tn.write(PASSWORD.encode(ascii) b\n) tn.read_until(b, timeout5) print(login success) tn.write(bdisplay version\n) time.sleep(1) output tn.read_very_eager().decode(ascii, errorsignore) print(output) tn.write(bquit\n) tn.close() if __name__ __main__: main()这一段代码就是后面所有自动化脚本的地基。你把它跑通就已经完成“用Python远程登录设备”这件事了。3.5 封装成函数避免脚本越写越乱真实场景下你不会只登一台设备也不会只执行一条命令。所以我建议尽早把登录和发送命令封装成函数。import telnetlib import time def login(host, user, password, timeout10): tn telnetlib.Telnet(host, timeouttimeout) tn.read_until(bUsername:, timeout5) tn.write(user.encode(ascii) b\n) tn.read_until(bPassword:, timeout5) tn.write(password.encode(ascii) b\n) tn.read_until(b, timeout5) return tn def send_command(tn, cmd, wait1): tn.write(cmd.encode(ascii) b\n) time.sleep(wait) return tn.read_very_eager().decode(ascii, errorsignore)这样调用起来就很清晰tn login(192.0.2.1, admin, 123456) print(send_command(tn, display version)) print(send_command(tn, display interface brief)) tn.close()把登录和命令发送分开以后做批量巡检时只需要循环调用login和send_command就行代码结构会清爽很多。4. 实操演示用脚本给设备配置一个接口地址4.1 明确实验目标配置接口并验证结果光登录打印版本还不够我们来做一个更有实际意义的实验用脚本给设备的某个接口配置一个IP地址然后再查看接口状态确认配置生效。这个流程放到手工操作里很简单进入系统视图进入接口视图配置IP退出查看接口。放到脚本里就要注意每一步的等待和提示符变化。4.2 命令序列设计哪些命令先发哪些后发以我这台实验设备为例配置接口的命令序列是这样的system-view interface GigabitEthernet0/0/0 ip address 192.0.2.10 255.255.255.0 quit quit display ip interface brief注意进入系统视图后设备的提示符会从“”变成“[]”这种带方括号的形式进入接口视图后提示符又会变化。每次改变视图最好都等待对应提示符出现或者干脆用“发送命令sleep”的方式。实际上我会这样做tn.write(bsystem-view\n) time.sleep(1) tn.read_very_eager() tn.write(binterface GigabitEthernet0/0/0\n) time.sleep(1) tn.read_very_eager() tn.write(bip address 192.0.2.10 255.255.255.0\n) time.sleep(1) tn.read_very_eager() tn.write(bquit\n) tn.write(bquit\n) time.sleep(1) tn.read_very_eager() tn.write(bdisplay ip interface brief\n) time.sleep(1) output tn.read_very_eager().decode(ascii, errorsignore) print(output)这份命令是典型配置操作在模拟环境里跑没问题。但请务必记住不同厂商、不同型号设备的视图变化和命令风格差异很大你需要先手工登录看一眼提示符是什么再写匹配逻辑。4.3 运行结果分析与回显解读脚本跑完以后如果一切正常最后的display ip interface brief回显里应该能看到接口GigabitEthernet0/0/0的地址已经变成192.0.2.10状态是up。如果看到地址没变或者接口状态是down就要排查了地址是不是被占用接口是不是没开启命令是不是少了一个换行符这一整套流程跟手工配置时遇到问题的排查思路完全一样只是排查入口从“敲命令”变成了“看脚本日志”。4.4 sleep和read_until怎么搭配才不翻车很多新手纠结到底用read_until等提示符还是用sleep硬等我的经验是两个混着用登录阶段用read_until因为“Username:”“Password:”这些关键字非常固定匹配准确而且登录属于单次操作等多久都有耐心。命令执行阶段用sleepread_very_eager因为命令回显内容不固定你没法每次都准确预测结尾提示符。而且有些命令回显特别长read_until容易串到中间某个字符上。sleep的时长也要灵活。执行display current-configuration这种长命令设备可能要好几秒才能回显完1秒不够就设3秒、5秒。执行system-view这种短命令0.5秒都够。我的习惯是统一的send_command函数默认等1秒遇到长命令单独指定更长的等待时间。5. 常见问题与排错实录收藏这份避坑清单5.1 半天连不上连接失败和超时怎么查最常见的问题就是脚本卡在Telnet(host, timeout10)这一步过一会儿报错。先检查网络能不能通我用socket来测import socket s socket.create_connection((192.0.2.1, 23), timeout5) print(port 23 open) s.close()如果端口都不通那就是设备telnet服务没开或者中间有防火墙拦了。如果在模拟器里还要看设备接口是不是upIP配没配对。5.2 明明输入正确登录却失败用户名密码都对但脚本就是登不进去。我踩过的一个坑是密码提示还没出现脚本就把密码发出去了。设备启动慢或者网络延时大设备还没显示Password:脚本就已经执行到read_until(bPassword:)这步了read_until会一直等到超时然后你发的密码其实会被发送到错误的位置。解决办法把read_until的超时时间设长一点比如10秒。如果还不行在连接建立后先read_until(bUsername:)一定等它出现再处理。5.3 命令没执行或者执行一半就停了在模拟器里我还遇到过发送命令后提示符变了但命令没生效。查了半天发现是换行符问题。有些设备必须收到\r\n才认为你按了回车只发\n会被忽略。遇到命令不执行优先尝试把换行符从b\n改成b\r\n。另外命令发送太快也会出问题设备还在处理上一条下一条就到了。中间加个sleep就能解决。5.4 中文回显乱码怎么办设备回显里如果有中文用decode(ascii)大概率乱码。我的做法是优先试utf-8output tn.read_very_eager().decode(utf-8, errorsignore)如果还是乱码就换gbkoutput tn.read_very_eager().decode(gbk, errorsignore)不同厂商的设备默认字符集不太一样具体用哪个看看手册。但配合errorsignore至少不会因为乱码把脚本搞崩。5.5 新版Python用不了telnetlib怎么办前面提过Python 3.13移除了telnetlib。如果你用的是新版import telnetlib会直接报ModuleNotFoundError。两个解决方案一是装回Python 3.10或3.11很多做网络自动化的人至今还锁在3.8到3.11之间就是为了兼容这些老库二是用netmiko库它底层支持telnet协议而且专门为网络设备设计体验比裸写telnetlib好很多。这个方案后面我详细说。整理一份速查表方便排错时快速对照现象可能原因解决办法连接超时网络不通或telnet服务未开用socket测端口连通性检查设备配置登录失败用户名/密码错误或时序错位核对密码增大read_until超时时间命令不执行换行符不对换b\r\n重试回显乱码编码不匹配尝试utf-8或gbk配合errorsignore脚本卡死没有设置timeout所有read_until都加timeout参数telnetlib报错Python版本过高改用Python 3.11或netmiko6. 从单台设备到批量运维下一步就这么走6.1 多设备批量巡检的脚本骨架telnet登录跑通以后批量巡检就是顺水推舟的事。把设备信息放在列表里循环调用登录和命令函数就行devices [ {host: 192.0.2.1, user: admin, password: 123456}, {host: 192.0.2.2, user: admin, password: 123456}, {host: 192.0.2.3, user: admin, password: 123456}, ] for device in devices: print(, device[host], ) try: tn login(device[host], device[user], device[password]) output send_command(tn, display version) print(output) tn.close() except Exception as e: print(error:, e)每个设备都加上try/except一台失败不影响后面的设备。日志最好写到文件里方便后续查看with open(inventory_result.txt, a, encodingutf-8) as f: f.write(device[host] \n) f.write(output \n)6.2 生产环境为什么建议换更成熟的库telnetlib虽然简单但只适合学习和小范围使用。生产环境我强烈建议切换到netmiko或paramiko这类成熟库。paramiko是SSH协议库适合写底层逻辑netmiko建立在paramiko之上针对网络设备做了大量适配。我们常用的操作在netmiko里就是几行代码from netmiko import ConnectHandler device { device_type: cisco_ios_telnet, host: 192.0.2.1, username: admin, password: 123456, } with ConnectHandler(**device) as conn: output conn.send_command(display version) print(output)netmiko封装好了登录等待、提示符识别、命令回显这些繁琐细节拿到设备类型就能用。这也是为什么我说telnetlib更像是一块跳板把底层原理摸清了再用netmiko会特别顺手因为你知道它背后替你干了哪些事。另外生产环境一定要用SSH替代telnet。telnet是明文传输用户名密码和命令内容在网络上是裸奔的抓包就能看到。实验随便玩正式环境必须把安全放在第一位。6.3 自动化脚本的三个工程化习惯最后分享三个我从实战里总结出来的习惯适合所有准备把脚本往自动化方向推的人。第一login和send_command函数要返回和接收连接对象。这样单个脚本可以灵活组合不会为了加一个功能就要重写整个流程。第二所有连接都要考虑异常释放。可以用try/finally确保tn.close()一定执行避免脚本异常退出后设备上残留半开连接。第三操作日志必须完整。什么时间、登录了哪台设备、发了什么命令、回显是什么都记录下来。出故障的时候这些日志就是第一手排查资料。我在实际项目里做自动化配置下发时甚至会在脚本里加一个审计级日志模块每次操作都写清楚谁在什么时间改了哪台设备的什么配置。这一点在多人协作的团队里尤其重要设备配置出错能追溯到人否则出了问题连谁干的都查不出来。这篇文章写到这里telnet这个老伙计在Python自动化里的基本用法就梳理得差不多了。就我个人经验来说telnetlib这块内容虽然简单但特别适合建立“设备对话”的直觉。当你把登录、发命令、读回显这套动作理解透了后面再学netmiko、学RESTCONF、学netconf都会轻松很多。真到了大批量设备需要管理的时候你反而会感谢那个在模拟器里一遍一遍调试telnet脚本晚上。