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

如何使用multiprocessing加速BeautifulSoup爬虫及批量图片下载任务

优化方案及实现

原有代码的核心问题

  • 串行执行:所有页面请求、图片下载都是逐个等待完成,IO等待时间被完全浪费,是速度慢的核心原因
  • 冗余逻辑:函数定义放在循环内部,每次遍历URL都会重复定义函数,无意义消耗性能
  • 无超时设置:网络请求没有超时规则,遇到无响应的站点会无限卡住
  • 全局变量风险:原代码用全局变量z做文件名计数,多进程场景下会出现资源竞争,导致文件名重复/错乱

多进程实现版本

from bs4 import BeautifulSoup
import requests
import os
import pandas as pd
from multiprocessing import Pool, cpu_count

# 全局配置
FOLDER_NAME = "downloadedpics"
REQUEST_TIMEOUT = 10  # 网络请求超时时间,单位秒
MAX_WORKERS = cpu_count() * 2  # 进程数,IO密集型可设为CPU核心数的2-4倍,可根据实际情况调整

# 提前创建存储目录,避免多进程下重复创建报错
if not os.path.exists(FOLDER_NAME):
    os.mkdir(FOLDER_NAME)

def get_image_link(image):
    """提取图片链接的公共逻辑"""
    attrs = ["data-srcset", "data-src", "data-fallback-src", "src"]
    for attr in attrs:
        try:
            return image[attr]
        except KeyError:
            continue
    return None

def process_url(args):
    """单个URL的完整处理流程:请求页面、提取图片、下载图片"""
    url_idx, url = args  # 传入url的索引和url本身,用来命名文件避免冲突
    count = 0
    try:
        # 请求页面
        r = requests.get(url, timeout=REQUEST_TIMEOUT)
        r.raise_for_status()  # 遇到4xx/5xx错误直接抛出异常跳过
        soup = BeautifulSoup(r.text, 'html.parser')
        images = soup.find_all('img', class_='pannable-image')
        print(f"URL索引{url_idx}:找到{len(images)}张图片")

        if not images:
            return 0

        for img_idx, image in enumerate(images):
            img_link = get_image_link(image)
            if not img_link:
                continue
            try:
                # 下载图片
                img_content = requests.get(img_link, timeout=REQUEST_TIMEOUT).content
                # 用url索引+图片索引命名,完全避免文件名冲突
                file_path = os.path.join(FOLDER_NAME, f"{url_idx}_image_{img_idx+1}.jpg")
                with open(file_path, "wb+") as f:
                    f.write(img_content)
                count += 1
            except Exception:
                continue
        print(f"URL索引{url_idx}:成功下载{count}/{len(images)}张图片")
        return count
    except Exception:
        print(f"URL索引{url_idx}:请求失败,跳过")
        return 0

if __name__ == "__main__":
    # 读取URL列表
    df = pd.read_csv(r'C:\Users\fani\Desktop\History.csv')
    url_list = list(enumerate(df['url'].tolist()))  # 给每个url加索引
    
    # 创建进程池执行任务
    with Pool(processes=MAX_WORKERS) as pool:
        results = pool.map(process_url, url_list)
    
    total_download = sum(results)
    print(f"所有任务执行完成,总共下载{total_download}张图片")

关键逻辑说明

  • 进程池管理:使用multiprocessing.Pool统一管理进程,不需要手动创建销毁进程,效率更高,同时可以限制最大并发数,避免并发太高被站点封禁IP
  • 避免资源竞争:给每个URL加上独有的索引,用索引命名文件,彻底解决多进程下全局计数变量的冲突问题
  • 异常兼容:所有网络请求都加了超时和异常捕获,不会因为个别站点/图片的问题卡住整个任务
  • 参数可调整:MAX_WORKERS可以根据你的网络情况调整,如果出现频繁请求失败可以适当调低数值

可选优化:多线程版本(更适合IO密集型场景)

爬取场景属于典型的IO密集型任务,用多线程也可以达到几乎相同的提速效果,且资源消耗更低,只需要把主函数部分替换为以下代码即可,其余逻辑完全不变:

from concurrent.futures import ThreadPoolExecutor

if __name__ == "__main__":
    df = pd.read_csv(r'C:\Users\fani\Desktop\History.csv')
    url_list = list(enumerate(df['url'].tolist()))
    MAX_WORKERS = 16  # 线程数可以设得更高,比如16-32,根据网络情况调整
    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
        results = list(executor.map(process_url, url_list))
    total_download = sum(results)
    print(f"所有任务执行完成,总共下载{total_download}张图片")

内容的提问来源于stack exchange,提问作者Sheykh Bahaei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 17:54:05