Python ThreadPoolExecutor多参数传递:文本URL列表传参取值异常
问题原因
ThreadPoolExecutor.map()默认将传入可迭代对象的单个元素作为位置参数传给目标函数,不会自动对嵌套结构做拆包,直接传嵌套列表时,函数收到的就是整个子列表对象。- 你之前测试嵌套列表传参时出现索引取值异常,核心原因是读取的URL文件存在空行:空行经
strip()处理后为空字符串,未过滤空行时会生成结构不符合预期的列表元素,加上未同步修改函数的参数接收、拆包逻辑,就会出现索引打印结果异常的问题。
正确多参数传递方案
方案1:多可迭代对象传参(最适配你的场景)
pool.map支持同时传入多个可迭代对象,会自动将同位置的元素拆包为多个位置参数传给目标函数。对于iterations_file_number这类同一批任务共用的固定值,搭配itertools.repeat即可实现批量传参,不需要修改URL的存储结构。
修改步骤:
- 导入依赖模块
import itertools
- 修改
get_url函数,增加参数接收,同时增加空URL过滤逻辑
def get_url(url, file_number): global queue global count_requests global host_error # 过滤空值避免无效请求 if not url: return try: result_request = requests.get(url, verify=False, timeout=10) soup = BeautifulSoup(result_request.text, 'html.parser') title = str(soup.title.get_text().splitlines(False)) count_requests = count_requests + 1 queue.put(f'{url} - {title} \n') except: queue.put(f'FAILED : {url} \n') host_error = host_error + 1
- 修改URL读取逻辑,过滤空行
def open_url_files(): global urls global total_number_of_lines global iterations_file_number iterations_file_number = iterations_file_number + 1 with open(str(iterations_file_number) + ".txt") as stream: # 过滤strip后为空的空行 urls = [line.strip() for line in stream if line.strip()] total_number_of_lines = len(urls) threadpool()
- 修改线程池调用逻辑,传入固定参数,建议调低并发数避免端口耗尽、目标站限流
def threadpool(): global urls global iterations_file_number with ThreadPoolExecutor(max_workers=50) as pool: # 固定参数用itertools.repeat包裹,自动给每个任务传相同值 pool.map(get_url, urls, itertools.repeat(iterations_file_number)) open_url_files()
方案2:偏函数绑定固定参数
如果不想修改map的传参结构,可以用functools.partial提前把固定参数绑定到目标函数上:
from functools import partial def threadpool(): global urls global iterations_file_number with ThreadPoolExecutor(max_workers=50) as pool: # 提前绑定file_number参数 task = partial(get_url, file_number=iterations_file_number) pool.map(task, urls) open_url_files()
注意这种写法需要把get_url的file_number定义为关键字参数,或者按位置绑定。
方案3:嵌套列表手动拆包(修正你之前的写法)
如果要保留嵌套列表的传参结构,只需要在函数内手动拆包即可,同时必须过滤空行:
- 生成嵌套URL列表时过滤空行
urls = [[line.strip(), iterations_file_number] for line in stream if line.strip()]
- 修改
get_url的参数接收逻辑,手动拆包
def get_url(task_args): url, file_number = task_args # 手动将传入的列表拆为两个变量 # 其余业务逻辑不变
现有代码其他问题修复
- 你当前将
queue.join()放在了writer线程启动后、open_url_files()调用前,此时队列中还没有写入任何任务,join会直接返回,无法实现等待所有结果写入文件的效果。需要将queue.join()移动到所有线程池任务执行完成之后,并且在任务全部结束后向队列传入None作为终止信号,通知writer线程正常退出。 - 500的并发数设置过高,很容易触发本地TCP端口耗尽、目标站点限流封禁问题,普通爬虫场景设置20-100的并发数即可。
- 多线程下直接修改全局计数变量存在线程安全问题,高并发场景下计数会不准,如果需要精确统计建议搭配
threading.Lock加锁,或者用as_completed拿到任务返回值后再统计。
内容的提问来源于stack exchange,提问作者jdm0
相关产品推荐
相关产品推荐

