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

时间:2017-01-21 21:03:03

标签: python excel vba

我在Python中运行模拟,生成需要由excel工作簿中的建模者直接使用的输出。我生成的代码将直接将我的数据输出到他们的Excel电子表格模板中。我生成的用于将数据直接输出到其模板的代码很好,但我遇到的问题是建模者有一系列“链接”在一起的工作簿。如果我将数据插入其电子表格,则除非用户将工作簿实际打开到“编辑链接” - >,否则指向该工作簿的链接不会更新。 “更新值”。如果有一个工作簿,则用户可以毫无问题地打开工作簿。实际上,将有超过100个工作簿需要更新链接。不幸的是,我无法改变建模者链接工作簿的方法 - 我唯一能做的就是适应他们的方法。

我的目标是创建一个Python解决方案,允许我1)生成模拟数据,2)将生成的数据插入到建模者的工作簿中,3)更新工作簿之间的所有链接。最终,为了简化,我希望能够在一个端到端的python程序中完成所有三个。我已经解决了(1)和(2),我有一个解决方案(3)几乎可以工作。我已经生成了以下功能脚本:

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()

这个脚本非常接近我正在寻找的正确解决方案,但我遇到了一个看起来像'随机'的VBA错误:

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

每次我尝试运行此脚本时都会出现此错误,但每次都不会出现相同的工作簿 - 有时会出现在第一个工作簿上,有时会发生在15日等等...

我可以选择在VBA中进行调试,我可以继续使用下一个工作簿的唯一方法是将宏更改为

sub UpdateLinkValues()
    Application.AskToUpdateLinks = False
end sub

如果我运行此宏并退出调试,程序将继续运行,直到再次遇到相同的错误。我的第一个想法是,打开工作簿和尝试运行宏之间可能存在时间问题。我找到的解决方法是我可以更改宏和应用程序可见性:

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

xl_app.Visible = True

这很好用,但我并不喜欢让每个工作簿都打开和关闭,因为它需要很长时间。我的问题是,有没有人知道为什么会出现这个运行时错误 - 有一个解决方案?或许,有没有人知道如何在Python中拦截这个运行时错误作为例外?如果我可以在python中拦截此错误作为异常,那么我可以使用我的替代解决方案来处理那些详细的工作簿。

提前致谢!

1 个答案:

答案 0 :(得分:1)

考虑让Python直接使用您初始化的COM对象运行方法UpdateLink,即xl_appwb对象。无需在每个工作簿中构建宏,然后调用它。

以下UpdateLink()包含在try/except/finally块中以防工作簿没有链接,因为LinkSources将返回 Empty 值,引发COM异常,你收到的错误很多:

  

运行时错误' 1004'方法' updatelink'对象的方法' _workbook'   失败

还要确保在使用之后取消初始化对象(在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

除此之外 - 尽管VBA默认提供Excel和MS Office应用程序,但它实际上是一个单独的组件。要检查,在VBA IDE中的Tools \ References下,您将看到VBA是第一个已检查的项目,没有内置任何内容。实际上,VBA正是您在Python中所做的:为Excel对象库创建一个COM接口。所以从某种意义上说,VBA与Excel和Python有关!