使用 Pollen API 网络服务的最佳实践

Google Maps Platform 网络服务是一系列 Google 服务的 HTTP 接口,这些服务可以为您的地图应用提供地理数据。

本指南介绍了一些有用的做法,可用于设置 Web 服务请求和处理服务响应。如需查看 Pollen API 的完整文档,请参阅开发者指南

什么是网络服务?

Google Maps Platform Web 服务是一个接口,可用于从外部服务请求 Maps API 数据,并在您的 Google 地图应用中使用这些数据。这些服务旨在根据 Google Maps Platform 服务条款中的许可限制与地图结合使用。

Maps API Web 服务使用 HTTP(S) 请求向特定网址发出请求,并将网址参数和/或 JSON 格式的 POST 数据作为参数传递给服务。一般而言,这些服务会以 JSON 格式在响应正文中返回数据,以供您的应用进行解析和/或处理。

以下示例展示了向 forecast:lookup 方法发出的 REST GET 请求的网址:

https://pollen.googleapis.com/v1/forecast:lookup?&key=API_KEY

注意:所有 Pollen API 应用都需要进行身份验证。详细了解身份验证凭据

SSL/TLS 访问

对于使用 API 密钥或包含用户数据的所有 Google Maps Platform 请求,都必须采用 HTTPS 协议。通过 HTTP 发起并包含敏感数据的请求将被拒绝。

构建有效网址

您可能认为“有效”网址不言自明,但实际并非如此。例如,在浏览器地址栏中输入的网址可能包含特殊字符(例如 "上海+中國");浏览器需要先在内部将这些字符转换为其他编码,然后再进行传输。同样,任何生成或接受 UTF-8 输入的代码都可能会将包含 UTF-8 字符的网址视为“有效”,但同样需要先转换这些字符,然后再将其发送给网络服务器。该过程称为网址编码百分号编码

特殊字符

我们之所以需要转换特殊字符,是因为所有网址都需要符合统一资源标识符 (URI) 规范所规定的语法。实际上,这意味着网址必须只包含一个特殊的 ASCII 字符子集:大家熟悉的字母数字符号以及一些在网址内用作控制字符的预留字符。下表汇总了这些字符:

有效网址字符汇总
字符集字符在网址中的用法
字母数字 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 文本字符串、在 scheme 中使用 (http)、端口 (8080) 等
非预留字符 - _ . ~ 文本字符串
预留字符 ! * ' ( ) ; : @ & = + $ , / ? % # [ ] 控制字符和/或文本字符串

构建有效网址时,您必须确保网址只包含下表中显示的那些字符。让网址按照上述字符集使用字符通常会带来两个问题,一个是遗漏问题,一个是替换问题:

  • 您要处理的字符未包含在上述字符集内。举例来说,非英语字符(例如 上海+中國)需要使用上述字符进行编码。按照常见惯例,空格(网址内不允许使用空格)通常也使用加号字符 '+' 表示。
  • 字符在上述字符集内存在且属于预留字符,但需要按原义使用。例如,? 在网址内用于表示查询字符串的开头;如果您想要使用字符串“? and the Mysterions”,则需要对 '?' 字符进行编码。

所有要进行网址编码的字符都会使用一个 '%' 字符和一个与其 UTF-8 字符对应的双字符十六进制值进行编码。例如,UTF-8 中的 上海+中國 在进行网址编码后将变为 %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B。字符串 ? and the Mysterians 在进行网址编码后将变为 %3F+and+the+Mysterians%3F%20and%20the%20Mysterians

需要编码的常见字符

以下是一些必须进行编码的常见字符:

不安全的字符 编码后的值
空格 %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

转换您通过用户输入获取的网址有时颇为棘手。例如,用户可能会输入“5th&Main St.”这样的地址。一般而言,您应该根据网址的组成部分来构建网址,将所有用户输入都视为原义字符。

此外,对于所有 Google Maps Platform 网络服务 API 和静态网络 API,网址不得超过 16384 个字符。对于大多数服务,很少出现接近这一字符数限制的情况。但请注意,某些服务具有的若干参数可能会导致网址较长。

合理使用 Google API

设计拙劣的 API 客户端可能会给网络和 Google 服务器增加额外负载。本部分包含适用于 API 客户端的一些最佳实践。遵循这些最佳实践有助于您的应用避免因无意中滥用 API 而遭到屏蔽。

指数后退

在极少数情况下,您的请求可能会在传送时出错。您可能会收到 4XX 或 5XX HTTP 响应代码,或者 TCP 连接可能会在客户端与 Google 服务器之间的某处出现错误。通常情况下,重试请求值得一试。首次请求失败后,后续请求可能会成功。不过,也要确保不能单纯地向 Google 服务器反复发起请求。这种循环行为可能会使客户端与 Google 之间的网络过载,给多方造成问题。

较好的方法是不断增加两次重试之间的延迟。通常,两次尝试之间的间隔以固定乘积因子为幅度增加,这种方法称为指数后退

例如,假设一个应用希望向 Time Zone API 发出以下请求:

https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510&timestamp=1331161200&key=YOUR_API_KEY

下面的 Python 示例显示了如何通过指数后退发起请求:

import json
import time
import urllib.error
import urllib.parse
import urllib.request

# The maps_key defined below isn't a valid Google Maps API key.
# You need to get your own API key.
# See https://developers.google.com/maps/documentation/timezone/get-api-key
API_KEY = "YOUR_KEY_HERE"
TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json"


def timezone(lat, lng, timestamp):

    # Join the parts of the URL together into one string.
    params = urllib.parse.urlencode(
        {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,}
    )
    url = f"{TIMEZONE_BASE_URL}?{params}"

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 5  # Set the maximum retry delay to 5 seconds.

    while True:
        try:
            # Get the API response.
            response = urllib.request.urlopen(url)
        except urllib.error.URLError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.load(response)

            if result["status"] == "OK":
                return result["timeZoneId"]
            elif result["status"] != "UNKNOWN_ERROR":
                # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
                # ZERO_RESULTS. There is no point retrying these requests.
                raise Exception(result["error_message"])

        if current_delay > max_delay:
            raise Exception("Too many retry attempts.")

        print("Waiting", current_delay, "seconds before retrying.")

        time.sleep(current_delay)
        current_delay *= 2  # Increase the delay each time we retry.


if __name__ == "__main__":
    tz = timezone(39.6034810, -119.6822510, 1331161200)
    print(f"Timezone: {tz}")

您还应注意,应用调用链中并没有更高的重试代码可导致快速连续地重复请求。

同步请求

向 Google API 发起大量同步请求在行为上类似于对 Google 基础设施的分布式拒绝服务 (DDoS) 攻击,我们也将据此采取相应处理措施。为避免这种情况,请确保避免在客户端之间同步发起 API 请求。

例如,假设某应用显示当前所在时区的时间。此应用可能会在客户端操作系统中设置警报,并在分钟开始时唤醒该操作系统,以便更新显示时间。在相关报警的处理过程中,应用应发起任何 API 调用。

但通过 API 调用来响应固定警报的做法并不妥当,因为这会导致 API 调用与分钟开始时间同步(即使在不同设备之间亦是如此),而非在一段时间内均匀分布。设计拙劣的应用就会这样操作,导致每分钟开始时出现流量尖峰,达到正常水平的六倍之多。

相较之下,我们可以采用一种较好的设计,即将第二个警报设为随机选择的时间。当第二个报警触发时,应用将调用它需要的任何 API 并存储结果。如果应用想要在分钟开始时更新其显示画面,它会使用之前的存储值,而不是再次调用 API。通过这种方式,API 调用将在一段时间内平均分配。此外,在更新显示画面时,API 调用也不会使显示延迟。

除了分钟开始之外,您应该注意不以分钟为目标的其他常用同步时间,包括小时开始和每天开始的午夜。

处理响应

此部分介绍如何以动态方式从 Web 服务响应中提取这些值。

Google 地图网络服务提供的响应虽易于理解,但并非完全易用。执行查询时,您的目的很可能是提取几个具体的值,而不是显示一组数据。一般而言,您的目的是解析 Web 服务的响应,只提取您感兴趣的那些值。

您使用的解析架构取决于您返回输出采用的是 JSON 格式。由于 JSON 响应已经采用了 Javascript 对象形式,因此可在客户端上在 Javascript 自身内处理。