PAYATHON 2026

Google Places API Nearby Search 未返回结果

数据小许

结论

问题出在请求 URL 的构造上。代码将纬度写成了字符串常量 "latitude",没有使用函数参数 latitude。

当前代码实际发送的坐标类似:

location=latitude,-122.6628821

正确的坐标应该是:

location=45.5662453,-122.6628821

Google Places API 因此收到了无效坐标。XML 解析路径 //status 和 //result/name 通常适用于该响应结构,不是问题的主要原因。

修正请求构造

将:

"location=" & "latitude" & "," & longitude

改为:

"location=" & latitude & "," & longitude

最小修改如下:

xmlhttpRequest.Open "GET", _
    "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?" & _
    "location=" & latitude & "," & longitude & _
    "&radius=" & Radius & _
    "&type=park" & _
    "&key=" & apiKey, _
    False

latitude 不能放在双引号内。双引号中的内容是固定文本,不会被当作变量名解析。

更稳妥的实现

VBA 将 Double 转换成字符串时,会受到 Windows 区域设置的影响。某些区域使用逗号作为小数分隔符,生成的内容可能如下:

location=45,5662453,-122,6628821

这样的坐标同样无效。可以显式将小数分隔符替换为英文句点:

Public Function GetNearbyPark( _
    ByVal latitude As Double, _
    ByVal longitude As Double, _
    ByVal Radius As Long) As String

    Dim apiKey As String
    Dim requestUrl As String
    Dim latitudeText As String
    Dim longitudeText As String
    Dim xmlhttpRequest As Object
    Dim xmlDoc As Object
    Dim xmlStatusNode As Object
    Dim xmlNearbyParkNameNode As Object

    On Error GoTo errorHandler

    apiKey = "YOUR_API_KEY"

    If Len(apiKey) = 0 Or apiKey = "YOUR_API_KEY" Then
        GetNearbyPark = "Empty or invalid API Key"
        Exit Function
    End If

    latitudeText = Replace$(CStr(latitude), _
        Application.International(xlDecimalSeparator), ".")
    longitudeText = Replace$(CStr(longitude), _
        Application.International(xlDecimalSeparator), ".")

    requestUrl = _
        "https://maps.googleapis.com/maps/api/place/nearbysearch/xml?" & _
        "location=" & latitudeText & "," & longitudeText & _
        "&radius=" & CStr(Radius) & _
        "&type=park" & _
        "&key=" & apiKey

    Debug.Print requestUrl

    Set xmlhttpRequest = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    xmlhttpRequest.Open "GET", requestUrl, False
    xmlhttpRequest.send

    If xmlhttpRequest.Status <> 200 Then
        GetNearbyPark = "HTTP error: " & xmlhttpRequest.Status & _
                        " " & xmlhttpRequest.statusText
        GoTo cleanUp
    End If

    Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
    xmlDoc.async = False
    xmlDoc.validateOnParse = False

    If Not xmlDoc.LoadXML(xmlhttpRequest.responseText) Then
        GetNearbyPark = "Invalid XML response: " & _
                        xmlDoc.parseError.reason
        GoTo cleanUp
    End If

    Set xmlStatusNode = xmlDoc.SelectSingleNode("/PlaceSearchResponse/status")

    If xmlStatusNode Is Nothing Then
        GetNearbyPark = "The response does not contain a status node"
        GoTo cleanUp
    End If

    Select Case UCase$(xmlStatusNode.Text)
        Case "OK"
            Set xmlNearbyParkNameNode = _
                xmlDoc.SelectSingleNode("/PlaceSearchResponse/result[1]/name")

            If xmlNearbyParkNameNode Is Nothing Then
                GetNearbyPark = "The response contains no park name"
            Else
                GetNearbyPark = xmlNearbyParkNameNode.Text
            End If

        Case "ZERO_RESULTS"
            GetNearbyPark = _
                "No park exists within the radius of the defined coordinates"

        Case "OVER_DAILY_LIMIT"
            GetNearbyPark = "Billing, API key, or usage limit problem"

        Case "OVER_QUERY_LIMIT"
            GetNearbyPark = "Quota limit exceeded"

        Case "REQUEST_DENIED"
            GetNearbyPark = "Server denied the request"

        Case "INVALID_REQUEST"
            GetNearbyPark = "Request was empty or malformed"

        Case "UNKNOWN_ERROR"
            GetNearbyPark = "Unknown server error"

        Case Else
            GetNearbyPark = "API status: " & xmlStatusNode.Text
    End Select

cleanUp:
    Set xmlNearbyParkNameNode = Nothing
    Set xmlStatusNode = Nothing
    Set xmlDoc = Nothing
    Set xmlhttpRequest = Nothing
    Exit Function

errorHandler:
    GetNearbyPark = "VBA error " & Err.Number & ": " & Err.Description
    Resume cleanUp
End Function

XML 解析是否有问题

下面的 XPath 可以取得第一个结果的名称:

xmlDoc.SelectSingleNode("//result/name")

使用完整路径会更明确:

xmlDoc.SelectSingleNode("/PlaceSearchResponse/result[1]/name")

需要补强的是空节点检查。请求失败、响应格式改变或没有结果时,SelectSingleNode 可能返回 Nothing。如果此时直接访问:

xmlStatusNode.Text

或:

xmlNearbyParkNameNode.Text

就会触发对象变量错误。原代码虽然使用了 On Error GoTo errorHandler,但错误处理程序没有将错误信息赋给函数返回值,所以表面上看起来像是“没有返回结果”。

排查方法

先输出 VBA 实际发送的完整 URL:

Debug.Print requestUrl

将输出内容复制到浏览器中测试,并逐项确认:

  • location 中是实际数值,不是单词 latitude;
  • 纬度和经度之间只有一个逗号;
  • 小数分隔符是 .;
  • HTTP 状态码为 200;
  • XML 中的 <status> 是 OK;
  • Places API 已启用,项目的结算、配额和 API key 限制也已正确配置。

安全注意事项

问题中公开了真实 API key,应将该密钥视为已经泄露。建议立即在 Google Cloud Console 中撤销或轮换密钥,并为新密钥设置适当的 API 限制和应用限制。不要继续在公开代码、日志或网页中使用该密钥。

备注:内容仅供参考。