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

Python多线程Selenium在Digital Ocean服务器渲染超时问题

问题:多线程爬虫在低配置VPS上触发渲染超时,单线程正常但速度缓慢

本地MacBook Air M2(8GB内存,macOS Sonoma 14.2.1)运行正常,但部署到Digital Ocean的1核1GB内存Ubuntu 23.10 Droplet的Django服务中,处理POST请求时抛出Timed out receiving message from renderer: 60.000错误。单线程运行无报错,但效率极低。


相关代码

主调度代码

from pathlib import Path
from multiprocessing import Manager
from multiprocessing.pool import ThreadPool

from .scrapers.scraper_onu_odc import scrap_from_onu_odc
from .scrapers.scraper_peps import scrap_from_senaclaft_peps
from .scrapers.scraper_bcu import scrap_from_bcu_infractores_cheques
from .scrapers.scraper_onu_scs import scrap_from_onu_scs
from .scrapers.scraper_ofac import scrap_from_ofac
from .scrapers.scraper_google import scrap_from_google
from .scrapers.scraper_yahoo import scrap_from_yahoo
from .scrapers.scraper_bing import scrap_from_bing
from .scrapers.scraper_wikipedia import scrap_from_wikipedia
from .scrapers.scraper_fincen import scrap_from_fincen

BASE_DIR = Path(__file__).resolve().parent.parent.parent

def scrapping_pool(scraper):
    scraper[0](*scraper[1])


def get_search_results(info, uuid):

    paths = {
    'screenshots_path' : BASE_DIR / f'searcher/screenshots/{uuid}/',
    'certificate_path' : str(BASE_DIR / 'searcher/ssl_certificates/bcu.pem')
    }
    
    manager = Manager()
    results = manager.dict()

    person_to_search = info['person_to_search_name'] + ' ' + info['person_to_search_surname']

    params = (person_to_search, results, paths)
    scrapers = [
        (scrap_from_onu_odc, params),
        (scrap_from_senaclaft_peps, params),
        (scrap_from_bcu_infractores_cheques, params),
        (scrap_from_onu_scs, params),
        (scrap_from_ofac, params),
        (scrap_from_fincen, params),
        (scrap_from_google, params),
        (scrap_from_yahoo, params),
        (scrap_from_bing, params),
        (scrap_from_wikipedia, params)
    ]

    try:
        with ThreadPool(8) as pool:
            for result in pool.map(scrapping_pool, scrapers):
                print(result)
        pool.close()
    except Exception as e:
        print(e)

    return results, paths['screenshots_path']

示例爬虫代码

import os
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.chrome.service import Service

from ...pages import get_pages
from .utils import get_selenium_params

def scrap_from_yahoo(name, results_dic, paths):
    page_info = get_pages()
    screenshot_folder = paths['screenshots_path']
    selenium_params = get_selenium_params()

    browser = webdriver.Chrome(service=selenium_params['service'], options=selenium_params['options'])

    url = page_info['yahoo']['url'] + name.replace(' ', '+')
    
    os.makedirs(screenshot_folder, exist_ok=True)
    screenshot_path = f'{screenshot_folder}/yahoo.png'

    has_matches = False
    try:
        browser.get(url)
        browser.set_window_size(1366, 728)
        browser.save_screenshot(screenshot_path)
        has_matches = True
        
    except Exception as e:
        has_matches = False
        
    results_dic['yahoo'] = (has_matches, screenshot_path, screenshot_folder, url)

ChromeDriver参数配置

def get_selenium_params():
    driver_path = BASE_DIR / 'driver/chromedriver-linux64/chromedriver'
    driver_options = webdriver.ChromeOptions()
    driver_options.binary_location = "/usr/bin/chromium-browser"
    driver_options.add_argument('--headless')
    driver_options.add_argument('--start-maximized')
    driver_options.add_argument('--remote-debugging-pipe')
    driver_options.add_argument("enable-automation")
    driver_options.add_argument("--no-sandbox")
    driver_options.add_argument("--disable-extensions")
    driver_options.add_argument("--dns-prefetch-disable")
    driver_options.add_argument("--disable-gpu")
    driver_options.add_argument('--disable-dev-shm-usage')

    return {
        'service': Service(executable_path=driver_path),
        'options': driver_options
    }

错误信息

Message: timeout: Timed out receiving message from renderer: 60.000
Stacktrace:
#0 0x594000d8b8a3 <unknown>
#1 0x594000a818c6 <unknown>
#2 0x594000a697a0 <unknown>
#3 0x594000a67516 <unknown>
#4 0x594000a67b9f <unknown>
#5 0x594000ab53de <unknown>
#6 0x594000ab1d3d <unknown>
#7 0x594000afaaed <unknown>
#8 0x594000aee343 <unknown>
#9 0x594000abf593 <unknown>
#10 0x594000abff5e <unknown>
#11 0x594000d4f88b <unknown>
#12 0x594000d537e5 <unknown>
#13 0x594000d3d5b1 <unknown>
#14 0x594000d54372 <unknown>
#15 0x594000d228bf <unknown>
#16 0x594000d7a768 <unknown>
#17 0x594000d7a93b <unknown>
#18 0x594000d8a9f4 <unknown>
#19 0x707d49897b5a <unknown>

解决方案

1. 降低线程池规模

1核1GB内存的VPS无法同时承载8个Chrome实例(每个Chrome实例至少占用100-200MB内存),将线程数降至2-3:

with ThreadPool(2) as pool:

2. 优化Chrome参数减少资源占用

更新get_selenium_params,添加轻量配置:

def get_selenium_params():
    driver_path = BASE_DIR / 'driver/chromedriver-linux64/chromedriver'
    driver_options = webdriver.ChromeOptions()
    driver_options.binary_location = "/usr/bin/chromium-browser"
    driver_options.add_argument('--headless=new')  # 新版无头模式,资源占用更低
    driver_options.add_argument('--window-size=1024,768')  # 缩小渲染窗口
    driver_options.add_argument('--single-process')  # 强制单进程运行
    driver_options.add_argument("--no-sandbox")
    driver_options.add_argument("--disable-extensions")
    driver_options.add_argument("--dns-prefetch-disable")
    driver_options.add_argument("--disable-gpu")
    driver_options.add_argument('--disable-dev-shm-usage')
    driver_options.add_argument('--disable-images')  # 禁用图片加载(如果截图不需要)
    driver_options.add_argument('--blink-settings=imagesEnabled=false')

    return {
        'service': Service(executable_path=driver_path),
        'options': driver_options
    }

3. 添加强制超时并释放浏览器资源

每个爬虫函数必须显式关闭浏览器,避免内存泄漏,同时设置页面加载超时:

def scrap_from_yahoo(name, results_dic, paths):
    page_info = get_pages()
    screenshot_folder = paths['screenshots_path']
    selenium_params = get_selenium_params()

    browser = None
    try:
        browser = webdriver.Chrome(service=selenium_params['service'], options=selenium_params['options'])
        browser.set_page_load_timeout(30)  # 30秒页面加载超时
        browser.set_script_timeout(30)     # 30秒脚本执行超时
        
        url = page_info['yahoo']['url'] + name.replace(' ', '+')
        os.makedirs(screenshot_folder, exist_ok=True)
        screenshot_path = f'{screenshot_folder}/yahoo.png'

        browser.get(url)
        browser.set_window_size(1024, 768)
        browser.save_screenshot(screenshot_path)
        has_matches = True
        
    except Exception as e:
        has_matches = False
        print(f"Yahoo爬虫出错: {e}")
    finally:
        if browser:
            browser.quit()  # 强制关闭浏览器释放资源
        
    results_dic['yahoo'] = (has_matches, screenshot_path, screenshot_folder, url)

4. 补充Swap分区缓解内存压力

如果内存仍然不足,创建1GB Swap文件:

sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 设置开机自动挂载
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

5. 改用进程池替代线程池(可选)

受Python GIL限制,线程池在Chrome渲染这类CPU密集任务上效率有限,改用multiprocessing.Pool:

from multiprocessing import Pool  # 替换ThreadPool
# ...
try:
    with Pool(2) as pool:
        for result in pool.map(scrapping_pool, scrapers):
            print(result)
    pool.close()
except Exception as e:
    print(e)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:45:57