Python用requests并行发起站点GET请求校验3000用户名异常求解
问题原因排查
- 异常处理逻辑有致命漏洞:请求抛出异常后,
responses[username]被赋值为字符串'tgme_page_extra',后续直接调用.text属性会直接触发AttributeError,这是程序跑久后崩溃的核心原因之一 - 无请求超时配置:
requests.get默认无超时限制,部分请求会无限挂起,导致线程资源被占满,后续请求全部阻塞 - 线程模型不合理:3000个用户名对应创建3000个常驻线程,操作系统线程切换开销随运行时间上升会急剧增加,整体性能陡降
- 代理侧并发无控制:单个代理同时承载50个线程的请求,高频请求下极易被目标站点限流、拉黑,返回异常响应
- 连接无复用:每次请求都新建TCP连接,三次握手开销大,也更容易被站点的反爬策略识别
- 共享变量无锁:全局
responses字典被多线程同时读写,会出现竞态条件,导致存储的响应数据错乱
优化实现方案
采用ThreadPoolExecutor控制全局并发总量,每个代理绑定独立的requests.Session复用连接,限制单代理并发数,补全异常处理和超时配置,加锁保护共享变量,参考代码如下:
import requests import time import random import config from concurrent.futures import ThreadPoolExecutor from threading import Lock # 可根据实际情况调整的配置项 MAX_TOTAL_CONCURRENCY = 200 # 全局总并发数,匹配代理池总承载能力 MAX_PROXY_CONCURRENCY = 10 # 单代理最大并发数,避免单IP请求过频被封 REQUEST_TIMEOUT = 10 # 单请求最长等待时间,单位秒 # 全局共享变量加锁避免竞态 responses = {} res_lock = Lock() # 每个代理绑定独立Session复用TCP连接,降低握手开销 proxy_sessions = {} for proxy in config.PROXY_LIST: session = requests.Session() session.headers.update(config.HEADERS) session.proxies = {"http": proxy, "https": proxy} # 可根据代理实际格式调整 proxy_sessions[proxy] = session # 用户名按代理分组,每组大小不超过单代理并发上限 username_groups = [] temp_group = [] for idx, username in enumerate(config.USERNAME_LIST): assigned_proxy = config.PROXY_LIST[idx % len(config.PROXY_LIST)] temp_group.append((username, assigned_proxy)) if len(temp_group) == MAX_PROXY_CONCURRENCY: username_groups.append(temp_group) temp_group = [] if temp_group: username_groups.append(temp_group) def check_username(username, proxy): session = proxy_sessions[proxy] current_text = None try: resp = session.get(config.URL + username, timeout=REQUEST_TIMEOUT) resp.raise_for_status() # 主动抛出4xx、5xx类响应异常 current_text = resp.text except Exception as e: print(f"用户名{username}校验失败: {str(e)}") time.sleep(3) return # 写入全局变量前加锁 with res_lock: responses[username] = current_text if "tgme_page_extra" not in current_text: # 用户名不可认领的对应业务逻辑 pass else: # 其他业务逻辑 pass def run_single_check_cycle(): with ThreadPoolExecutor(max_workers=MAX_TOTAL_CONCURRENCY) as executor: all_tasks = [] for group in username_groups: for username, proxy in group: all_tasks.append(executor.submit(check_username, username, proxy)) # 等待整轮所有校验任务完成 for task in all_tasks: task.result() if __name__ == "__main__": while True: run_single_check_cycle() # 整轮校验完成后统一休眠,控制整体请求频率 time.sleep(random.choice(config.CHECK_INTERVAL))
额外优化建议
- 可以根据代理返回的状态码(比如429、403)动态调整对应代理的并发数和冷却时间,遇到限流时自动降低该代理的请求频率
- 如果后续校验量级继续提升,可以换用异步HTTP库
aiohttp,并发性能会比线程池方案高30%以上 - 可以增加代理可用性检测逻辑,遇到持续返回异常的代理自动从可用池中剔除,避免影响校验结果
内容的提问来源于stack exchange,提问作者Николай Добрыднев
相关产品推荐
相关产品推荐

