如何用Python Requests加速循环并避免线程执行时重复输出?
问题根源
你遇到的重复输出(或输出混乱),主要是两个原因:
- 多线程同时调用
print时,标准输出流的操作不是线程安全的,多个线程的输出会互相干扰,看起来像重复; - 如果每个线程都完整遍历整个URL列表,会导致同一URL被多个线程重复处理,直接产生重复输出。
解决方案
1. 给输出加锁,避免线程抢占输出流
用threading.Lock()确保同一时间只有一个线程能执行print操作,解决输出混乱问题。
2. 拆分任务,每个线程处理独立的URL片段
把URL列表分成多份,每个线程只处理自己的那一部分,从根源避免重复处理同一个URL。
完整改进代码
import requests import json import threading # 加载URL数据,用上下文管理器自动关文件 with open("urls.json") as f: data = json.load(f) urls_list = data['urls'] # 创建线程锁,控制输出的线程安全 print_lock = threading.Lock() def process_url_chunk(url_chunk): for url in url_chunk: try: r = requests.get(f"https://{url}") # 加锁后再输出,保证同一时间只有一个线程在打印 with print_lock: print(f"=== 处理URL: {url} ===") print(r.headers) print("\n") except Exception as e: with print_lock: print(f"URL {url} 请求失败: {str(e)}") def main(): # 设置线程数,根据你的需求调整,别开太多,不然请求会被目标网站限制 thread_num = 4 # 拆分URL列表 chunk_size = len(urls_list) // thread_num threads = [] for i in range(thread_num): start = i * chunk_size # 最后一个线程处理剩下的所有URL,避免遗漏 end = start + chunk_size if i != thread_num -1 else len(urls_list) current_chunk = urls_list[start:end] # 创建线程并启动 t = threading.Thread(target=process_url_chunk, args=(current_chunk,)) threads.append(t) t.start() # 等待所有线程跑完 for t in threads: t.join() if __name__ == "__main__": main()
关键改进点
- 用
with open()替代直接open(),自动关闭文件,避免资源浪费; - 加了
print_lock,所有输出操作都在锁的保护下执行,彻底解决输出混乱/重复的问题; - 拆分URL列表,每个线程只处理自己的片段,不会重复处理同一URL;
- 加了异常捕获,单个URL请求失败不会影响整个线程,错误信息也能安全输出。
更简洁的线程池写法
如果不想手动管理线程,可以用concurrent.futures.ThreadPoolExecutor,它会自动帮你拆分任务、管理线程:
import requests import json import threading from concurrent.futures import ThreadPoolExecutor with open("urls.json") as f: data = json.load(f) urls_list = data['urls'] print_lock = threading.Lock() def process_single_url(url): try: r = requests.get(f"https://{url}") with print_lock: print(f"=== 处理URL: {url} ===") print(r.headers) print("\n") except Exception as e: with print_lock: print(f"URL {url} 请求失败: {str(e)}") def main(): # 线程池最大线程数,按需调整 with ThreadPoolExecutor(max_workers=4) as executor: executor.map(process_single_url, urls_list) if __name__ == "__main__": main()
这个版本代码更简洁,不用手动拆分列表,ThreadPoolExecutor会自动把任务分配给各个线程,效率一样高。
内容的提问来源于stack exchange,提问作者user19966157
相关产品推荐
相关产品推荐

