使用 Places API 網路服務的最佳做法

Google 地圖平台網路服務集合了 Google 的 HTTP 介面 為您的地圖應用程式提供地理資料的服務。

本指南說明一些常見的設定做法, Web 服務 要求及處理服務回應請參閱開發人員指南 查看 Places API 的完整說明文件。

什麼是網路服務?

Google 地圖平台網路服務是一種用來要求 Maps API 資料的介面 並在地圖應用程式中使用資料。這些服務 設計為與地圖搭配使用,如 授權限制

Maps API 網路服務使用對特定網址的 HTTP(S) 要求,並傳遞網址參數和/或 JSON 格式的 POST 資料做為服務的引數。一般而言,這些服務會在 用來剖析的回應內文 (JSON 或 XML) 和/或應用程式處理的流程

典型的 Places API 要求通常是 以下表單:

https://places.googleapis.com/v1/places/PLACE_ID?parameters

注意:所有 Places API 應用程式都需要驗證。 進一步瞭解驗證憑證

SSL/TLS 存取

凡是使用 API 金鑰或包含使用者的 Google 地圖平台要求,一律必須採用 HTTPS 資料。如果透過 HTTP 傳送包含機密資料的要求,可能會遭到拒絕。

建立有效網址

您可能認為網址是否「有效」一眼就能判斷,但實際情況不然。例如,在瀏覽器的網址列內輸入的網址可能包含特殊字元 (例如 "上海+中國");瀏覽器必須在內部將這些字元轉譯為其他編碼方式才能傳送。同理可證,產生或接受 UTF-8 輸入值的任何程式碼都可能會將含有 UTF-8 字元的網址視為「有效網址」,但也需要先轉譯這些字元,才能向外傳送至網路伺服器。這個過程稱為網址編碼百分比編碼

特殊字元

所有網址都必須符合統一資源 ID (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 文字字串、結構用途 (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 地圖平台網路服務的網址長度上限都是 16384 個字元 以及靜態網路 API對於大部分的服務而言,很少出現接近此字元限制的情況。但請注意,某些服務的幾個參數可能會產生較長的網址。

謹慎使用 Google API

設計不良的 API 用戶端,可能承載於網際網路和網際網路上的負載 Google 伺服器。本節包含 API 用戶端的一些最佳做法。追蹤中 這些最佳做法,有助於避免您因為無意間濫用 並嚴謹測試及提升 API 的公平性後 我們才能放心地推出 API

指數型退讓

在極少數情況下,您的要求可能發生錯誤。您可能會收到 4XX 或 5XX HTTP 否則 TCP 連線可能會在用戶端和 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 呼叫 因為就算是在不同裝置之間同步 平均分配時間如果應用程式的設計不良, 每分鐘開始時的正常流量為正常 60 倍

相反的,可以考慮將第二個鬧鐘設定為隨機選擇的時間。 當第二個鬧鐘觸發時,應用程式會呼叫其所需的任何 API,然後儲存 也就是預測結果當應用程式想要在一開始就更新顯示畫面時,會使用 不必再次呼叫 API使用這個方法時,API 呼叫 而是在一段時間內平均分配此外,當顯示位於以下位置時,API 呼叫不會延遲轉譯 正在更新。

除了每一刻開始,也應特別留意其他常見的同步處理時間 「不」在指定小時開始時是每天午夜,而從每天午夜開始。

處理回應

本章節討論如何從網路服務回應中,以動態方式擷取這些值。

Google 地圖網路服務提供 但不代表使用者容易使用執行查詢時 比起顯示一組資料,您可能會希望 輕鬆分配獎金您通常會希望剖析網路回應 並僅擷取感興趣的值。

使用的剖析配置,取決於您是否傳回 以 XML 或 JSON 格式輸出JSON 回應,目前格式為 JavaScript 物件,可以在 JavaScript 本身內處理 呼叫。 XML 回應應使用 XML 處理器來處理 和 XML 查詢語言來處理 XML 格式內的元素。 我們在 XPath 中採用的 以下範例,因為 XML 處理中通常支援這種語言 程式庫

利用 XPath 處理 XML

XML 是相對成熟的結構化資訊格式, 資料交換雖然不像 JSON、XML 不但支援更多語言,也提供更強大的工具以下項目的代碼: 例如,Java 處理 XML 的方式就內建於 javax.xml 個套件。

處理 XML 回應時, 查詢語言 (例如選擇 XML 文件內的節點) 然後假設元素位於 XML 標記。XPath 是一種語言語法,專門用來描述節點和元素 。XPath 運算式可讓您找出 特定內容。

XPath 運算式

熟悉 XPath 對於開發 健全的剖析配置本節將著重說明 都會透過 XPath 處理,因此您可以 處理多個元素及建構複雜的查詢

XPath 會使用「運算式」選取 XML 內的元素 文件,使用的語法與目錄路徑所用的語法類似。 這些運算式可識別 XML 文件中的元素 樹狀結構,為階層式樹狀結構,與 DOM 類似。 一般來說,XPath 運算式是貪婪 符合提供條件的所有節點。

我們將使用下列抽象 XML 來說明 範例:

<WebServiceResponse>
 <status>OK</status>
 <result>
  <type>sample</type>
  <name>Sample XML</name>
  <location>
   <lat>37.4217550</lat>
   <lng>-122.0846330</lng>
  </location>
 </result>
 <result>
  <message>The secret message</message>
 </result>
</WebServiceResponse>

運算式中的節點選項

XPath 選項會選取節點。根節點 內容涵蓋整份文件您會使用 特殊運算式「/」。請注意 node 不是 XML 文件的頂層節點;其實 位於這個頂層元素上方的 1 個層級,且包含 基礎架構

元素節點代表 XML 中的各項元素 。<WebServiceResponse> 元素 例如,代表在 上述範例服務您可以透過下列任一方法選取個別節點 絕對或相對路徑,以 缺少開頭的「/」字元。

  • 絕對路徑:「/WebServiceResponse/result」 運算式會選取所有 <result> 節點 是「<WebServiceResponse>」的子項 節點。(請注意,這兩項元素都是從根目錄 節點「/」)。
  • 目前內容的相對路徑:運算式 「result」可符合任何 <result> 元素。一般情況下 不必煩惱背景資訊 Service 會透過單一運算式產生預測結果

其中一個運算式可能會因為加法而增加 以雙斜線表示 (「//」) 的萬用字元路徑。 萬用字元表示, 能找出人類介入XPath 運算式「//formatted_address」 例如在目前的文件中,比對所有使用該名稱的節點。 運算式 //viewport//lat 會與全部相符 可追蹤 <viewport><lat> 元素 。

根據預設,XPath 運算式會比對所有元件。您可以在單一層級 運算式來比對特定元素,方法是提供述詞、 並用方括號 ([]) 括住。XPath 運算式「/GeocodeResponse/result[2]」一律會傳回 例如第二個結果

運算式類型
根節點
XPath 運算式:"/"
選取:
    <WebServiceResponse>
     <status>OK</status>
     <result>
      <type>sample</type>
      <name>Sample XML</name>
      <location>
       <lat>37.4217550</lat>
       <lng>-122.0846330</lng>
      </location>
     </result>
     <result>
      <message>The secret message</message>
     </result>
    </WebServiceResponse>
    
絕對路徑
XPath 運算式:"/WebServiceResponse/result"
選取:
    <result>
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    </result>
    <result>
     <message>The secret message</message>
    </result>
    
含萬用字元的路徑
XPath 運算式:"/WebServiceResponse//location"
選取:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
含述詞的路徑
XPath 運算式:"/WebServiceResponse/result[2]/message"
選取:
    <message>The secret message</message>
    
第一個 result 的所有直接子項
XPath 運算式:"/WebServiceResponse/result[1]/*"
選取:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
通知的 name resulttype文字為「sample」。
XPath 運算式:"/WebServiceResponse/result[type/text()='sample']/name"
選取:
    Sample XML
    

請務必注意 在選取元素、節點和節點時 而不只是這些物件中的文字一般而言, 會疊代所有符合的節點並擷取文字個人中心 也可能直接與文字節點比對請參閱文字節點

請注意,XPath 也支援屬性節點。不過 所有的 Google 地圖網路服務提供的元素都不會有屬性,因此 不需要比對屬性

運算式中的文字選項

XML 文件內的文字會在 XPath 運算式中指定 透過「文字節點」運算子傳送文字。這個運算子「text()」 會指出擷取自指示節點的文字。例如: XPath 運算式「//formatted_address/text()」將 傳回 <formatted_address> 中的所有文字 元素。

運算式類型
所有文字節點 (包括空白字元)
XPath 運算式:"//text()"
選取:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
文字選項
XPath 運算式:"/WebServiceRequest/result[2]/message/text()"
選取:
    The secret message
    
內容相關選項
XPath 運算式:"/WebServiceRequest/result[type/text() = 'sample']/name/text()"
選取:
    Sample XML
    

或者,您也可以評估運算式,然後傳回集合 然後反覆查看「節點集」擷取 以及每個節點的文字我們會在下方的範例中採用此作法。

如要進一步瞭解 XPath,請參閱 XPath W3C 規格

在 Java 中評估 XPath

Java 提供廣泛支援,以便剖析 XML 並使用 XPath 運算式 在 javax.xml.xpath.* 套件中。 因此,本節的範例程式碼使用 Java 將 說明如何處理 XML 及剖析 XML 服務回應中的資料。

如要在 Java 程式碼中使用 XPath,您必須先將 XPathFactory 的執行個體,並呼叫 使用該工廠的 newXPath() 來建立 XPath 物件。接著,這個物件便可處理傳遞的 XML 檔案 和 XPath 運算式evaluate()

評估 XPath 運算式時,請務必疊代 任何可能的「節點集」例如系統可能傳回的由於這些 結果會在 Java 程式碼中以 DOM 節點的形式傳回,您必須擷取 例如 NodeList 物件中的多個值,以及 反覆執行該物件,從這些物件中擷取任何文字或值 節點。

下列程式碼說明如何建立 XPath 物件,為其指派 XML 和 XPath 運算式,然後評估 輸出相關內容。

import org.xml.sax.InputSource;
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.io.*;

public class SimpleParser {

  public static void main(String[] args) throws IOException {

	XPathFactory factory = XPathFactory.newInstance();

    XPath xpath = factory.newXPath();

    try {
      System.out.print("Web Service Parser 1.0\n");

      // In practice, you'd retrieve your XML via an HTTP request.
      // Here we simply access an existing file.
      File xmlFile = new File("XML_FILE");

      // The xpath evaluator requires the XML be in the format of an InputSource
	  InputSource inputXml = new InputSource(new FileInputStream(xmlFile));

      // Because the evaluator may return multiple entries, we specify that the expression
      // return a NODESET and place the result in a NodeList.
      NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET);

      // We can then iterate over the NodeList and extract the content via getTextContent().
      // NOTE: this will only return text for element nodes at the returned context.
      for (int i = 0, n = nodes.getLength(); i < n; i++) {
        String nodeString = nodes.item(i).getTextContent();
        System.out.print(nodeString);
        System.out.print("\n");
      }
    } catch (XPathExpressionException ex) {
	  System.out.print("XPath Error");
    } catch (FileNotFoundException ex) {
      System.out.print("File Error");
    }
  }
}