VBA-IE自动化-另存为PDF无法正常工作

时间:2018-10-19 09:54:46

标签: html vba internet-explorer web-scraping pdf-generation

由于VBA中的IE自动化,我试图从网站上自动下载PDF(职位发布),由于某种原因,我无法生成单个PDF。

通过在网页上进行手动操作,然后在pdf图标上执行“另存为”操作,效果很好,可以给我有效的PDF,但是自动化失败。

我不明白为什么,希望有人能够给我提示。

谢谢

VeeBee

请在此后找到我到目前为止的代码(URL是公开的,我已经随机选择了报价)

Private Declare Function DownloadFilefromURL Lib "urlmon" _
Alias "URLDownloadToFileA" _
(ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long

Private Const ERROR_SUCCESS As Long = 0
Private Const BINDF_GETNEWESTVERSION As Long = &H10


Public Function DownloadFile(SourceUrl As String, LocalFile As String) As Boolean
    DownloadFile = DownloadFilefromURL(0&, SourceUrl, LocalFile, BINDF_GETNEWESTVERSION, 0&) = ERROR_SUCCESS
End Function


Sub TestSavePDF()
    Dim oNav As SHDocVw.InternetExplorer
    Dim oDoc As MSHTML.HTMLDocument
    Dim MyURL As String

    Set oNav = New SHDocVw.InternetExplorer
    oNav.Visible = True
    'Test Altays Client A (Banque de France)
    MyURL = "https://www.recrutement.banque-france.fr/detail-offre/?NoSource=16001&NoSociete=167&NoOffre=2036788&NoLangue=1"
    'Test Altays Client B (Egis)
    '        MyURL = "https://www.altays-progiciels.com/clicnjob/FicheOffreCand.php?PageCour=1&Liste=Oui&Autonome=0&NoOffre=2037501&RefOffrel=&NoFaml=0&NoParam1l=0&NoParam2l=0&NoParam3l=0&NoParam133l=0&NoParam134l=0&NoParam136l=0&NoEntite1=0&NoEntite=&NoPaysl=0&NoRegionl=0&NoDepartementl=0&NoTableOffreLieePl=0&NoTableOffreLieeFl=0&NoNivEtl=0&NoTableCCl=0&NoTableCC2l=0&NoTableCC3l=0&NoTableOffreUnl=0&NoTypContratl=0&NoTypContratProl=0&NoStatutOffrel=&NoUtilisateurl=&RechPleinTextel=#ancre3"


    oNav.navigate MyURL
    'link provided to download the job offer in PDF. when clicked the PDF opens in a new tab
    MyURL = "https://www.altays-progiciels.com/clicnjob/ExportPDFFront.php"

    DownloadFile MyURL, "C:\[...Path...]\test.pdf"

End Sub

1 个答案:

答案 0 :(得分:1)

影子DOM和无效的链接生成:

initial job page自动点击目标href不会产生可行的页面链接。大概是因为重要的事情实际上发生在服务器端。

目标href:

enter image description here

您可以单击此页面上的实际下载按钮

下载按钮:

enter image description here

这将打开一个新窗口,这就是Selenium很棒的原因。 Selenium具有切换到此新窗口的方法。否则,您可以使用答案中稍后详细介绍的FindWindow方法来查找Save As窗口。

在这个新窗口中,由于不能通过DOM获得所需的内容,因此无法像平常一样按常规方式与按钮进行交互。如果仔细检查,您会看到pdf按钮位于shadow-root中,即您无法访问的位置。这是一种设计选择。我确实需要在某个时候研究this的可能性(使用'/ deep /'组合器通过阴影DOM进行选择),但我认为在VBA中它不成立。

影子根目录中的

下载按钮:

enter image description here


模仿键盘操作:

我正在使用selenium basic VBA包装器和API来模拟屏幕上的操作,并使用Save As窗口将其另存为pdf(请参见底部的图像)。特别是通过Save使用SendKeys键盘快捷键。这可行。 我用Spy++来检查Window树形结构,并检查Window Class的名称和Titles

我使用SendKeys自动打开pdf的Save As对话框。然后,我下降Window树结构以在输入文件名的ComboBox上获取句柄,因此可以向其中发送 message 即文件名,并在Save按钮上发送,可以单击它。您可能需要更长的等待时间,以确保下载正确进行。我认为这有点不足,我希望有所改进。

通过Spy++

的窗口结构

它相当健壮。我使用Selenium Basic是为了轻松使用iframe并解决same origin policy问题。使用IE,您不能简单地获取iframe的src链接并愉快地导航至原始添加的pdf打印页面。我相信您可以做的是发出一个初始XMLHTTP request并获取src属性值,即链接。然后将src链接传递到IE,然后对Windows处理部分进行如下所示的操作。

与更多的时间相比,我可以添加IE版本,并且比使用显式的等待时间来查看更可靠的方法,该方法用于在退出IE实例之前监视文件下载。可能与this相似(如答案之一所述:使用SetWindowsHookEx设置WH_SHELL挂钩并查找HSHELL_WINDOWCREATED事件。)


注释:

  1. 这是为64位编写的。 32位删除PtrSafe。您可以将LongPtr切换为Long,但我认为它仍然兼容。
  2. 非常感谢@ErikvonAsmuth耐心地与我一起使用API​​。看看他在这里与Windows合作的出色答案。

VBA:

Option Explicit

Declare PtrSafe Function SendMessageW Lib "User32" (ByVal hWnd As LongPtr, ByVal wMsg As LongPtr, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr

Declare PtrSafe Function FindWindowExW Lib "User32" (ByVal hWndParent As LongPtr, _
                                                     Optional ByVal hwndChildAfter As LongPtr, Optional ByVal lpszClass As LongPtr, _
                                                     Optional ByVal lpszWindow As LongPtr) As LongPtr

Public Declare PtrSafe Function FindWindowW Lib "User32" (ByVal lpClassName As LongPtr, Optional ByVal lpWindowName As LongPtr) As LongPtr

Public Const WM_SETTEXT = &HC
Public Const BM_CLICK = &HF5

Public Sub GetInfo()
    Dim d As WebDriver, keys As New Selenium.keys
    Const MAX_WAIT_SEC As Long = 5
    Dim t As Date

    Set d = New ChromeDriver
    Const URL = "https://www.recrutement.banque-france.fr/detail-offre/charge-de-recrutement-confirme-h-f-2037343/"
    With d
        .start "Chrome"
        .get URL
        .SwitchToFrame .FindElementById("altiframe")
        .FindElementById("btn-pdf").Click
        .SwitchToNextWindow
        .SendKeys keys.Control, "s"

        Dim str1 As String, cls As String, name As String
        Dim ptrSaveWindow As LongPtr

        str1 = "#32770" & vbNullChar

        t = Timer
        Do
            DoEvents
            ptrSaveWindow = FindWindowW(StrPtr(str1))
            If Timer - t > MAX_WAIT_SEC Then Exit Do
        Loop While ptrSaveWindow = 0

        Dim duiViewWND As LongPtr, directUIHWND As LongPtr
        Dim floatNotifySinkHWND As LongPtr, comboBoxHWND As LongPtr, editHWND As LongPtr


        If Not ptrSaveWindow > 0 Then Exit Sub

        duiViewWND = FindWindowExW(ptrSaveWindow, 0&)

        If Not duiViewWND > 0 Then Exit Sub

        directUIHWND = FindWindowExW(duiViewWND, 0&)

        If Not directUIHWND > 0 Then Exit Sub

        floatNotifySinkHWND = FindWindowExW(directUIHWND, 0&)

        If Not floatNotifySinkHWND > 0 Then Exit Sub

        comboBoxHWND = FindWindowExW(floatNotifySinkHWND, 0&)

        If Not comboBoxHWND > 0 Then Exit Sub

        editHWND = FindWindowExW(comboBoxHWND, 0&)

        If Not editHWND > 0 Then Exit Sub

        Dim msg As String
        msg = "myTest.pdf" & vbNullChar

        SendMessageW editHWND, WM_SETTEXT, 0, StrPtr(msg)

        .SendKeys keys.Control, "s"

        Dim ptrSaveButton As LongPtr
        cls = "Button" & vbNullChar
        name = "&Save" & vbNullChar

        ptrSaveButton = FindWindowExW(ptrSaveWindow, 0, StrPtr(cls), StrPtr(name))

        SendMessageW ptrSaveButton, BM_CLICK, 0, 0

        Application.Wait Now + TimeSerial(0, 0, 4)

        .Quit
    End With
End Sub

另存为对话框窗口:


参考:

  1. Shadow DOM
  2. Using shadow DOM - Developer Mozilla pages.

项目参考:

  1. 硒类型库

    `