【问题标题】:How to save a file from a website as PDF?如何将网站上的文件保存为 PDF?
【发布时间】:2019-03-24 04:58:04
【问题描述】:

由于 VBA 中的 IE 自动化,我正在尝试从网站(职位发布)下载 PDF。我无法生成单个 PDF。

在网页上手动执行此操作并在 pdf 图标上执行“目标另存为”会给我一个有效的 PDF。

我目前拥有的代码(网址是公开的,我随机选择了优惠)。

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

【问题讨论】:

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


    【解决方案1】:

    Shadow DOM 和无效链接生成:

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

    目标链接:

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

    下载按钮:

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

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

    Shadow root 中的下载按钮:


    模仿键盘动作:

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

    我使用SendKeys 自动打开pdf 的Save As 对话框。然后我下降窗口树结构以获取输入文件名的 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.
    3. Accessing shadow-root when marked open - selenium

    项目参考:

    1. Selenium 类型库

    `

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-19
      • 1970-01-01
      • 2011-07-25
      • 2010-11-26
      • 1970-01-01
      • 2015-11-10
      • 1970-01-01
      • 2014-12-01
      相关资源
      最近更新 更多