Python 通过 Shadowsocks SOCKS5 代理发送请求

核心原理

Python 不能直接解析 Shadowsocks 协议。标准做法是:

Python
   ↓ SOCKS5
sslocal (本地 1080 端口)
   ↓ Shadowsocks 协议
远端 SS 服务器
   ↓ 明文
目标网站

先用 Shadowsocks 客户端在本地起一个 SOCKS5 代理,Python 通过这个代理访问外网。

方法一:requests[socks](推荐)

安装:

pip install requests[socks]

使用:

import requests

proxies = {
    "http":  "socks5://127.0.0.1:1080",
    "https": "socks5://127.0.0.1:1080"
}

r = requests.get(
    "https://httpbin.org/ip",
    proxies=proxies,
    timeout=10
)

print(r.json())

socks5:// 使用远端 DNS 解析;如果想让本地 DNS 解析,换成 socks5h://

方法二:PySocks 全局劫持

安装:

pip install pysocks

设置全局默认代理,之后所有 socket 连接都走 SOCKS5:

import socket
import socks

socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 1080)
socket.socket = socks.socksocket

import requests

r = requests.get("https://httpbin.org/ip")
print(r.json())

适合需要代理所有网络调用(requests、httpx、urllib 等)的场景,但会影响进程内所有 socket,慎用。

方法三:环境变量

export ALL_PROXY=socks5://127.0.0.1:1080
export HTTPS_PROXY=socks5://127.0.0.1:1080
export HTTP_PROXY=socks5://127.0.0.1:1080

或者在 Python 代码里设置:

import os

os.environ["ALL_PROXY"] = "socks5://127.0.0.1:1080"

import requests

r = requests.get("https://httpbin.org/ip")
print(r.json())

requests 会自动读取 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY 环境变量。

在代码里启动 sslocal

如果想让 Python 程序自己管理 Shadowsocks 子进程:

import subprocess
import time
import requests

proc = subprocess.Popen([
    "sslocal",
    "-s", "服务器IP",
    "-p", "8388",
    "-k", "密码",
    "-m", "aes-256-gcm",
    "-l", "1080"
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

time.sleep(1)   # 等待 sslocal 就绪

proxies = {
    "http":  "socks5://127.0.0.1:1080",
    "https": "socks5://127.0.0.1:1080"
}

try:
    r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
    print(r.json())
finally:
    proc.terminate()

sslocal 来自 shadowsocks-libevshadowsocks-rust 包,需要提前安装。

验证代理是否生效

import requests

proxies = {
    "http":  "socks5://127.0.0.1:1080",
    "https": "socks5://127.0.0.1:1080"
}

r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(r.json())   # 返回的 origin IP 应为代理服务器 IP,不是本机 IP