python - 无法使用 win32com.client 打开只读 Microsoft Word 文件
问题描述
我有数千个 docx 文件,我需要通过 Python 提取其中的某些元素。我在我的 Python 脚本中使用 win32com.client 来完成这项任务。
import win32com.client
doc = win32com.client.GetObject(some_path_to_a_docx_file)
这适用于 99% 的文件;但是,对于一些文件,Microsoft Word 对话框会启动:“作者希望您以只读方式打开此文件,除非您需要进行更改。以只读方式打开? ”。脚本此时停止等待用户输入。
在对话框上按是继续根据需要运行脚本;但是,这是低于标准的,因为我需要它完全自动化,而不会弹出任何对话框。有没有办法通过 win32com Python 或通过 MS Word 永久禁用 MS Word 提示?(注意:这里不能选择导入 docx 作为 win32com 的替代品。)
解决方案
Office 应用程序是最终用户应用程序,并未作为开发工具进行优化。这意味着它们在等待用户输入时可能会出现挂起,如问题中所述。没有简单、干净的方法可以解决这个问题,这就是为什么建议将 Open XML 文件格式用于消除对话框存在问题的需求...
如果必须使用自动化,那么我知道有两种可能性。详细信息不是 python,但这确实记录了基本方法。
- 如果代码无法继续,请使用 Timer 函数和 SendKeys 自动关闭对话框。这有点像彩票,因为不可能知道哪个对话框被关闭了。通常,发送“Escape”键。曾几何时,有一组针对各种编程语言的知识库文章,但 Microsoft 站点上不再提供这些文章。我找到了一个存档C-Bit并正在复制演示经典 VB6 原理的相关示例内容:
本节中的步骤演示了 Microsoft Word 打印文档的自动化。自动化客户端调用 Word 文档对象的 PrintOut 方法。如果用户的默认打印机配置为打印到 FILE 端口,则调用 PrintOut 会生成一个对话框,提示用户输入文件名。若要确定 PrintOut 方法是否导致出现此对话框,Visual Basic 自动化客户端使用 Timer 控件来检测调用 PrintOut 方法后的空闲时间。在调用 PrintOut 之前,定时器已启用并设置为在 5 秒内触发。打印输出完成后,计时器被禁用。因此,如果 PrintOut 方法在五秒内完成,则不会发生 Timer 事件并且不会采取进一步的操作。文档被打印并且代码执行在 PrintOut 方法之外继续。但是,如果 Timer 事件发生在五秒间隔内,则假定 PrintOut 方法尚未完成,并且延迟是由等待用户输入的对话框引起的。当 Timer 事件发生时,自动化客户端将焦点赋予 Word 并使用 SendKeys 关闭对话框。
注意 出于演示目的,此示例使用 PrintOut 方法,以便在打印到设置为 FILE 端口的打印机时有意显示一个对话框。请注意,PrintOut 方法有两个参数,OutputfileName 和 PrintToFile,您可以提供它们以避免出现此对话框。
此外,当使用这种“计时器”方法时,您可以自定义等待时间大于或小于五秒,以及自定义发送到对话框的击键。
该演示包含两个 Visual Basic 项目:
提供用于检测延迟的 Timer 类的 ActiveX EXE。为 Timer 类使用 ActiveX EXE 的原因是在单独的进程中运行 Timer 代码,因此在单独的线程中运行。这使得 Timer 类可以在暂停的自动化调用期间引发事件。
使用 Word 自动化并调用 PrintOut 方法来打印文档的标准 EXE。它使用 ActiveX EXE 来检测调用 PrintOut 方法时的延迟。创建 ActiveX EXE 项目
- 启动 Visual Basic 并创建一个 ActiveX EXE 项目。Class1 是默认创建的。
- 在项目菜单上单击以选择属性,然后将项目名称更改为 MyTimer。
- 将以下代码复制并粘贴到 Class1 模块中:Option Explicit
Public Event Timer() Private oForm1 As Form1
Private Sub Class_Initialize()
Set oForm1 = New Form1
oForm1.Timer1.Enabled = False
End Sub
Private Sub Class_Terminate()
Me.Enabled = False
Unload oForm1
Set oForm1 = Nothing
End Sub
Public Property Get Enabled() As Boolean
Enabled = oForm1.Timer1.Enabled
End Property
Public Property Let Enabled(ByVal vNewValue As Boolean)
oForm1.Timer1.Enabled = vNewValue
If vNewValue = True Then
Set oForm1.oClass1 = Me
Else
Set oForm1.oClass1 = Nothing
End If
End Property
Public Property Get Interval() As Integer
Interval = oForm1.Timer1.Interval
End Property
Public Property Let Interval(ByVal vNewValue As Integer)
oForm1.Timer1.Interval = vNewValue End Property
Friend Sub TimerEvent()
RaiseEvent Timer
End Sub
- 在“项目”菜单上,选择“添加表单”以将新表单添加到项目中。
- 将 Timer 控件添加到窗体。
- 将以下代码复制并粘贴到 Form1 的代码模块中:Option Explicit
Public oClass1 As Class1
Private Sub Timer1_Timer()
oClass1.TimerEvent
End Sub
- 将此项目保存在名为 Server 的新子文件夹中。
- 在“文件”菜单上,选择“生成 MyTimer.Exe”以构建和注册组件。创建自动化客户端
- 使用 Windows API 来识别和消除可能的问题对话框。我在MSDN 论坛上找到了一些代码,我在这里复制。归因于用户名
yet
:
这是一个通过 C# 中的 pinvoke 使用 Win32 API 的示例。我可以通过 FindWindow 和 SendMessage 或 PostMessage 处理已知的 Word 窗口,例如 Word->File->Options 对话框窗口。请仔细阅读示例,看看它是否适合您。由于您知道要处理哪些对话框,请使用 spy++ 查找窗口标题和窗口类并在此示例中使用。
对于您的场景,可能不需要 SendKeys。
希望这可以帮助。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace SendKeys
{
public partial class Form1 : Form
{
// For Windows Mobile, replace user32.dll with coredll.dll
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
// Find window by Caption only. Note you must pass IntPtr.Zero as the first parameter.
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
static uint WM_CLOSE = 0x10;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// the caption and the className is for the Word -> File -> Options window
// the caption and the className are got by using spy++ application and focussing on the window we are researching.
string caption = "Word Options";
string className = "NUIDialog";
IntPtr hWnd= (IntPtr)(0);
// Win 32 API being called through PInvoke
hWnd = FindWindow(className, caption);
/*bool retVal = false;
if ((int)hWnd != 0)
{
// Win 32 API being called through PInvoke
retVal = SetForegroundWindow(hWnd);
}*/
if ((int)hWnd != 0)
{
CloseWindow2(hWnd);
//CloseWindow(hWnd); // either sendMessage or PostMessage can be used.
}
}
static bool CloseWindow(IntPtr hWnd)
{
// Win 32 API being called through PInvoke
SendMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
return true;
}
static bool CloseWindow2(IntPtr hWnd)
{
// Win 32 API being called through PInvoke
PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
return true;
}
}
}
推荐阅读
- python - 将列添加到一个熊猫数据框到另一个有条件的数据框
- gradle - gradle:让工具 api 模型调用依赖于 buildscript 中的任务
- api - vue中刷新页面后如何获取cookie值并放入Vuex store
- java - 从服务发送到活动的数据不起作用
- c - 在不知道结构实现的情况下如何编写通用列表?
- angular - 通过 ngrx 操作传递回调是一种不好的做法吗
- javascript - 多个或运算符检查不同变量是否为空字符串的简写
- ti-nspire - ti-nspire cx cas 上的简单范围和域功能不起作用
- r - 如何按条件隐藏多个闪亮的 ui,以及如何使用 session$userData?
- python - Confluent-kafka 消费者未阅读最后发布的消息