Sprawdzone metody korzystania z usług internetowych interfejsu Places API

Usługi internetowe Google Maps Platform to zbiór interfejsów HTTP usług Google, które dostarczają dane geograficzne na potrzeby aplikacji mapowych.

W tym przewodniku opisano kilka typowych metod przydatnych podczas konfigurowania żądań usług internetowych i przetwarzania odpowiedzi usługi. Pełną dokumentację interfejsu Places API znajdziesz w przewodniku dla programistów.

Co to jest usługa sieciowa?

Usługi internetowe Google Maps Platform to interfejs umożliwiający zgłaszanie żądań danych interfejsu API Map Google z usług zewnętrznych i korzystanie z tych danych w aplikacjach Map. Usługi te są przeznaczone do używania w połączeniu z mapą, zgodnie z ograniczeniami licencji zawartymi w Warunkach korzystania z Google Maps Platform.

Usługi internetowe interfejsów API Map Google używają żądań HTTP(S) kierowanych do konkretnych adresów URL i przekazują parametry adresów URL lub dane POST w formacie JSON jako argumenty do usług. Ogólnie rzecz biorąc, usługi te zwracają dane w treści odpowiedzi w postaci kodu JSON lub XML, które są następnie analizowane lub przetwarzane przez aplikację.

Typowe żądanie do interfejsu Places API ma zwykle następującą postać:

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

Uwaga: wszystkie aplikacje interfejsu Places API wymagają uwierzytelniania. Dowiedz się więcej o danych uwierzytelniających.

Dostęp SSL/TLS

HTTPS jest wymagany w przypadku wszystkich żądań Google Maps Platform, które używają kluczy interfejsu API lub zawierają dane użytkownika. Żądania przesyłane za pomocą protokołu HTTP, które zawierają dane wrażliwe, mogą zostać odrzucone.

Tworzenie prawidłowego adresu URL

Może Ci się wydawać, że „prawidłowy” adres URL jest oczywisty, ale to nieprawda. Na przykład adres URL wpisany w pasku adresu w przeglądarce może zawierać znaki specjalne (np."上海+中國"). Przed transmisją przeglądarka musi wewnętrznie przetłumaczyć te znaki na inne kodowanie. Zgodnie z tym samym tokenem każdy kod, który generuje lub akceptuje dane wejściowe UTF-8, może traktować adresy URL ze znakami UTF-8 jako „prawidłowe”, ale musiałby również je przetłumaczyć przed wysłaniem ich na serwer WWW. Ten proces nazywamy kodowaniem adresów URL, czyli kodowaniem procentowym.

Znaki specjalne

Musimy tłumaczyć znaki specjalne, ponieważ wszystkie adresy URL muszą spełniać wymagania opisane w specyfikacji identyfikatora URI (Uniform Resource Identifier). Oznacza to, że adresy URL mogą zawierać tylko specjalny podzbiór znaków ASCII: znane symbole alfanumeryczne i niektóre znaki zarezerwowane do wykorzystania jako znaki kontrolne w adresach URL. Tabela zawiera podsumowanie tych znaków:

Podsumowanie prawidłowych znaków adresu URL
UstawpostacieUżycie adresu URL
Alfanumeryczne a b c d e f g h i j k l m Ciągi tekstowe, wykorzystanie schematu (http), port (8080) itp.
Niezarezerwowane - _ . ~ Ciągi tekstowe
Zarezerwowano ! * ' ( ) ; : @ & = + $ , / ? % # [ ] Znaki kontrolne lub ciągi tekstowe

Podczas tworzenia prawidłowego adresu URL musisz upewnić się, że zawiera on tylko znaki podane w tabeli. Skonfigurowanie adresu URL tak, aby używał takiego zestawu znaków, zwykle skutkuje 2 problemami: pominięciem i zastąpieniem:

  • Znaki, które chcesz obsługiwać, znajdują się poza powyższym zestawem. Na przykład znaki w innych językach, takich jak 上海+中國, muszą być zakodowane przy użyciu podanych wyżej znaków. Zgodnie z popularną konwencją spacje (niedozwolone w adresach URL) często są przedstawiane za pomocą znaku plusa '+'.
  • Znaki w powyższym zestawie występują jako znaki zastrzeżone, ale muszą być używane dosłownie. Na przykład ? jest używany w adresach URL do wskazywania początku ciągu zapytania. Jeśli chcesz użyć ciągu znaków „?” i „tajemnic”, musisz zakodować znak '?'.

Wszystkie znaki do kodowania URL są kodowane za pomocą znaku '%' i dwuznakowej wartości szesnastkowej odpowiadającej ich znakowi UTF-8. Na przykład kod 上海+中國 w formacie UTF-8 byłby zakodowany w adresie URL jako %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. Ciąg ? and the Mysterians byłby zakodowany w adresie URL jako %3F+and+the+Mysterians lub %3F%20and%20the%20Mysterians.

Typowe znaki, które wymagają kodowania

Oto kilka typowych znaków, które muszą być zakodowane:

Niebezpieczna postać Zakodowana wartość
Miejsce %20
%22
< %3C
> %3E
# %23
% %25
| %7C

Konwersja adresu URL otrzymanego z danych wejściowych użytkownika bywa czasem skomplikowana. Użytkownik może na przykład wprowadzić adres „ul. Główna 5" Ogólnie adres URL należy tworzyć z części, a wszelkie dane wpisane przez użytkownika traktuj jak znaki.

Poza tym adresy URL we wszystkich usługach internetowych Google Maps Platform i statycznych internetowych interfejsach API mogą mieć maksymalnie 16 384 znaki. W większości usług limit znaków będzie rzadko osiągany. Pamiętaj jednak, że niektóre usługi mają kilka parametrów, które mogą powodować powstawanie długich adresów URL.

Kontrolowane korzystanie z interfejsów API Google

Źle zaprojektowane klienty interfejsu API mogą zwiększać obciążenie zarówno internetu, jak i serwerów Google. Ta sekcja zawiera kilka sprawdzonych metod dla klientów korzystających z interfejsów API. Postępując zgodnie z tymi sprawdzonymi metodami, unikniesz zablokowania aplikacji z powodu niezamierzonego nadużywania interfejsów API.

Exponential Backoff

W rzadkich przypadkach coś może pójść nie tak przy realizacji żądania, możesz otrzymać kod odpowiedzi HTTP 4XX lub 5XX albo połączenie TCP może po prostu ulec awarii gdzieś pomiędzy klientem a serwerem Google. Często warto ponawiać próbę, ponieważ kolejna prośba może się udać, gdy pierwotna prośba nie powiedzie się. Ważne jest jednak, aby nie zapętlać sobie wielu żądań wysyłanych do serwerów Google. Takie zapętlenie może spowodować przeciążenie sieci między klientem a Google, co może powodować problemy u wielu stron.

Lepszym sposobem jest ponowienie próby z coraz większymi opóźnieniami. Zazwyczaj opóźnienie jest zwiększane przez mnożnik przy każdej próbie. Jest to tzw. wykładniczy czas ponowienia.

Rozważmy na przykład aplikację, która chce wysłać to żądanie do interfejsu Time Zone API:

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

Poniższy przykładowy Python pokazuje, jak wysłać żądanie ze wzrastającym czasem do ponowienia:

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}")

Zadbaj też o to, by wyżej w łańcuchu wywołań aplikacji nie było kodu ponawiania prób, który prowadzi do powtarzających się żądań w krótkich odstępach czasu.

Zsynchronizowane żądania

Duża liczba zsynchronizowanych żądań do interfejsów API Google może wyglądać jak atak typu Distributed Denial of Service (DDoS) na infrastrukturę Google i jest odpowiednio traktowany. Aby tego uniknąć, upewnij się, że żądania do interfejsu API nie są synchronizowane między klientami.

Weźmy na przykład aplikację, która wyświetla godzinę w bieżącej strefie czasowej. Ta aplikacja prawdopodobnie ustawi alarm w systemie operacyjnym klienta, który aktywuje go na początku minuty, aby można było zaktualizować wyświetlaną godzinę. Podczas przetwarzania powiązanego z tym alarmem aplikacja nie powinna wykonywać żadnych wywołań interfejsu API.

Wykonywanie wywołań interfejsu API w odpowiedzi na ustalony alarm jest niewłaściwe, ponieważ powoduje synchronizację wywołań interfejsu API z początkiem minuty, nawet między różnymi urządzeniami, a nie równomiernie rozkładania się w czasie. Źle zaprojektowana aplikacja, która to robi, na początku każdej minuty wywoła skok ruchu na poziomie 60-krotnie większym niż normalne.

Dobrym rozwiązaniem jest ustawienie drugiego alarmu na losowo wybraną godzinę. Po uruchomieniu tego drugiego alarmu aplikacja wywołuje potrzebne interfejsy API i zapisuje wyniki. Gdy aplikacja chce zaktualizować wyświetlacz na początku minuty, używa wcześniej zapisanych wyników, zamiast ponownie wywoływać interfejs API. Dzięki temu wywołania interfejsu API są rozłożone równomiernie w czasie. Dodatkowo wywołania interfejsu API nie opóźniają renderowania podczas aktualizowania wyświetlacza.

Oprócz początku minuty należy też pamiętać, że synchronizacja nie przypada na początek godziny i początek każdego dnia o północy.

Przetwarzanie odpowiedzi

W tej sekcji omówiono sposób dynamicznego wyodrębniania tych wartości z odpowiedzi usługi sieciowej.

Usługi internetowe Map Google udzielają odpowiedzi, które są łatwe do zrozumienia, ale niezbyt przyjazne dla użytkownika. Podczas wykonywania zapytania zamiast wyświetlania zbioru danych prawdopodobnie zechcesz wyodrębnić kilka konkretnych wartości. Ogólnie zalecamy analizowanie odpowiedzi z usługi internetowej i wyodrębnienie tylko tych wartości, które Cię interesują.

Schemat analizy zależy od tego, czy zwracasz dane wyjściowe w formacie XML czy JSON. Odpowiedzi JSON, które występują już w formie obiektów JavaScript, mogą być przetwarzane w samym JavaScripcie na kliencie. Aby adresować elementy w formacie XML, odpowiedzi XML powinny być przetwarzane przy użyciu procesora XML i języka zapytań XML. W poniższych przykładach używamy XPath, ponieważ jest ona powszechnie obsługiwana w bibliotekach przetwarzania XML.

Przetwarzanie pliku XML z użyciem XPath

XML to stosunkowo dopracowany format uporządkowanych informacji używany do wymiany danych. Chociaż format XML nie jest tak łatwy jak format JSON, zapewnia większą obsługę języków i bardziej zaawansowane narzędzia. Na przykład kod przetwarzania XML w Javie jest wbudowany w pakiety javax.xml.

Podczas przetwarzania odpowiedzi XML należy używać odpowiedniego języka zapytań do wybierania węzłów w dokumencie XML, zamiast zakładać, że elementy znajdują się w bezwzględnych pozycjach w znacznikach XML. XPath to składnia języka służąca do jednoznacznego opisywania węzłów i elementów dokumentu XML. Wyrażenia XPath umożliwiają identyfikowanie konkretnej treści w dokumencie odpowiedzi XML.

Wyrażenia XPath

Częściowa znajomość XPath ułatwia opracowanie niezawodnego schematu analizy. W tej sekcji skupimy się na adresowaniu elementów dokumentu XML za pomocą ścieżki XPath, ponieważ umożliwia to adresowanie wielu elementów i tworzenie złożonych zapytań.

XPath korzysta ze składni podobnej do używanej w przypadku ścieżek do katalogów, korzystając z wyrażeń do wybierania elementów w dokumencie XML. Te wyrażenia identyfikują elementy w drzewie dokumentów XML, które jest drzewem hierarchicznym podobnym do DOM. Wyrażenia XPath są zwykle zachłanne, co oznacza, że będą pasować do wszystkich węzłów spełniających podane kryteria.

Aby zilustrować te przykłady, użyjemy tego abstrakcyjnego kodu 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>

Wybór węzła w wyrażeniach

Wybory XPath wybierają węzły. Węzeł główny obejmuje cały dokument. Wybierasz ten węzeł za pomocą wyrażenia specjalnego „/”. Pamiętaj, że węzeł główny nie jest węzłem najwyższego poziomu w dokumencie XML – w rzeczywistości znajduje się o jeden poziom powyżej tego elementu najwyższego poziomu i zawiera go.

Węzły elementów reprezentują różne elementy w drzewie dokumentów XML. Na przykład element <WebServiceResponse> reprezentuje element najwyższego poziomu zwrócony w naszej przykładowej usłudze powyżej. Poszczególne węzły wybierasz za pomocą ścieżek bezwzględnych lub względnych, co wskazuje obecność lub brak znaku „/”.

  • Ścieżka bezwzględna: wyrażenie „/WebServiceResponse/result” wybiera wszystkie węzły <result>, które są podrzędne względem węzła <WebServiceResponse>. (Pamiętaj, że oba te elementy pochodzą od węzła głównego „/”).
  • Ścieżka względna z bieżącego kontekstu: wyrażenie „result” będzie pasować do wszystkich elementów <result> w bieżącym kontekście. Nie musisz przejmować się kontekstem, ponieważ wyniki z usług internetowych przetwarzasz zwykle za pomocą jednego wyrażenia.

Każde z tych wyrażeń można rozszerzyć przez dodanie ścieżki z symbolem wieloznacznym oznaczonym podwójnym ukośnikiem („//”). Ten symbol wieloznaczny oznacza, że do ścieżki interwencyjnej może pasować zero lub więcej elementów. Na przykład wyrażenie XPath „//formatted_address” będzie pasować do wszystkich węzłów o tej nazwie w bieżącym dokumencie. Wyrażenie //viewport//lat pasuje do wszystkich elementów <lat>, które mogą być powiązane z elementem nadrzędnym <viewport>.

Domyślnie wyrażenia XPath pasują do wszystkich elementów. Możesz ograniczyć dopasowanie wyrażenia do konkretnego elementu, podając predicate ujęty w nawiasy kwadratowe ([]). Wyrażenie XPath „/GeocodeResponse/result[2]” zawsze zwraca drugi wynik.

Typ wyrażenia
Węzeł główny
Wyrażenie XPath:/
Wybór:
    <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>
    
Ścieżka bezwzględna
Wyrażenie XPath:/WebServiceResponse/result
Wybór:
    <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>
    
Ścieżka z symbolem wieloznacznym
Wyrażenie XPath:/WebServiceResponse//location
Wybór:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
Ścieżka z predykatem
Wyrażenie XPath:/WebServiceResponse/result[2]/message
Wybór:
    <message>The secret message</message>
    
Wszystkie bezpośrednie elementy podrzędne z pierwszego result roku
Wyrażenie XPath:/WebServiceResponse/result[1]/*
Wybór:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
name obiektu result, w którym tekst type to „próbka”.
Wyrażenie XPath:/WebServiceResponse/result[type/text()='sample']/name
Wybór:
    Sample XML
    

Pamiętaj, że podczas wybierania elementów należy wybrać węzły, a nie tylko tekst w tych obiektach. Ogólnie warto iterować wszystkie dopasowane węzły i wyodrębnić tekst. Węzły tekstowe mogą być też dopasowywane bezpośrednio – patrz Węzły tekstowe poniżej.

Pamiętaj, że XPath obsługuje również węzły atrybutów. Jednak wszystkie usługi internetowe Map Google wyświetlają elementy bez atrybutów, więc dopasowanie atrybutów nie jest konieczne.

Zaznaczanie tekstu w wyrażeniach

Tekst w dokumencie XML jest określany w wyrażeniach XPath za pomocą operatora węzła tekstowego. Operator „text()” wskazuje wyodrębnianie tekstu ze wskazanego węzła. Na przykład wyrażenie XPath „//formatted_address/text()” zwróci cały tekst w elementach <formatted_address>.

Typ wyrażenia
Wszystkie węzły tekstowe (w tym odstępy)
Wyrażenie XPath://text()
Wybór:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
Zaznaczenie tekstu
Wyrażenie XPath:/WebServiceRequest/result[2]/message/text()
Wybór:
    The secret message
    
Wybór zależny od kontekstu
Wyrażenie XPath:/WebServiceRequest/result[type/text() = 'sample']/name/text()
Wybór:
    Sample XML
    

Możesz też ocenić wyrażenie i zwrócić zbiór węzłów, a następnie iterować ten „zbiór węzłów” i wyodrębnić tekst z każdego węzła. Stosujemy tę metodę w przykładzie poniżej.

Więcej informacji o XPath znajdziesz w specyfikacji XPath W3C.

Ocena XPath w Javie

Javę obsługuje szeroki zakres analizy składni XML i używania wyrażeń XPath w pakiecie javax.xml.xpath.*. Dlatego w przykładowym kodzie w tej sekcji pokazano sposób obsługi kodu XML i analizowania danych z odpowiedzi usługi XML.

Aby użyć XPath w kodzie Java, musisz najpierw utworzyć instancję XPathFactory i wywołać w tej fabryce newXPath(), by utworzyć obiekt XPath . Ten obiekt może następnie przetwarzać przekazywane wyrażenia XML i XPath za pomocą metody evaluate().

Podczas oceniania wyrażeń XPath upewnij się, że wykonujesz iterację przez wszystkie możliwe „zbiory węzłów”, które mogą być zwracane. Wyniki te są zwracane w kodzie Java jako węzły DOM, dlatego należy zarejestrować większą liczbę wartości w obiekcie NodeList i wykonać iterację w obiekcie, aby wyodrębnić tekst lub wartości z tych węzłów.

Poniższy kod ilustruje, jak utworzyć obiekt XPath, przypisać do niego kod XML i wyrażenie XPath, a także ocenić wyrażenie w celu wyświetlenia odpowiedniej treści.

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");
    }
  }
}