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

从Excel实例池转为动态创建实例时,xlwings打开工作簿失败的问题求助

从Excel实例池转为动态创建实例时,xlwings打开工作簿失败的问题求助

大家好,我目前在Windows服务器上运行一个Flask API,用来对Excel文件进行增删改操作以及运行宏,交互部分用的是xlwings。

之前的方案是预先创建一个Excel实例池,每个请求携带user_id,我用它来给用户分配对应的实例;如果没有可用实例的话,就用一个备份实例兜底,这套方案运行得还不错。

现在我想改成动态创建实例——用户需要的时候再创建。因为Excel不支持多线程和同一个实例交互,所以我没法在请求线程里直接创建实例。我本来想沿用之前重启实例的思路:在Redis里写入一个信号,让主线程创建新实例,然后把实例的PID存到Redis里,再让请求线程取出来用。但这么做之后,打开工作簿时直接报错了,错误信息如下:

info:app found f<App [excel] 9232>
ERROR:custom_logger:Error opening workbook: (-2147352567, 'Exception occurred.', (0, 'Microsoft Excel', 'La méthode Open de la classe Workbooks a échoué.', 'xlmain11.chm', 0, -2146827284), None)

下面是我之前用实例池时的核心代码,以及尝试动态创建时沿用的重启/创建逻辑:

初始化与实例池创建代码

import os
import sys
import logging
import pythoncom
import xlwings as xw
from flask import Flask
from redis import Redis

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(SCRIPT_DIR))

# Initialize Flask app
app = Flask(__name__)
app.logger.setLevel(logging.DEBUG)

BUCKET_NAME = os.getenv('AWS_BUCKET')

# Initialize COM once during app startup
pythoncom.CoInitialize()

redis_instance = Redis(host='your-redis-host', port=6379, db=0)

def create_excel_instances(number_of_instances):
    xl_apps = []
    number_of_instances = int(number_of_instances)
    for i in range(1, number_of_instances + 1):
        xl_app = xw.App(visible=False)
        xl_app.screen_updating = False
        xl_app.display_alerts = False
        xl_app.enable_events = False
        xl_app.ask_to_update_links = False
        xl_app.ignore_remote_requests = True
        redis_instance.set(f'XL_APP{i}_PID', xl_app.pid)
        print(f'xl_app{i} pid: {xl_app.pid}')
        xl_apps.append(xl_app)
    
    # 创建备份实例
    backup_app = xw.App(visible=False)
    backup_app.screen_updating = False
    backup_app.display_alerts = False
    backup_app.enable_events = False
    backup_app.ask_to_update_links = False
    backup_app.ignore_remote_requests = True
    redis_instance.set('XL_APP_BACK_UP_PID', backup_app.pid)
    print(f'backup app pid: {backup_app.pid}')
    xl_apps.append(backup_app)
    return xl_apps

# 创建初始实例池
xl_apps = create_excel_instances(os.getenv('NUMBER_OF_EXCEL_INSTANCES'))

setup_routes(app)

# 调度器相关(略)
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
if scheduler.running:
    scheduler.shutdown(wait=False)
scheduler.start()

def create_app():
    return app

实例重启/动态创建逻辑

import time
import psutil
import threading

def restart_excel_instance(instance_key):
    try:
        custom_logger.info(f"Starting restart of instance {instance_key}")
        old_pid = int(redis_instance.get(instance_key))
        custom_logger.info(f"Old PID: {old_pid}")
        
        # 尝试通过xlwings关闭旧实例
        try:
            if old_pid in xw.apps:
                custom_logger.info(f"Closing via xlwings Excel instance (PID: {old_pid})")
                for book in xw.apps[old_pid].books:
                    try:
                        book.close()
                    except Exception as e:
                        custom_logger.error(f"Error closing workbook: {e}")
                try:
                    xw.apps[old_pid].quit()
                except Exception as e:
                    custom_logger.error(f"Error closing via xlwings: {e}")
        except Exception as e:
            custom_logger.error(f"Error during xlwings closure: {e}")
        
        time.sleep(2)
        
        # 强制终止进程(如果还在运行)
        try:
            process = psutil.Process(old_pid)
            if process.is_running():
                custom_logger.warning(f"Excel instance still running, attempting forced termination")
                process.terminate()
                process.wait(timeout=5)
                custom_logger.info("Excel instance successfully terminated")
        except psutil.NoSuchProcess:
            custom_logger.info(f"Process {old_pid} no longer exists")
        except Exception as e:
            custom_logger.error(f"Error during forced process termination: {e}")
        
        # 创建新实例
        custom_logger.info("Creating new Excel instance")
        xl_app = xw.App(visible=False)
        xl_app.screen_updating = False
        xl_app.display_alerts = False
        new_pid = xl_app.pid
        redis_instance.set(instance_key, new_pid)
        custom_logger.info(f"New instance successfully created (PID: {new_pid})")
        return True
    except Exception as e:
        custom_logger.error(f"Error during instance restart {instance_key}: {e}")
        return False

# 监听Redis的重启请求
def check_for_restart_requests():
    while True:
        instance_keys = [f'XL_APP{i}_PID' for i in range(1, int(os.getenv('NUMBER_OF_EXCEL_INSTANCES')) + 1)] + ['XL_APP_BACK_UP_PID']
        for instance_key in instance_keys:
            restart_flag = redis_instance.get(f'{instance_key}_needs_restart')
            if restart_flag and restart_flag.decode('utf-8') == '1':
                print(f"Restart required for instance {instance_key}")
                restart_excel_instance(instance_key)
                redis_instance.delete(f'{instance_key}_needs_restart')
        time.sleep(5)

# 启动监听线程
restart_checker = threading.Thread(target=check_for_restart_requests)
restart_checker.daemon = True
restart_checker.start()

我猜测问题可能出在COM线程模型上?因为预先创建实例时是在Flask启动的主线程里做的,而动态创建是在后台线程里完成的,xlwings和Excel的COM交互是不是对线程有严格要求?

有没有朋友遇到过类似的问题?或者能给我一些排查方向?感谢大家!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 09:44:35