【问题标题】:Download file from url in Excel 2019 (it works on Excel 2007)从 Excel 2019 中的 url 下载文件(适用于 Excel 2007)
【发布时间】:2021-01-26 22:14:25
【问题描述】:

我得到了一个代码,可以从需要凭据的网站下载 CSV 文件。感谢这个网站,我得到了一个代码,我可以适应我的需要。我的相关代码部分是:

Option Explicit

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

Private Function DownloadUrlFile(URL As String, LocalFilename As String) As Boolean
    Dim RetVal As Long
    RetVal = URLDownloadToFileA(0, URL, LocalFilename, 0, 0)
    If RetVal = 0 Then DownloadUrlFile = True
End Function

Sub DESCARGAR_CSV_DATOS()

Dim EstaURL As String
Dim EsteCSV As String

EstaURL = "https://user:token@www.privatewebsite.com/export/targetfile.csv"
EsteCSV = "MyCSV " & Format(Date, "dd-mm-yyyy") & ".csv"

    With Application
        .DisplayAlerts = False
        .ScreenUpdating = False
    End With

    DownloadUrlFile EstaURL, _
        ThisWorkbook.Path & "\" & EsteCSV

    DoEvents

    Workbooks.Open ThisWorkbook.Path & "\" & EsteCSV, , True, , , , , , , , , , , True

    'rest is just doing operations and calculations inside workbook

End Sub

抱歉,我无法提供真实的网址。无论如何,这段代码自 2019 年 9 月以来一直运行良好。而且它仍然每天都运行良好。

执行此代码的计算机都是 Windows 7 和 Excel 2007 和 64 位。他们都没有失败。

但是现在,这项任务将外包给另一个办公室。那里的计算机是 Excel 2019、Windows 10 和 64 位。

并且代码在那里不起作用。它不会出现任何错误,但函数DownloadUrlFile不会在Excel 2019 + W10上下载任何文件

所以继续,Excel 2007 + Windows 7 完美运行(今天测试)。 Excel 2019 + Windows 10 不起作用(屏幕上没有错误)。

我试图解决的问题:

  1. 我检查了文件 urlmon.dll 存在于 system32 中,确实存在
  2. 我尝试使用PtrSafe 声明函数URLDownloadToFileA
  3. 如果我用 Excel 2019 + W10 在 PC 的 Chrome 中手动输入 url,文件下载正确,所以 URL 没问题。

这些都没有解决我的问题。我很确定解决方案真的很简单,因为代码在 Excel 2007 中完美运行,但我在这里找不到我缺少的东西。

我想获得一个在任何情况下都可以使用的代码,但如果这是唯一的方法,我也会接受仅适用于 Excel 2019 和 Windows 10 的解决方案。

希望有人能对此有所了解。提前致谢。

更新:也尝试了this post 中的解决方案,但仍然没有。

更新 2:另外,使用 Excel 2010 测试了此处发布的代码 (Excel 2007),它运行良好。

更新 3: 变量 RetVal 存储下载结果。我知道一些价值观:

' Returns 0 if success, error code if not.
' Error codes:
' -2146697210 "file not found".
' -2146697211 "domain not found".
' -2147467260 "transfer aborted".

但就我而言,它返回-2147221020。那会是什么?

更新 4: 嗯,这很奇怪。我已尝试使用相同的代码从公共网站下载不同的文件,它适用于 Excel 2019 + W10。 我制作了一个新的简单代码,如下所示:

#If VBA7 And Win64 Then
    Private Declare PtrSafe Function URLDownloadToFile Lib "urlmon" _
      Alias "URLDownloadToFileA" ( _
        ByVal pCaller As LongPtr, _
        ByVal szURL As String, _
        ByVal szFileName As String, _
        ByVal dwReserved As LongPtr, _
        ByVal lpfnCB As LongPtr _
      ) As Long
#Else
    Private Declare Function URLDownloadToFile 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
#End If

Sub Descarga()
Dim EstaURL As String
Dim EsteCSV As String

EstaURL = privateone 'can't be shared, sorry
EsteCSV = "CSV Datos " & Format(Date, "dd-mm-yyyy") & ".csv"

    On Error GoTo Errores
    URLDownloadToFile 0, "https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm", ThisWorkbook.Path & "\" & EsteCSV, 0, 0
    URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" & EsteCSV, 0, 0
    Exit Sub
Errores:
    'Si es un bucle lo mejor sería no mostrar ningún mensaje
    MsgBox "Not downloaded", vbCritical, "Errores"
End Sub

URLDownloadToFile 0, "https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm", ThisWorkbook.Path & "\" & EsteCSV, 0, 行完美运行并下载文件。

URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" & EsteCSV, 0, 0这一行不起作用。

所以再次测试完全相同的代码,但在 Excel 2007 上,它们都可以工作

为什么第一次下载有效而第二次在 Excel 2019 + W10 上无效,但它们都在 Excel 2007+W7 上有效?

更新 5: URL 是私有的,因为它包含用户名和密码,但它是这样的:

https://user:token@www.privatewebsite.com/export/target%20file.csv

感谢@Stachu,该 URL 无法在任何 PC 上的 Internet Explorer 上手动运行(我的意思是在资源管理器导航栏中复制/粘贴)。该 URL 可在所有 PC 的 Google Chrome 中完美运行。

真的很好奇,手动地,Internet Explorer 上的 URL 不起作用,但用 VBA 编码并在 Excel2007/2010 上执行的相同 URL 工作得很好。也许我应该改变一些关于编码的东西?

更新 6: 仍在研究您的所有帖子。这里的问题是我只是数据专家,分析师,所以这里发布的大量信息对我来说听起来真的很核心。

我已在 1 天前将所有信息通过电子邮件发送给 IT 人员,但仍在等待答复。

与此同时,根据此处的信息,最终编写了适用于所有机器的完全不同的代码。它适用于 Windows 7 和 10,它适用于 Excel 2007 和 2010(安装为 32 位)和 Excel 2019(安装为 64 位)。

我在这里添加代码,所以也许有人可以解释为什么它可以正常工作,但看起来问题是 base64 编码。

我现在得到的代码是这样的(添加了对Microsoft Winhttp Setvices 5.1的引用)

Application.ScreenUpdating = False

Dim whr As WinHttp.WinHttpRequest
Dim oStream As Object
Dim EsteCSV As String
Dim EstaURL As String


EstaURL = "https://user:pass@www.privatewebsite.com/export/target%20file.csv" 
EsteCSV = "CSV Datos" & Format(Date, "dd-mm-yyyy") & ".csv"

'Set whr = CreateObject("WinHttp.WinHttpRequest.5.1")

Set whr = New WinHttp.WinHttpRequest

whr.Open "GET", EstaURL, True
whr.setRequestHeader "Authorization", "Basic " & EncodeBase64("user" & ":" & "password")
whr.send

' Using 'true' above and the call below allows the script to remain responsive.
whr.waitForResponse
DoEvents

Set oStream = CreateObject("ADODB.Stream")
oStream.Open
oStream.Type = 1
oStream.Write whr.responseBody
oStream.SaveToFile ThisWorkbook.Path & "\" & EsteCSV
oStream.Close
DoEvents

Set oStream = Nothing
whr.abort
Set whr = Nothing
'rest of code for operations

Kill ThisWorkbook.Path & "\" & EsteCSV

Application.ScreenUpdating = True

End Sub

Private Function EncodeBase64(text As String) As String
  Dim arrData() As Byte
  arrData = StrConv(text, vbFromUnicode)

  Dim objXML As Object
  Dim objNode As Object

  Set objXML = CreateObject("MSXML2.DOMDocument")
  Set objNode = objXML.createElement("b64")

  objNode.DataType = "bin.base64"
  objNode.nodeTypedValue = arrData
  EncodeBase64 = objNode.text

  Set objNode = Nothing
  Set objXML = Nothing
End Function

【问题讨论】:

标签: excel vba


【解决方案1】:

子代码很好。检查vba中工具菜单中的引用并声明ptrsafe如下

Private Declare PtrSafe Function URLDownloadToFileA Lib "urlmon" _

【讨论】:

  • 感谢您的回答,因为文件是一样的,所以参考是好的。还尝试声明Private Declare PtrSafe Function URLDownloadToFileA Lib "urlmon_" ,如我的帖子中所述,但这并不能解决问题。
  • Option Explicit 后声明不是 PTRSAFE
  • 在我发布的代码中,不,没有 PtrSafe。但是在我用 Excel 2019 和 W10 在 PC 上测试的尝试中,是的,代码是使用 PtrSafe 的,但正如我所说,这并没有解决问题。
【解决方案2】:

执行此代码的计算机都是 Windows 7 和 Excel 2007 和 64 位。他们都没有失败。

但是现在,这项任务将外包给另一个办公室。那里, 计算机是 Excel 2019、Windows 10 和 64 位。

并且代码在那里不起作用。它不会出现任何错误,但 函数 DownloadUrlFile 不会在 Excel 2019 上下载任何文件 + W10

我猜它不在另一个办公室工作。

仅当 URL 为私有且 IP 未列入白名单时才会发生这种情况。您可以向您的网络团队查询他们是否已将该 URL 的 IP 列入白名单。

表示 URLDownloadToFile 0 的行, "https://tutorialesexcel.com/wp-content/uploads/2018/10/funciones.xlsm", ThisWorkbook.Path & "\" & EsteCSV, 0, 完美运行并下载 文件。

行 URLDownloadToFile 0, EstaURL, ThisWorkbook.Path & "\" & EsteCSV, 0, 0 不起作用。

所以再次测试完全相同的代码,但在 Excel 2007 和它们两者上 工作

为什么第一次下载有效而第二次在 Excel 2019 上无效 + W10 但它们都在 Excel 2007+W7 上工作?

此外,相同的代码在公共 URL 上工作得很好,而在私有 URL 上却没有任何意义,除非有 IP 限制。

【讨论】:

  • 感谢您的回答。在其中一个位置,有安装 Excel 2010 的机器和安装 Excel 2019 的其他机器。它们都共享相同的网络和 Internet 网关,因此 IP 相同。而且我最初发布的代码仅适用于那些使用 Excel 2010(32 位)的机器,所以我认为这不是白名单,因为所有机器都有相同的 IP。
【解决方案3】:

至于你的错误,-2147221020 => 0x800401E4 根据VBA Error Codes and Descriptions 此错误是 MK_E_SYNTAX,即“无效的名字对象语法”。

当它说绰号时,我猜这意味着您的网址,老实说,网址看起来语法不正确......

"https://user:token@www.privatewebsite.com/export/targetfile.csv"

我必须四处寻找,看看它是否真正符合 url 的网络标准。与此同时,我建议找出一个不同的网址。可能是升级到 urlmon.dll 现在抱怨 url 而 Windows 7 版本没有。

好吧,我的错,实际上看起来你可以做这样的 uris,理论上,所以我有一个 uri 片段

first-client:noonewilleverguess@localhost:8080/oauth/token取自OAuth2 Boot

好的,所以它是有效的,重新回到第17页的顶部rfc3986

authority = [ userinfo "@" ] host [ ":" port ]

看来您必须通过 Windows API 调用来设置用户名和密码。所以这里是示例代码

Option Explicit

'* with thanks to http://www.holmessoft.co.uk/homepage/WininetVB.htm

Private Declare Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _
    ByVal lpszAgent As String, _
    ByVal dwAccessType As Long, _
    ByVal lpszProxyName As String, _
    ByVal lpszProxyBypass As String, _
    ByVal dwFlags As Long) As Long


Private Enum InternetOpenAccessTypes
    INTERNET_OPEN_TYPE_PRECONFIG = 0 'Retrieves the proxy or direct configuration from the registry.
    INTERNET_OPEN_TYPE_DIRECT = 1 'Resolves all host names locally.
    INTERNET_OPEN_TYPE_PROXY = 3 'Passes requests to the proxy unless a proxy bypass list is supplied and the name to be resolved bypasses the proxy. In this case, the function uses INTERNET_OPEN_TYPE_DIRECT.
    INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4 'Retrieves the proxy or direct configuration from the registry and prevents the use of a startup Microsoft JScript or Internet Setup (INS) file.
End Enum


Private Declare Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _
    ByVal hInternetSession As Long, _
    ByVal lpszServerName As String, _
    ByVal nServerPort As Integer, _
    ByVal lpszUsername As String, _
    ByVal lpszPassword As String, _
    ByVal dwService As Long, _
    ByVal dwFlags As Long, _
    ByVal dwContext As Long) As Long

Private Const INTERNET_SERVICE_HTTP = 3

Private Const INTERNET_FLAG_NO_COOKIES = &H80000
Private Const INTERNET_FLAG_NO_CACHE_WRITE = &H4000000

Private Declare Function HttpOpenRequest Lib "wininet.dll" Alias "HttpOpenRequestA" ( _
    ByVal hHttpSession As Long, _
    ByVal lpszVerb As String, _
    ByVal lpszObjectName As String, _
    ByVal lpszVersion As String, _
    ByVal lpszReferer As String, _
    ByVal lpszAcceptTypes As String, _
    ByVal dwFlags As Long, _
    ByVal dwContext As Long) As Long

Private Declare Function HttpSendRequest Lib "wininet.dll" Alias "HttpSendRequestA" ( _
    ByVal hHttpRequest As Long, _
    ByVal lpszHeaders As String, _
    ByVal dwHeadersLength As Long, _
    ByVal lpOptional As String, _
    ByVal dwOptionalLength As Long) As Boolean

Private Declare Function InternetReadFile Lib "wininet.dll" ( _
    ByVal hFile As Long, _
    ByVal lpBuffer As String, _
    ByVal dwNumberOfBytesToRead As Long, _
    ByRef lpNumberOfBytesRead As Long) As Boolean

Private Sub Test()
    Dim hInternet As Long
    hInternet = InternetOpen("Mozilla", INTERNET_OPEN_TYPE_DIRECT, vbNullString, vbNullString, 0)
    If hInternet = 0 Then
        Debug.Print "InternetOpen failed"
        GoTo SingleExit
    End If

    Dim sUSERNAME As String
    sUSERNAME = "foo"

    Dim sPASSWORD As String
    sPASSWORD = "bar"


    Dim hConnect As Long
    hConnect = InternetConnect(hInternet, "www.microsoft.com", 80, sUSERNAME, sPASSWORD, INTERNET_SERVICE_HTTP, 0, 0)
    If hConnect = 0 Then
        Debug.Print "InternetConnect failed"
        GoTo SingleExit
    End If

    Dim lFlags As Long
    Dim hRequest As Long

    lFlags = INTERNET_FLAG_NO_COOKIES
    lFlags = lFlags Or INTERNET_FLAG_NO_CACHE_WRITE

    hRequest = HttpOpenRequest(hConnect, "GET", "www.microsoft.com", "HTTP/1.0", vbNullString, vbNullString, lFlags, 0)

    Dim bRes As Boolean
    bRes = HttpSendRequest(hRequest, vbNullString, 0, vbNullString, 0)

    Dim strFile As String
    strFile = "downloadedfile.txt"

    Dim strBuffer As String * 1
    Dim strDir As String
    strDir = Dir(ThisWorkbook.Path & "\" & strFile)
    If Len(strDir) > 0 Then
        Kill ThisWorkbook.Path & "\" & strFile
    End If

    Dim iFile As Long
    iFile = FreeFile()
    Open ThisWorkbook.Path & "\" & strFile For Binary Access Write As iFile

    Do
        Dim lBytesRead As Long
        bRes = InternetReadFile(hRequest, strBuffer, Len(strBuffer), lBytesRead)
        If lBytesRead > 0 Then
            Put iFile, , strBuffer
        End If
    Loop While lBytesRead > 0

    Debug.Print "finished"
SingleExit:


End Sub

更新:恭喜你邀请解释的解决方案,也许看到这个MSDN Forum,其中的论述概述了不同的技术堆栈。如果我浏览 C++ 头文件urlmon.h,那么URLDownloadToFile 看起来像是基于WinInet。因此,切换到 WinHTTP 是向更基于服务器的堆栈的明智之举。
此外,在相同的堆栈逻辑上,我相信您可以使用 MSXML2.ServerXMLHTTP 看到这个VBScript newsgroup archive

【讨论】:

  • 我也会检查一下。现在我们找到了一个使用 httprequest 的解决方案,但是将用户名和密码编码为 Base64,如下所示:url = "https://" & EncodeBase64("user" & ":" & "token" & "@www.privatewebsite.com/export/target%20file.csv",这很有效。 EncodeBase64 的代码可以在这里找到:stackoverflow.com/a/169945/9199828
  • 使用工作代码编辑原始问题,以防您想检查解决方案。我有点惊讶为什么它会这样工作,但至少它可以按预期工作。
  • 好的,干得好。使用 WinHttpRequest 比使用 Windows API 更好。
  • 由于完成了所有调查,并且因为它有助于建立我自己的答案,因此我奖励了 500 分。此外,它提供了很多有用的信息。谢谢!
  • @FoxfireAndBurnsAndBurns 干杯兄弟!我保证先付款。
【解决方案4】:

我在 Excel 2019 / O365 64 bits (version: 1912) / win 10 64 bits 中试过这个解决方案

我知道你有一个有效的代码,但如果其他人需要替代代码,这里是:

Sub DownloadFile()

    Dim evalURL As String
    Dim streamObject As Object
    Dim winHttpRequest As Object
    Set winHttpRequest = CreateObject("Microsoft.XMLHTTP")

    evalURL = "https://fullPathTofile/tst.csv" ' -> Didn't need to add the username at the beginning

    winHttpRequest.Open "GET", evalURL, False, "username", "password"
    winHttpRequest.send

    If winHttpRequest.Status = 200 Then
        Set streamObject = CreateObject("ADODB.Stream")
        streamObject.Open
        streamObject.Type = 1
        streamObject.Write winHttpRequest.responseBody
        streamObject.SaveToFile "C:\temp\file.csv", 2 ' 1 = no overwrite, 2 = overwrite
        streamObject.Close
    End If

End Sub

【讨论】:

  • 这正是我 9 小时前在我的问题中发布的代码。几乎是一样的。
【解决方案5】:

您使用的“简化”方法(用户+密码@url)充其量只能得到少量支持,因为它可能会被远程滥用。 Some browsers no longer even support it.

例如,...admin:admin@192.168.1.1/cgi-bin/something-else?... 的 HREF 链接足以利用几个仅受“拒绝远程访问”默认值而不是可靠密码保护的路由器,并且有 很多他们中的一个。

可能可以通过将用户名和密码保存在 Excel 使用的 Internet Explorer 中的用户和密码,和/或将远程站点从 Internet 选项中放入“受信任的站点”组中来解决此问题.但这也是权宜之计,因为密码缓存可能会被意外删除,并且安全级别可能会随时通过更新重置(我不止一次遇到过这种情况)。

Here还有其他方法讨论。否则,您的解决方案当然有效(您可能希望添加一个答案,并将其标记为已接受以供下一个遇到相同问题的人使用)。

【讨论】:

    【解决方案6】:

    感谢大家的帮助和回答。不幸的是,我的 IT 部门无法告诉我到底发生了什么,即使这里提供的所有链接都包含很多有用的信息。

    我在这里发布我们现在在这里使用的代码。它在 Excel 2007 32 位、Excel 2010 32 和 64 位以及 Excel 2019 64 位上完美运行。它也适用于 Windows 7 和 10。

    要使此代码正常工作,您需要添加对Microsoft Winhttp Setvices 5.1 的引用。如果您不知道如何操作,请查看How to Add an Object Library Reference in VBA

    Application.ScreenUpdating = False
    
    Dim whr As WinHttp.WinHttpRequest
    Dim oStream As Object
    Dim EsteCSV As String
    Dim EstaURL As String
    
    
    EstaURL = "https://user:pass@www.privatewebsite.com/export/target%20file.csv" 
    EsteCSV = "CSV Datos" & Format(Date, "dd-mm-yyyy") & ".csv"
    
    'Set whr = CreateObject("WinHttp.WinHttpRequest.5.1")
    
    Set whr = New WinHttp.WinHttpRequest
    
    whr.Open "GET", EstaURL, True
    whr.setRequestHeader "Authorization", "Basic " & EncodeBase64("user" & ":" & "password")
    whr.send
    
    ' Using 'true' above and the call below allows the script to remain responsive.
    whr.waitForResponse
    DoEvents
    
    Set oStream = CreateObject("ADODB.Stream")
    oStream.Open
    oStream.Type = 1
    oStream.Write whr.responseBody
    oStream.SaveToFile ThisWorkbook.Path & "\" & EsteCSV
    oStream.Close
    DoEvents
    
    Set oStream = Nothing
    whr.abort
    Set whr = Nothing
    'rest of code for operations
    
    Kill ThisWorkbook.Path & "\" & EsteCSV
    
    Application.ScreenUpdating = True
    
    End Sub
    
    Private Function EncodeBase64(text As String) As String
      Dim arrData() As Byte
      arrData = StrConv(text, vbFromUnicode)
    
      Dim objXML As Object
      Dim objNode As Object
    
      Set objXML = CreateObject("MSXML2.DOMDocument")
      Set objNode = objXML.createElement("b64")
    
      objNode.DataType = "bin.base64"
      objNode.nodeTypedValue = arrData
      EncodeBase64 = objNode.text
    
      Set objNode = Nothing
      Set objXML = Nothing
    End Function
    

    再次感谢大家。 SO是个好地方。

    【讨论】:

      【解决方案7】:

      我为此苦苦挣扎了好几天,直到我发现它可以在一行 PowerShell 中完成,例如:

      invoke-webrequest -Uri "http://myserver/Reports/Pages/ReportViewer.aspx?%2fClients%2ftest&rs:Format=PDF&rs:ClearSession=true&CaseCode=12345678" -OutFile "C:\Temp\test.pdf" -UseDefaultCredentials
      

      我原本打算完全在 VBA 中执行此操作,但它会运行到好几页,让我失去理智,所以每次我想下载文件时,我只需从 VBA 调用我的 PowerShell 脚本。

      非常非常简单,“UseDefaultCredentials”绝对是一种享受,我不必担心登录远程站点等。

      这让我可以用几行代码从 SSRS 下载 PDF 格式的报告到一个文件夹中。

      【讨论】:

        猜你喜欢
        • 2011-06-23
        • 2023-03-12
        • 1970-01-01
        • 1970-01-01
        • 2021-10-27
        • 2018-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多