模块产生#VALUE

时间:2018-07-13 13:58:49

标签: excel vba excel-vba web-scraping

在外部资源的帮助下,我获得了此代码,该代码可以获取经度和纬度并提取邮政编码。这是代码:

Public Function ReverseGeoCode(myInput1 As String, myInput2 As String) As String

'You will need to reference Microsoft XML, v6.0 object library

    Dim XMLDoc As New DOMDocument60
    Dim XMLNODE As IXMLDOMNode
    Dim I As Long
    Dim lat, lng, myAddress, myZipcode, reportZipcode As String
    Dim splitAddress, splitZipcode As Variant

    lat = myInput1
    lng = myInput2

    XMLDoc.Load "http://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & lng & " &sensor=false"

    Do Until XMLDoc.readyState = 4
        DoEvents
    Loop

    If Len(XMLDoc.Text) = 0 Then
        Call MsgBox("No Data!")
        Exit Function
    End If

    Set XMLNode = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")

    For i= 0 To XMLNode.ChildNodes.Length - 1
        myAddress = XMLNode.ChildNodes(i).Text
    Next i

    splitAddress = Split(myAddress, ",")
    myZipcode = splitAddress(2)
    myZipcode = Trim(myZipcode)
    splitZipcode = Split(myZipcode, " ")
    reportZipcode = splitZipcode(1)
    reportZipcode = Trim(reportZipcode)

    ReverseGeoCode = reportZipcode

End Function

因此该代码有效,而且我知道它可能不是最干净的。但是问题是当我在Excel工作表中使用“ = ReverseGeoCode(Cell1,Cell2)”调用它时。有时它可以正常工作,而其他时候它会产生返回“ #VALUE!”。我不确定为什么。我在下面附上一张图片,向您显示该错误的示例。有谁知道为什么会产生此错误?(Image of Error in Excel Sheet)

1 个答案:

答案 0 :(得分:1)

一般观察

因此,请按照我在评论中所写的内容进行概述。

您不想使用用户定义函数。这样可以继续进行重复通话。您肯定会冒险在没有API密钥的情况下(可能有)达到API的调用限制;它效率低下,没有必要。而是编写一个您调用一次的子程序,该子程序将循环工作表中的所有必需单元格,并发出API调用并返回邮政编码。 API密钥是一种用于许多API调用的身份验证方法。您不应该顺便分享一下。

这些重复调用,可能会达到极限,而经常计算UDF的事实可能是您麻烦的根源。

出于效率考虑,首先从工作表中删除重复项,以避免不必要的呼叫。切换屏幕更新和其他任何方式,例如在执行时将CalculationMode设置为手动。

据我所读,一旦达到每日限额,您就需要一个API密钥。不确定免费版本或没有API密钥的API限制是什么。


概述代码(带有一些伪代码的XML请求):

  Option Explicit
    Public Sub ListZipCodes()
        Dim lat As Double, longitude As Double
        Const APIKEY As String = "yourAPIkey"
        Application.ScreenUpdating = False           '<==Speed up code when actually working with sheet
        'Code to remove duplicates
        'Code to loop sheet and call function on each input set of values

        'Example call. These would be picked up from cells
        lat = 40.714224
        longitude = -73.961452

        Debug.Print GetZipCode(lat, longitude, APIKEY)

        Application.ScreenUpdating = True
    End Sub

    Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
        Dim sResponse As String
        With CreateObject("MSXML2.XMLHTTP")
            Dim URL As String
            URL = "https://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & longitude & "&key=" & APIKEY
            .Open "GET", URL, False
            .send
            If .Status <> 200 Then 
                GetZipCode = "API call failed"
                Exit Function
            End If
            Dim XMLDoc As New DOMDocument60, XMLNODE As IXMLDOMNode

            XMLDoc.Load .responseBody
            If Len(XMLDoc.Text) = 0 Then GetZipCode = "No data"

            Set XMLNODE = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
            GetZipCode = Split(Trim$(Split(XMLNODE.Text, Chr$(44))(2)), Chr$(32))(1)
        End With
    End Function

请求JSON而不是XML响应:

调用JSON失败的原因是响应需要解码。这是重写为处理JSON响应的函数。

这需要下载JSONConverter,然后通过VBE>工具>引用导入并添加对Microsoft脚本运行时的引用。

下面的示例使用

运行
latitude: 42.9865913391113, 
longitude: -100.137954711914

VBA:

Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
    Dim sResponse As String, json As Object
    With CreateObject("MSXML2.XMLHTTP")
        Dim URL As String, formattedAddress As String
        URL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & lat & "," & longitude & "&key=" & APIKEY
        .Open "GET", URL, False
        .send
        If .Status <> 200 Then 
            GetZipCode = "API call failed"
            Exit Function
        End If
        Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode))

        formattedAddress = json("results").item(1)("formatted_address")
        GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
    End With
End Function

对于JSON请求,您返回的初始对象是字典,如解码响应中的开头"{"所示:

JSON Object

Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode))返回字典对象

从上面您可能会发现,字典中的感兴趣数据具有键"results"

可以使用json("results")来访问它,它返回词典的集合。这由以下"["表示,以供收集,随后由集合中第一个词典的开头表示,再次由"{"表示。

Collection of dictionaries

我可以使用以下索引按索引获取集合中的第一本字典:

json("results").item(1)

对该词典中的键进行检查后发现,其中一个键就是我们所追求的"formatted_address"

formatted_address key

它的关联值是原始数据类型;在这种情况下为字符串。这意味着我们可以使用键直接访问它(不返回另一个对象)。

formattedAddress = json("results").item(1)("formatted_address")

现在我们有了地址字符串,我们可以像以前一样解析它:

GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)

尾注:

您可以使用Postman等工具来测试API调用,在这种情况下,请检查JSON响应。确实,要看看您将获得什么样的回应。


帮助:

建立项目,生成API密钥并开始使用非常快捷,容易。可能需要10分钟才能通读并执行。

  1. Instructions on setting up a project and getting an API key
  2. Enabling the API
  3. Understanding how to make API calls to the Geocoding API