简体   繁体   English

使用 Python 更新 Excel 电子表格中的链接

[英]Update Links in for Excel Spreadsheet Using Python

I am running simulations in Python that generate output that need to be directly consumed by a modeler in their excel workbooks.我正在 Python 中运行模拟,生成的输出需要由建模者在其 excel 工作簿中直接使用。 I have generated code that will directly output my data into their excel spreadsheet template.我已经生成了将我的数据直接输出到他们的 excel 电子表格模板中的代码。 The code I have generated to output the data directly to their template is fine, but the problem I am running into is that the modeler has a series of workbooks that are "linked" together.我生成的用于将数据直接输出到他们的模板的代码很好,但我遇到的问题是建模者有一系列“链接”在一起的工作簿。 If I insert my data into their spreadsheet, the links to that workbook do no update unless the user physically opens the workbook to "Edit Links" -> "Update Values".如果我将数据插入到他们的电子表格中,则指向该工作簿的链接不会更新,除非用户实际打开工作簿以“编辑链接”->“更新值”。 If there was one workbook, then the user can simply open the workbook with no problem.如果只有一个工作簿,则用户可以毫无问题地打开该工作簿。 In reality, there will be over 100 workbooks that need the links updated.实际上,将有 100 多个工作簿需要更新链接。 Unfortunately, there is nothing I can do to change the modeler's approach in linking workbooks -- the only thing I can do is accommodate their approach.不幸的是,我无法改变建模者链接工作簿的方法——我唯一能做的就是适应他们的方法。

My goal is to create a Python solution that will allow me to 1) Generate the simulated Data, 2) Insert my generated data into the modeler's workbook, and 3) Update all of the links between workbooks.我的目标是创建一个 Python 解决方案,使我能够 1) 生成模拟数据,2) 将生成的数据插入建模者的工作簿,以及 3) 更新工作簿之间的所有链接。 Ultimately, in order to be streamlined, I want to be able to do all three in one end-to-end python program.最终,为了精简,我希望能够在一个端到端的 python 程序中完成所有三项工作。 I have solved (1) and (2), and I have a solution for (3) that almost works.我已经解决了 (1) 和 (2),并且我有一个几乎有效的 (3) 解决方案。 I have generated the following functional script:我生成了以下功能脚本:

from win32com.client import Dispatch
import pandas as pd
from openpyxl import load_workbook
import os
import time

def run_macro(workbook_name, vba_sub, com_instance):
    wb = com_instance.workbooks.open(workbook_name)
    wb.RefreshAll()
    xl_module = wb.VBProject.VBComponents.Add(1)
    xl_module.CodeModule.AddFromString(vba_sub.strip())
    com_instance.Application.Run('UpdateLinkValues')
    wb.Save()
    wb.Close()

    return True

def main():
    dir_root  = ("C:\\Model_Spreadsheets")

    vba_sub = \
        '''
        sub UpdateLinkValues()
            Application.AskToUpdateLinks = False
            ActiveWorkbook.UpdateLink Name:=ActiveWorkbook.LinkSources
        end sub
        '''

    xl_app = Dispatch("Excel.Application")
    xl_app.Visible = False
    xl_app.DisplayAlerts = False

    for root, dirs, files in os.walk(dir_root):
        for fn in files:
            if fn.endswith(".xlsx") and fn[0] is not "~":
                run_macro(os.path.join(root, fn), vba_sub, xl_app)
    xl_app.Quit()


if __name__ == "__main__":
    main()

This script is really close to the correct solution I am looking for, but I run into a VBA error seemingly 'randomly':该脚本非常接近我正在寻找的正确解决方案,但我似乎“随机”遇到了 VBA 错误:

run-time error '1004' method 'updatelink' method of object '_workbook' failed

This error does appear each time I try to run this script, but it does not occur for the same workbook each time -- sometimes, it occurs on the first workbook, sometimes on the 15th, etc...每次我尝试运行这个脚本时都会出现这个错误,但它不会每次都出现在同一个工作簿上——有时,它出现在第一个工作簿上,有时出现在 15 日,等等......

I have an option to debug in VBA, and the only way that I can continue on to the next workbook is if I change the macro to我可以选择在 VBA 中进行调试,我可以继续处理下一个工作簿的唯一方法是将宏更改为

sub UpdateLinkValues()
    Application.AskToUpdateLinks = False
end sub

if I run this macro and exit debug, the program will continue to run until it encounters the same error again.如果我运行这个宏并退出调试,程序将继续运行,直到再次遇到相同的错误。 My first thought was that maybe there is a timing issue between me opening the workbook and trying to run the macro.我的第一个想法是,在我打开工作簿和尝试运行宏之间可能存在时间问题。 A workaround that I have found is that I can change the macro and the app visibility:我发现的一种解决方法是我可以更改宏和应用程序可见性:

vba_sub = \
    '''
    sub UpdateLinkValues()
        Application.AskToUpdateLinks = False
    end sub
    '''

and

xl_app.Visible = True

This works fine, but I am not a fan of having each of the workbooks open and close because it takes a long time.这工作正常,但我不喜欢打开和关闭每个工作簿,因为这需要很长时间。 My question is, does anyone know why this run-time error is coming up -- with a solution?我的问题是,有谁知道为什么会出现这个运行时错误——有解决方案吗? Or perhaps, does anyone know how to intercept this run-time error in Python as an exception?或者,有没有人知道如何将 Python 中的这个运行时错误作为异常拦截? If I can intercept this error as an exception in python, then I could use my alternative solution for those particulars workbooks.如果我可以将此错误作为 python 中的异常拦截,那么我可以使用我的替代解决方案来处理这些细节工作簿。

Thanks in advance!提前致谢!

Consider having Python directly run the method UpdateLink with the COM objects you initialize, namely the xl_app and wb objects.考虑让 Python 使用您初始化的 COM 对象(即xl_appwb对象)直接运行UpdateLink方法。 No need to build a macro in each workbook and then call it.无需在每个工作簿中构建宏然后调用它。

Below UpdateLink() is wrapped in a try/except/finally block in case workbook has no links as LinkSources will return an Empty value, raising a COM exception, the very error you receive: UpdateLink()下面包含在try/except/finally块中,以防工作簿没有链接,因为LinkSources将返回一个Empty值,引发 COM 异常,即您收到的错误:

run-time error '1004' method 'updatelink' method of object '_workbook' failed对象“_workbook”的运行时错误“1004”方法“updatelink”方法失败

Also be sure to uninitialize objects (a good best practice in VBA too: Set wb = Nothing ) after use to free CPU resources else they remain as background processes until garbage collection.还要确保在使用后取消初始化对象(VBA 中的最佳实践: Set wb = Nothing )以释放 CPU 资源,否则它们将作为后台进程保留,直到垃圾回收。

def run_macro(workbook_name, com_instance):
    wb = com_instance.workbooks.open(workbook_name)
    com_instance.AskToUpdateLinks = False
    try:
       wb.UpdateLink(Name=wb.LinkSources())

    except Exception as e:
       print(e)

   finally:
       wb.Close(True)
       wb = None    
    return True

def main():
    dir_root  = ("C:\\Model_Spreadsheets")

    xl_app = Dispatch("Excel.Application")
    xl_app.Visible = False
    xl_app.DisplayAlerts = False

    for root, dirs, files in os.walk(dir_root):
        for fn in files:
            if fn.endswith(".xlsx") and fn[0] is not "~":
                run_macro(os.path.join(root, fn), xl_app)
    xl_app.Quit()
    xl = None

Aside - though VBA ships by default with Excel and MS Office applications, it is actually a separate component.另外 - 尽管 VBA 默认随 Excel 和 MS Office 应用程序一起提供,但它实际上是一个单独的组件。 To check, under Tools \\ References in VBA IDE, you will see VBA is the first checked item, nothing built-in.要检查,在 VBA IDE 中的 Tools \\ References 下,您将看到 VBA 是第一个检查的项目,没有内置。 In fact, VBA does exactly what you are doing in Python: making a COM interface to the Excel Object Library.事实上,VBA 所做的正是您在 Python 中所做的:为 Excel 对象库创建一个 COM 接口。 So in a sense VBA is just as related to Excel and Python is!所以在某种意义上,VBA 与 Excel 和 Python 的关系一样!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM