多线程批量请求URL提速爬虫:代码实现问题求助
修复你的ThreadPoolExecutor多线程爬虫代码
嘿,我一眼就看到你的多线程实现里有两个关键问题,这也是很多刚接触线程池的开发者容易踩的坑:
问题1:参数传递错误
你在调用pool.submit()时,把URL用括号包起来了:
future = pool.submit(requests.get, (baseurl + i))
这里的(baseurl + i)会被Python解析成一个单元素元组,而requests.get()的第一个参数需要是字符串类型的URL。这会导致请求失败,因为requests.get会把这个元组当成无效URL处理。
正确的写法应该去掉括号,直接传递字符串:
future = pool.submit(requests.get, baseurl + i)
问题2:阻塞式调用result()导致串行执行
你在循环里直接调用future.result():
for i in PostLink: future = pool.submit(requests.get, (baseurl + i)) res.append(future.result())
future.result()是阻塞方法——调用它时主线程会暂停,直到这个任务执行完成才会继续下一次循环。这相当于把多线程又变回了单线程,完全发挥不了并行爬取的优势,耗时和原单线程代码几乎没区别。
修正后的多线程代码
这里提供两种更合理的实现方式,都能真正实现并行爬取:
方式1:使用pool.map()(更简洁)
map()方法会自动把迭代器中的每个元素作为参数传给目标函数,并行执行后返回所有结果的列表:
from concurrent.futures import ThreadPoolExecutor import requests from bs4 import BeautifulSoup # 先获取所有帖子链接 bowl = requests.get('http://www.aaronsw.com/weblog/fullarchive') soup = BeautifulSoup(bowl.text, 'html.parser') PostLink = soup.body.find_all('a', href=True) PostLink = [i['href'] for i in PostLink][2:] baseurl = 'http://www.aaronsw.com/weblog/' # 生成完整URL列表 full_urls = [baseurl + link for link in PostLink] # 线程池并行爬取(IO密集型任务可设更大线程数) with ThreadPoolExecutor(max_workers=10) as pool: bowls = list(pool.map(requests.get, full_urls)) print(f"成功爬取{len(bowls)}个页面")
方式2:使用as_completed()(更灵活,支持实时处理结果)
如果需要在任务完成时立刻处理结果(比如保存到文件),可以用as_completed():
from concurrent.futures import ThreadPoolExecutor, as_completed import requests from bs4 import BeautifulSoup # 初始化部分和上面一致 bowl = requests.get('http://www.aaronsw.com/weblog/fullarchive') soup = BeautifulSoup(bowl.text, 'html.parser') PostLink = soup.body.find_all('a', href=True) PostLink = [i['href'] for i in PostLink][2:] baseurl = 'http://www.aaronsw.com/weblog/' bowls = [] with ThreadPoolExecutor(max_workers=10) as pool: # 先提交所有任务,收集future对象 futures = [pool.submit(requests.get, baseurl + link) for link in PostLink] # 遍历完成的任务,获取结果(带异常处理) for future in as_completed(futures): try: response = future.result() bowls.append(response) print(f"完成一个页面爬取,状态码:{response.status_code}") except Exception as e: print(f"爬取失败:{str(e)}") print(f"最终成功爬取{len(bowls)}个页面")
额外优化建议
- 线程数设置:IO密集型任务(比如网络请求)的线程数可以设得比CPU核心数多很多(比如10-20),因为线程大部分时间都在等待网络响应,你可以根据实际情况调整
max_workers的值。 - 会话复用:使用
requests.Session()复用TCP连接,能减少建立连接的开销,进一步提高爬取效率:
session = requests.Session() # 把requests.get换成session.get bowls = list(pool.map(session.get, full_urls))
内容的提问来源于stack exchange,提问作者ComplicatedPhenomenon
相关产品推荐
相关产品推荐

