#!/usr/bin/env python3 """ Webshare SOCKS代理获取脚本 使用简单的下载接口直接获取代理列表 """ import os import requests import yaml from typing import Optional, List, Dict def load_api_key() -> Optional[str]: """ 从.env文件或环境变量中加载API密钥 Returns: API密钥或None """ # 首先尝试从.env文件读取 env_file = ".env" if os.path.exists(env_file): try: with open(env_file, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue if '=' in line: # 处理 TOKEN=xxx 格式 key, value = line.split('=', 1) if key.strip() == 'TOKEN': return value.strip() else: # 如果第一行不包含=,则认为是直接的token return line except Exception as e: print(f"读取.env文件时出错: {e}") # 然后尝试从环境变量读取 api_key = os.getenv('TOKEN') or os.getenv('WEBSHARE_API_KEY') if api_key: return api_key.strip() return None def get_download_token(api_key: str) -> Optional[str]: """ 获取下载token Args: api_key: API密钥 Returns: 下载token或None """ url = "https://proxy.webshare.io/api/v2/download_token/proxy_list/" headers = { "Authorization": f"Token {api_key}", "Content-Type": "application/json" } try: print("🔑 正在获取下载token...") response = requests.post(url, headers=headers) if response.status_code == 200: data = response.json() download_token = data.get("key") # API返回的字段是'key'而不是'download_token' if download_token: print("✅ 成功获取下载token") return download_token else: print("❌ 响应中未找到下载token") print(f"🔍 可用的键: {list(data.keys())}") return None else: print(f"❌ 获取下载token失败 {response.status_code}: {response.text}") return None except requests.exceptions.RequestException as e: print(f"获取下载token时出错: {e}") return None def download_proxies(download_token: str, country: str = "-", auth_method: str = "username", endpoint_mode: str = "direct", location: str = ""): """ 使用下载token获取代理列表 Args: download_token: 下载token (不是API key) country: 国家代码 (- 表示任意国家) auth_method: 认证方式 (username 或 ipauth) endpoint_mode: 端点模式 (direct 或 backbone) location: 位置过滤 (可选) Returns: 代理列表文本 """ # 构建下载URL - 正确的格式 if location: # URL编码位置参数 location_encoded = requests.utils.quote(location) url = f"https://proxy.webshare.io/api/v2/proxy/list/download/{download_token}/{country}/any/{auth_method}/{endpoint_mode}/{location_encoded}/" else: # 没有位置过滤时,不包含最后的路径参数 url = f"https://proxy.webshare.io/api/v2/proxy/list/download/{download_token}/{country}/any/{auth_method}/{endpoint_mode}/" try: print(f"📡 正在从下载接口获取代理: {url}") response = requests.get(url) # 检查响应状态 if response.status_code == 404: print("❌ 404错误 - 可能是URL格式不正确或token无效") return None elif response.status_code == 401: print("❌ 401错误 - 下载token无效") return None elif response.status_code != 200: print(f"❌ HTTP错误 {response.status_code}: {response.text}") return None # 检查是否返回了错误信息 if "invalid" in response.text.lower() or "error" in response.text.lower(): print(f"❌ API返回错误: {response.text}") return None return response.text except requests.exceptions.RequestException as e: print(f"下载代理列表时出错: {e}") return None def format_proxy_list(proxy_text: str, output_format: str = "socks5") -> list: """ 格式化代理列表 Args: proxy_text: 原始代理文本 output_format: 输出格式 (socks5, http, raw) Returns: 格式化的代理列表 """ if not proxy_text: return [] lines = proxy_text.strip().split('\n') formatted_proxies = [] for line in lines: line = line.strip() if not line: continue # 假设格式为: ip:port:username:password parts = line.split(':') if len(parts) >= 4: ip, port, username, password = parts[0], parts[1], parts[2], parts[3] if output_format == "socks5": formatted_proxy = f"socks5://{username}:{password}@{ip}:{port}" elif output_format == "http": formatted_proxy = f"http://{username}:{password}@{ip}:{port}" else: # raw formatted_proxy = line formatted_proxies.append(formatted_proxy) elif len(parts) >= 2: # 无认证格式: ip:port ip, port = parts[0], parts[1] if output_format == "socks5": formatted_proxy = f"socks5://{ip}:{port}" elif output_format == "http": formatted_proxy = f"http://{ip}:{port}" else: # raw formatted_proxy = line formatted_proxies.append(formatted_proxy) return formatted_proxies def save_proxies_to_file(proxies: list, filename: str = "socks_proxies.txt"): """ 保存代理列表到文件 Args: proxies: 代理列表 filename: 文件名 """ try: with open(filename, 'w', encoding='utf-8') as f: for proxy in proxies: f.write(f"{proxy}\n") print(f"✅ 代理列表已保存到 {filename}") except Exception as e: print(f"❌ 保存文件时出错: {e}") def parse_proxies(proxy_text: str) -> list: """ 解析代理文本,提取代理信息 Args: proxy_text: 代理文本 Returns: 代理信息列表 """ proxies = [] lines = proxy_text.strip().split('\n') for line in lines: line = line.strip() if not line: continue # 解析格式: host:port:username:password parts = line.split(':') if len(parts) >= 4: proxy = { 'host': parts[0], 'port': parts[1], 'username': parts[2], 'password': ':'.join(parts[3:]) # 密码可能包含冒号 } proxies.append(proxy) return proxies def save_proxies(proxies: list, filename: str, format_type: str = "raw"): """ 保存代理列表到文件 Args: proxies: 代理列表 filename: 文件名 format_type: 格式类型 ("raw", "http", "socks5") """ with open(filename, 'w', encoding='utf-8') as f: for proxy in proxies: if format_type == "raw": # 原始格式: host:port:username:password line = f"{proxy['host']}:{proxy['port']}:{proxy['username']}:{proxy['password']}" elif format_type == "http": # HTTP格式: http://username:password@host:port line = f"http://{proxy['username']}:{proxy['password']}@{proxy['host']}:{proxy['port']}" elif format_type == "socks5": # SOCKS5格式: socks5://username:password@host:port line = f"socks5://{proxy['username']}:{proxy['password']}@{proxy['host']}:{proxy['port']}" else: line = f"{proxy['host']}:{proxy['port']}:{proxy['username']}:{proxy['password']}" f.write(line + '\n') print(f"📄 已保存 {len(proxies)} 个代理到 {filename} ({format_type}格式)") def generate_clash_config(proxies: List[Dict[str, str]]) -> Dict: """ 生成Clash配置文件 Args: proxies: 代理列表 Returns: Clash配置字典 """ clash_proxies = [] proxy_names = [] for i, proxy in enumerate(proxies): proxy_name = f"proxy-{i+1}" proxy_names.append(proxy_name) # 创建HTTP代理配置 clash_proxy = { "name": proxy_name, "type": "http", "server": proxy['host'], "port": int(proxy['port']), "username": proxy['username'], "password": proxy['password'] } clash_proxies.append(clash_proxy) # 创建完整的Clash配置 clash_config = { "port": 7890, "socks-port": 7891, "allow-lan": False, "mode": "rule", "log-level": "info", "external-controller": "127.0.0.1:9090", "proxies": clash_proxies, "proxy-groups": [ { "name": "🚀 节点选择", "type": "select", "proxies": ["♻️ 自动选择", "🎯 故障转移"] + proxy_names }, { "name": "♻️ 自动选择", "type": "url-test", "proxies": proxy_names, "url": "http://www.gstatic.com/generate_204", "interval": 300 }, { "name": "🎯 故障转移", "type": "fallback", "proxies": proxy_names, "url": "http://www.gstatic.com/generate_204", "interval": 300 }, { "name": "🌍 国外媒体", "type": "select", "proxies": ["🚀 节点选择", "♻️ 自动选择", "🎯 故障转移"] + proxy_names }, { "name": "🍃 应用净化", "type": "select", "proxies": ["REJECT", "DIRECT"] } ], "rules": [ "DOMAIN-SUFFIX,google.com,🚀 节点选择", "DOMAIN-SUFFIX,youtube.com,🌍 国外媒体", "DOMAIN-SUFFIX,facebook.com,🌍 国外媒体", "DOMAIN-SUFFIX,twitter.com,🌍 国外媒体", "DOMAIN-SUFFIX,instagram.com,🌍 国外媒体", "DOMAIN-SUFFIX,netflix.com,🌍 国外媒体", "DOMAIN-KEYWORD,google,🚀 节点选择", "DOMAIN-KEYWORD,youtube,🌍 国外媒体", "DOMAIN-KEYWORD,facebook,🌍 国外媒体", "DOMAIN-KEYWORD,github,🚀 节点选择", "GEOIP,CN,DIRECT", "MATCH,🚀 节点选择" ] } return clash_config def save_clash_config(proxies: List[Dict[str, str]], filename: str = "clash_config.yaml"): """ 保存Clash配置文件 Args: proxies: 代理列表 filename: 文件名 """ clash_config = generate_clash_config(proxies) try: with open(filename, 'w', encoding='utf-8') as f: yaml.dump(clash_config, f, default_flow_style=False, allow_unicode=True, indent=2) print(f"📄 已保存Clash配置到 {filename}") except Exception as e: print(f"❌ 保存Clash配置时出错: {e}") def main(): """主函数""" print("🚀 开始获取Webshare代理列表...") # 加载API密钥 api_key = load_api_key() if not api_key: print("❌ 无法加载API密钥") return # 获取下载token download_token = get_download_token(api_key) if not download_token: print("❌ 获取下载token失败") return # 下载代理列表 proxy_text = download_proxies(download_token) if not proxy_text: print("❌ 获取代理列表失败") return # 解析代理 proxies = parse_proxies(proxy_text) if not proxies: print("❌ 未找到有效的代理") return print(f"✅ 成功获取 {len(proxies)} 个代理") # 保存原始代理列表 save_proxies(proxies, "proxies_raw.txt", format_type="raw") # 保存HTTP格式代理列表 save_proxies(proxies, "proxies_http.txt", format_type="http") # 保存SOCKS5格式代理列表 save_proxies(proxies, "proxies_socks5.txt", format_type="socks5") # 生成Clash配置文件 save_clash_config(proxies, "clash_config.yaml") print("🎉 代理列表已保存完成!") print("📁 文件说明:") print(" - proxies_raw.txt: 原始格式 (host:port:username:password)") print(" - proxies_http.txt: HTTP格式 (http://username:password@host:port)") print(" - proxies_socks5.txt: SOCKS5格式 (socks5://username:password@host:port)") print(" - clash_config.yaml: Clash配置文件") if __name__ == "__main__": main()