You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程批量请求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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:11:10