本地轮转代理隧道脚本超时故障排查及定制需求求助
问题描述
我是编程新手,现在需要做一个可通过IP:Port访问的单代理,用于爬虫绕过Cloudflare防火墙,能直接在浏览器里配置。要求每次页面刷新时,把请求原封不动转发到从列表随机选的外部代理,列表里的代理不需要登录验证。
我擅长PHP但实现不了这个需求,作为纯Python新手,拼凑了下面的脚本:
import socket import socketserver import select import itertools # proxylist PROXY_LIST = ['xxx.xxx.xxx.220:3128', 'xxx.xxx.xxx.22:3128', 'xxx.xxx.xxx.105:3128'] proxy_pool = itertools.cycle(PROXY_LIST) class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass class HTTPRequestHandler(socketserver.BaseRequestHandler): def handle(self): data = self.request.recv(4096).strip() proxy = next(proxy_pool) hostname, port = proxy.split(':') remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM) remote.connect(("xxx.xxx.xxx.22", 3128)) # for testing a static proxy, not from above list remote.send(data) inputs = [self.request, remote] while True: readable, writable, exceptional = select.select(inputs, [], inputs, 60) if not readable and not writable and not exceptional: break for s in readable: if s is remote: out = self.request else: out = remote data = s.recv(4096) if data: out.send(data) for s in exceptional: inputs.remove(s) s.close() remote.close() self.request.close() if __name__ == "__main__": with ThreadingTCPServer(('localhost', 3128), HTTPRequestHandler) as server: server.serve_forever()
脚本启动无报错,能连接到本地代理(wget输出显示),但连接后没响应直到超时。已经在关闭防火墙的Windows和Debian系统测试过,外部代理本身是正常可用的。测试命令:
wget -v google.de -e use_proxy=yes -e http_proxy=127.0.0.1:3128
终止脚本时的报错:
Exception occurred during processing of request from ('127.0.0.1', 56714) Traceback (most recent call last): File "xxx\Python\Python311\Lib\socketserver.py", line 691, in process_request_thread self.finish_request(request, client_address) File "xxx\Python\Python311\Lib\socketserver.py", line 361, in finish_request self.RequestHandlerClass(request, client_address, self) File "xxx\Python\Python311\Lib\socketserver.py", line 755, in __init__ self.handle() File "xxx\Test\test.py", line 36, in handle data = s.recv(4096) ^^^^^^^^^^^^
需要排查问题,或者有没有现成的简单隧道代理脚本实现轮转转发,另外后续要加MySQL查询功能,随机从数据库取代理,脚本要可定制。
问题排查与解决方案
核心问题分析
- 请求格式被破坏:初始
data = self.request.recv(4096).strip()用了strip(),会删除HTTP请求头的换行、空格等关键格式符,导致外部代理无法识别合法请求,直接丢弃或不响应。 - 随机代理未生效:代码里
remote.connect(("xxx.xxx.xxx.22", 3128))是硬编码的固定代理,完全没用到随机选取的代理(即使是测试也不符合需求)。 - Socket转发逻辑缺陷:未处理
recv()返回空字节(表示对方连接关闭)的情况,导致循环一直阻塞直到超时;异常处理时直接修改inputs列表,会引发迭代错误。
修正后的代码
import socket import socketserver import select import random # 代理列表 PROXY_LIST = ['xxx.xxx.xxx.220:3128', 'xxx.xxx.xxx.22:3128', 'xxx.xxx.xxx.105:3128'] class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): # 允许地址重用,避免重启脚本时端口占用报错 allow_reuse_address = True class HTTPRequestHandler(socketserver.BaseRequestHandler): def handle(self): # 接收原始请求数据,禁止修改格式 data = self.request.recv(4096) if not data: self.request.close() return # 随机选择一个代理 proxy = random.choice(PROXY_LIST) hostname, port = proxy.split(':') port = int(port) remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: remote.connect((hostname, port)) # 用sendall确保所有数据发送完成 remote.sendall(data) inputs = [self.request, remote] while True: readable, _, exceptional = select.select(inputs, [], inputs, 60) if not readable and not exceptional: break # 迭代副本避免修改列表导致的错误 for s in readable.copy(): recv_data = s.recv(4096) if not recv_data: # 对方关闭连接,清理资源 inputs.remove(s) s.close() continue # 转发数据到对应socket if s is remote: self.request.sendall(recv_data) else: remote.sendall(recv_data) # 处理异常socket for s in exceptional.copy(): inputs.remove(s) s.close() except Exception as e: print(f"代理转发出错: {e}") finally: remote.close() self.request.close() if __name__ == "__main__": # 监听所有网卡,允许外部设备访问 with ThreadingTCPServer(('0.0.0.0', 3128), HTTPRequestHandler) as server: print(f"代理服务器启动,监听地址: 0.0.0.0:3128") server.serve_forever()
代码说明
- 随机代理选择:用
random.choice()每次随机选取代理,完全符合"每次刷新随机切换"的需求。 - 保留原始请求:去掉
strip(),确保HTTP请求格式完整,外部代理能正确解析。 - 完善Socket处理:
- 检查
recv()返回空的情况,及时关闭连接并退出循环。 - 迭代时使用列表副本,避免修改原列表引发的迭代错误。
- 用
sendall()替代send(),确保所有请求数据都发送完成。
- 检查
- 实用性优化:添加地址重用、监听所有网卡的配置,提升脚本稳定性和可用性。
后续扩展MySQL支持
要实现从MySQL随机获取代理,只需修改代理获取逻辑:
- 安装依赖:
pip install pymysql - 添加MySQL查询函数:
import pymysql def get_random_proxy_from_db(): db_config = { 'host': '你的数据库地址', 'user': '数据库用户名', 'password': '数据库密码', 'database': '数据库名' } conn = pymysql.connect(**db_config) try: with conn.cursor() as cursor: # 假设表名为proxies,is_active标记可用代理,ip_port存储代理地址 cursor.execute("SELECT ip_port FROM proxies WHERE is_active = 1 ORDER BY RAND() LIMIT 1") result = cursor.fetchone() if result: return result[0] else: # 数据库无可用代理时, fallback到本地列表 return random.choice(PROXY_LIST) finally: conn.close()
- 在
handle()函数中替换代理获取代码:
proxy = get_random_proxy_from_db()
现成替代方案
如果不想维护底层Socket代码,可以用更成熟的工具快速实现:
- mitmproxy:支持脚本扩展,可轻松实现请求转发和代理轮转,适配爬虫场景。
- aiohttp:异步HTTP框架,编写代理服务器更简洁,性能更高。
内容的提问来源于stack exchange,提问作者wwworm
相关产品推荐
相关产品推荐

