社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

paramiko,一个超酷的 Python 库

程序员老朱 • 1 月前 • 214 次点击  

 

做运维或后端开发,迟早会碰到这类需求:批量登录几十台服务器执行命令、上传下发文件、自动跑部署脚本。用 ssh 命令当然可以,但要在程序里精细控制、循环执行、统一收集结果时就很难受——要么靠 subprocess 拼 shell 字符串,要么依赖系统装了 sshpass,既不优雅也不跨平台。

paramiko 就是为此而生的:它用纯 Python 实现了 SSH2 协议,让你无需依赖系统的 ssh 客户端,就能在代码里完成远程连接、命令执行、文件传输(SFTP)等操作。它是很多运维自动化工具(如早期的 Ansible、Fabric)的底层依赖,是 Python 世界里最主流的 SSH 库。

它的核心能力:

  • • SSH 客户端:连接远程主机,执行命令、拿到标准输出/错误与退出码。
  • • SFTP 客户端:上传、下载、遍历、重命名、删除远程文件。
  • • 密钥认证:支持 RSA / ECDSA / Ed25519 私钥,比密码更安全。
  • • 底层可控:Transport 与 Channel 让你能复用连接、开交互式会话。

一、安装

pip install paramiko

paramiko 的加密运算依赖 cryptography,pip 会自动一并装上。

最小示例

安装好,我们创建一个 main.py,用密码连上一台远程主机并执行命令:

import paramiko

client = paramiko.SSHClient()
# 自动接受未知主机密钥(仅演示用,生产环境见「注意事项」)

client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 换成你自己的 IP、用户名、密码

client.connect("192.168.1.100" , username="root", password="你的密码")

stdin, stdout, stderr = client.exec_command("whoami")
print
("远程返回:", stdout.read().decode().strip())   # 远程返回: root

client.close()

运行 python main.py,终端会打印出远程主机上 whoami 的结果(如 root)。

二、具体执行

本节把 paramiko 的常用能力拆开讲:建立连接与主机密钥策略、执行命令、密钥认证、SFTP 文件传输。

2.1 建立连接:SSHClient.connect()

SSHClient 是最高层的封装,代表一条 SSH 连接。常用参数:

client.connect(
    hostname="192.168.1.100",   # 主机名或 IP
    port=22,                    # 端口,默认 22
    username="root",            # 用户名
    password="你的密码",         # 密码认证(与 pkey 二选一)
    pkey=None,                  # 私钥对象(见 2.3)
    key_filename=None,          # 或直接给私钥文件路径
    timeout=10,                 # 连接超时(秒)
    allow_agent=True,           # 允许使用 ssh-agent
    look_for_keys=True,         # 自动查找 ~/.ssh 下的密钥
)

只填 hostname 和 username 时,paramiko 会自动尝试 ssh-agent 和 ~/.ssh/ 里的密钥(look_for_keys 会依次找 ~/.ssh/id_rsa、id_ecdsa、id_ed25519,Windows 上还会多找一条 ~/ssh/)。想强制只用密码,就关掉这两个开关:




    
client.connect("192.168.1.100", username="root", password="你的密码",
               allow_agent=False, look_for_keys=False)

2.2 主机密钥策略:防中间人攻击

SSH 靠"主机密钥"确认对面是不是真的那台机器。paramiko 有三种策略:

# 1. 最安全:只信任 known_hosts 里已有的主机,不认识就拒绝
client.load_system_host_keys()               # 载入 ~/.ssh/known_hosts
client.set_missing_host_key_policy(paramiko.RejectPolicy())

# 2. 自动添加未知主机(方便,但有中间人风险,仅适合测试)

client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 3. 遇到未知主机只发警告,仍继续连接

client.set_missing_host_key_policy(paramiko.WarningPolicy())

生产环境务必用 RejectPolicy + load_system_host_keys(),或手动校验指纹,不要图省事一律 AutoAddPolicy。

2.3 密钥认证

比密码更安全、也更适合自动化的方式是用私钥,加载后通过 pkey 传入:

import paramiko

# RSA 私钥

key = paramiko.RSAKey.from_private_key_file("/home/user/.ssh/id_rsa")

# Ed25519 私钥(更现代、更推荐)

key = paramiko.Ed25519Key.from_private_key_file("/home/user/.ssh/id_ed25519")

# 带口令(passphrase)的加密私钥

key = paramiko.Ed25519Key.from_private_key_file(
    "/home/user/.ssh/id_ed25519"
, password="私钥口令"
)

client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect("192.168.1.100", username="root", pkey=key)

也可以直接把路径交给 connect(),省去手动加载:

client.connect("192.168.1.100", username="root",
               key_filename="/home/user/.ssh/id_ed25519")

私钥在哪个目录? Linux 是 /home//.ssh/,Windows 是 C:\Users\\.ssh\(即 %USERPROFILE%\.ssh\),文件名通常是 id_rsa 或 id_ed25519。别把用户名写死,用 Path.home() 自动定位:

from pathlib import Path

import
 paramiko

key = paramiko.RSAKey.from_private_key_file(
    str
(Path.home() / ".ssh" / "id_rsa")       # Windows 上即 C:\Users\\.ssh\id_rsa
)

⚠️ Windows 路径里的反斜杠是坑:"C:\Users\\.ssh\id_rsa" 里的 \U 会被 Python 当成 Unicode 转义而报 SyntaxError。请用原始字符串 r"C:\Users\...",或统一写正斜杠 "C:/Users/..."。

不想自己判断密钥类型,可以用 PKey.from_path() 让它自动识别:

key = paramiko.PKey.from_path(str(Path.home() / ".ssh" / "id_ed25519"))

密钥对象的常用属性与方法:

print(key.get_name())        # 'ssh-ed25519'(协议名)
print
(key.algorithm_name)    # 'ED25519'(纯算法名,大写)
print
(key.get_bits())        # 密钥长度(位)
print
(key.fingerprint)       # 指纹
print
(key.can_sign())        # 是否含私钥部分(能否用于认证)

PKey没有.name 属性,要取名字请用 get_name() 或 algorithm_name。

2.4 执行命令:exec_command()

exec_command() 会新开一个通道执行命令,返回 (stdin, stdout, stderr) 三个类文件对象:

stdin, stdout, stderr = client.exec_command("ls -l /tmp")

out = stdout.read().decode()               # 标准输出(bytes → str)
err = stderr.read().decode()               # 标准错误
code = stdout.channel.recv_exit_status()   # 退出码,0 表示成功

print
("输出:\n", out)
if
 err:
    print
("错误:\n", err)
print
("退出码:", code)

签名如下:

exec_command(command, bufsize=-1, timeout=None, get_pty=False, environment=None)
  • • timeout:命令执行的超时时间(秒)。
  • • get_pty=True:分配伪终端,适合那些「检测到非交互环境就改变行为」的命令。
  • • environment:传给远程进程的环境变量字典。

如果命令需要交互(比如 sudo 要输密码),往返回的 stdin 里写即可:

stdin, stdout, stderr = client.exec_command("sudo -S whoami")
stdin.write("你的密码\n")   # -S 让 sudo 从 stdin 读密码
stdin.flush()

print
(stdout.read().decode())

记住一个顺序:先读完 stdout/stderr,再取退出码。输出很大又不及时读取,缓冲区写满会导致命令卡死。

2.5 SFTP 文件传输

用 open_sftp() 打开一条 SFTP 会话,之后就能像操作本地文件一样操作远程:




    
sftp = client.open_sftp()

# 上传 / 下载

sftp.put("local.txt", "/remote/path/local.txt")     # 本地 → 远程
sftp.get("/remote/file.txt", "local_copy.txt")      # 远程 → 本地

# 列出目录

print
(sftp.listdir("/remote/dir"))                  # 只返回文件名列表
for
 attr in sftp.listdir_attr("/remote/dir"):       # 返回带属性的对象
    print
(f"{attr.filename:30s} {attr.st_size:>10d} 字节")

# 文件信息 / 重命名 / 删除 / 建目录

stat = sftp.stat("/remote/file.txt")
sftp.rename("/remote/a.txt", "/remote/b.txt")
sftp.remove("/remote/tmp.txt")
sftp.mkdir("/remote/newdir")

sftp.close()
client.close()

三、优化处理

基础用法掌握后,来看几个真实场景里的关键点。

3.1 用上下文管理器 + 异常处理

SSHClient 支持 with,退出时自动关闭,配合异常处理更稳健:

import paramiko

try
:
    with
 paramiko.SSHClient() as client:
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect("192.168.1.100", username="root",
                       password="你的密码", timeout=10 )
        _, stdout, _ = client.exec_command("uptime")
        print
(stdout.read().decode())
except
 paramiko.AuthenticationException:
    print
("认证失败:用户名/密码/密钥不正确")
except
 paramiko.SSHException as e:
    print
(f"SSH 协议错误: {e}")

3.2 递归上传整个目录

put / get 只传单个文件,递归目录需要自己遍历:

import os

def
 upload_dir(sftp, local_dir, remote_dir):
    try
:
        sftp.mkdir(remote_dir)
    except
 IOError:
        pass
  # 目录已存在,忽略

    for
 name in os.listdir(local_dir):
        local_path = os.path.join(local_dir, name)
        remote_path = f"{remote_dir}/{name}"
        if
 os.path.isdir(local_path):
            upload_dir(sftp, local_path, remote_path)   # 递归子目录
        else
:
            sftp.put(local_path, remote_path)

upload_dir(client.open_sftp(), "./build", "/var/www/html")

3.3 交互式会话:invoke_shell()

有些设备或工具必须要有真正的终端才能工作(网络设备 CLI、需要 TTY 的交互程序)。这时用 invoke_shell():




    
import time

channel = client.invoke_shell()
channel.send("cd /tmp\n")
time.sleep(0.5)
channel.send("ls -l\n")
time.sleep(1)
print
(channel.recv(65535).decode())

3.4 实战:并发在多台主机上执行命令

paramiko 的 SSHClient不是线程安全的,所以每台主机各建一条独立连接,用线程池并发:

import paramiko
from
 concurrent.futures import ThreadPoolExecutor

HOSTS = ["192.168.1.101", "192.168.1.102", "192.168.1.103"]

def
 run_on_host(host):
    try
:
        with
 paramiko.SSHClient() as client:
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            client.connect(host, username="root", password="你的密码",timeout=10)
            _, stdout, _ = client.exec_command("hostname && uptime")
            return
 host, stdout.read().decode().strip()
    except
 Exception as e:
        return
 host, f"失败: {e}"

with
 ThreadPoolExecutor(max_workers=10) as pool:
    for
 host, result in pool.map(run_on_host, HOSTS):
        print
(f"=====  {host} =====")
        print
(result)

3.5 底层玩法:复用 Transport

要在同一台主机上开很多通道(既跑命令又传文件),用 Transport 复用一条底层连接更高效:

import paramiko

transport = paramiko.Transport(("192.168.1.100", 22))
transport.connect(username="root", password="你的密码")

sftp = paramiko.SFTPClient.from_transport(transport)   # 从同一个 transport 开 SFTP
print
(sftp.listdir("/tmp"))

channel = transport.open_session()                     # 也可以直接开通道跑命令
channel.exec_command("df -h")
print
(channel.recv(65535).decode())

sftp.close()
transport.close()

四、「常见问题」

1、报 AuthenticationException 认证失败:检查用户名、密码或私钥是否正确;若主机只允许密钥登录,密码认证也会失败。

2、报 PasswordRequiredException:私钥是加密的,加载时没提供口令。给 from_private_key_file(path, password="口令") 传入 passphrase。

3、报 Server 'x.x.x.x' not found in known_hosts:用了 RejectPolicy 但主机不在 known_hosts 里。先 ssh user@host 手动记录一次,或执行 ssh-keyscan host >> ~/.ssh/known_hosts。

4、报 Private key file is accessible to others:私钥权限太开放,执行 chmod 600 私钥文件(Windows 上则是检查文件 ACL)。

5、exec_command 很慢或卡住:多为输出量大未及时读取,或命令需要 TTY。可改用循环读取、设置 timeout,或加 get_pty=True。

6、连接超时 / timed out:目标端口不通、防火墙拦截或 IP 有误。先用 ssh 命令确认能连通,再排查 timeout 设置。

7、SFTP 上传报 Permission denied:远程目标目录当前用户不可写。换有写权限的目录,或先调整权限。

8、怎么做到免密登录?客户端生成密钥对( ssh-keygen -t ed25519),把公钥追加到远程 ~/.ssh/authorized_keys,脚本里用 pkey= 或 key_filename= 指定私钥即可。

9、pysftp 还要用吗?它早已停止维护,直接用 paramiko 自身的 open_sftp() 即可。


从连接一台主机、执行一条命令,到密钥认证、SFTP 传文件,再到并发批量运维与 Transport 复用——paramiko 用纯 Python 把 SSH 完整搬进了代码,不依赖系统 ssh 客户端,因此在任何 Python 环境里都能跑。收紧主机密钥策略、别把密码写进代码,剩下的,就是把你手上那些重复的运维动作一段段换成脚本。

 


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/201727