第三方 API

到目前為止我們已經介紹過的API都是內置於瀏覽器中的,但並非所有API都是如此。許多大型網站和服務,如Google地圖、Twitter、Facebook、PayPal等,都提供了API,允許開發者利用他們的資料(例如,在你的部落格上顯示你的Twitter資訊流)或服務(例如,使用Facebook登入來讓你的使用者登入)。本文將探討瀏覽器API和第三方API之間的區別,並展示後者的一些典型用法。

預備知識 熟悉 HTMLCSSJavaScript,尤其是 JavaScript 物件基礎知識以及 DOM 指令碼網路請求等核心 API 知識。
學習成果
  • 第三方API背後的概念以及相關的模式,例如API金鑰。
  • 使用第三方地圖API。
  • 使用RESTful API。
  • 使用Google的YouTube API。

什麼是第三方API?

第三方API是由第三方(通常是像Facebook、Twitter或Google這樣的公司)提供的API,允許你透過JavaScript訪問它們的功能並在你的網站上使用。最明顯的例子之一是使用地圖API在你的頁面上顯示自定義地圖。

讓我們看看一個簡單的Mapquest API示例,並用它來說明第三方API與瀏覽器API有何不同。

它們位於第三方伺服器上

瀏覽器API是內置於瀏覽器中的——你可以立即從JavaScript訪問它們。例如,我們在介紹性文章中看到的Web Audio API,就是使用原生的AudioContext物件來訪問的。例如:

js
const audioCtx = new AudioContext();
// …
const audioElement = document.querySelector("audio");
// …
const audioSource = audioCtx.createMediaElementSource(audioElement);
// etc.

另一方面,第三方API位於第三方伺服器上。要從JavaScript訪問它們,你需要首先連線到API功能並使其在你的頁面上可用。這通常涉及首先透過一個<script>元素連結到伺服器上可用的JavaScript庫,如我們的Mapquest示例所示:

html
<script
  src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"
  defer></script>
<link
  rel="stylesheet"
  href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css" />

然後你就可以開始使用該庫中可用的物件了。例如:

js
const map = L.mapquest.map("map", {
  center: [53.480759, -2.242631],
  layers: L.mapquest.tileLayer("map"),
  zoom: 12,
});

這裡我們建立了一個變數來儲存地圖資訊,然後使用 mapquest.map() 方法建立一個新地圖,該方法以你想顯示地圖的 <div> 元素的 ID('map')和一個包含我們想顯示特定地圖詳細資訊的選項物件作為引數。在這個例子中,我們指定了地圖中心的座標,一個型別為 map 的地圖圖層(使用 mapquest.tileLayer() 方法建立),以及預設的縮放級別。

這就是Mapquest API繪製簡單地圖所需的所有資訊。你連線的伺服器處理所有複雜的工作,例如顯示正在顯示區域的正確地圖圖塊等。

注意:有些API處理其功能的訪問方式略有不同,要求開發者向特定的URL模式發出HTTP請求以檢索資料。這些被稱為RESTful API——我們稍後將展示一個示例

它們通常需要API金鑰

瀏覽器API的安全性通常透過許可權提示來處理,正如我們在第一篇文章中討論的。這些提示的目的是讓使用者知道他們訪問的網站正在發生什麼,並且不太可能成為惡意使用API的受害者。

第三方API擁有略微不同的許可權系統——它們傾向於使用開發者金鑰來允許開發者訪問API功能,這更多是為了保護API供應商而不是使用者。

你會在Mapquest API示例中找到類似下面這行程式碼:

js
L.mapquest.key = "YOUR-API-KEY-HERE";

此行指定了要在你的應用程式中使用的API或開發者金鑰——應用程式的開發者必須申請獲取金鑰,然後將其包含在程式碼中才能被允許訪問API的功能。在我們的示例中,我們只是提供了一個佔位符。

注意:建立自己的示例時,您將使用自己的API金鑰替換任何佔位符。

其他API可能要求你以略微不同的方式包含金鑰,但對於大多數API來說,模式是相對相似的。

要求金鑰使得API提供商能夠追究API使用者行為的責任。當開發者註冊了金鑰後,API提供商就會知道他們,如果他們開始惡意使用API(例如,跟蹤人們的位置或嘗試用大量請求垃圾郵件攻擊API使其停止工作),就可以採取行動。最簡單的行動就是撤銷他們的API許可權。

擴充套件Mapquest示例

讓我們為Mapquest示例新增更多功能,以展示如何使用API的其他一些特性。

  1. 要開始本節,請在新的目錄中複製一份Mapquest起始檔案。如果您已經克隆了示例儲存庫,您將在 javascript/apis/third-party-apis/mapquest/start 目錄中找到該檔案的副本。
  2. 接下來,你需要訪問Mapquest開發者網站,建立一個賬戶,然後建立一個開發者金鑰用於你的示例。(在撰寫本文時,網站上稱之為“消費者金鑰”,金鑰建立過程還要求提供一個可選的“回撥URL”。你無需在此處填寫URL:只需留空即可。)
  3. 開啟您的起始檔案,並用您的金鑰替換API金鑰佔位符。

更改地圖型別

Mapquest API 可以顯示多種不同型別的地圖。為此,請找到以下行:

js
layers: L.mapquest.tileLayer("map"),

嘗試將 'map' 更改為 'hybrid' 以顯示混合風格的地圖。還可以嘗試其他值。tileLayer 參考頁面顯示了不同的可用選項,以及更多資訊。

新增不同的控制元件

地圖有許多不同的可用控制元件;預設情況下它只顯示一個縮放控制元件。你可以使用 map.addControl() 方法來擴充套件可用控制元件;將此程式碼新增到你的程式碼中:

js
map.addControl(L.mapquest.control());

mapquest.control() 方法只是建立一個簡單的全功能控制集,預設情況下它被放置在右上角。你可以透過指定一個選項物件作為控制元件的引數來調整位置,該選項物件包含一個 position 屬性,其值是一個指定控制元件位置的字串。例如,嘗試這樣:

js
map.addControl(L.mapquest.control({ position: "bottomright" }));

還有其他型別的可用控制元件,例如mapquest.searchControl()mapquest.satelliteControl(),有些相當複雜和強大。盡情嘗試一下,看看你能創造出什麼!

新增自定義標記

在地圖上的某個點新增標記(圖示)很簡單——你只需使用L.marker()方法(這似乎在相關的Leaflet.js文件中有所記載)。將以下程式碼新增到你的示例中,同樣在window.onload內部:

js
L.marker([53.480759, -2.242631], {
  icon: L.mapquest.icons.marker({
    primaryColor: "#22407F",
    secondaryColor: "#3B5998",
    shadow: true,
    size: "md",
    symbol: "A",
  }),
})
  .bindPopup("This is Manchester!")
  .addTo(map);

如您所見,最簡單的情況下,它接受兩個引數:一個包含要顯示標記的座標的陣列,以及一個包含定義在該點顯示的圖示的 icon 屬性的選項物件。

圖示是使用mapquest.icons.marker()方法定義的,如您所見,該方法包含諸如標記顏色和大小等資訊。

在第一個方法呼叫之後,我們鏈式呼叫 .bindPopup('這是曼徹斯特!'),它定義了當標記被點選時顯示的內容。

最後,我們將 .addTo(map) 鏈式新增到鏈的末尾,以實際將標記新增到地圖中。

玩轉文件中顯示的其他選項,看看你能想出什麼!Mapquest提供了一些非常高階的功能,例如導航、搜尋等。

注意:如果您在執行示例時遇到問題,請對照我們的完成版本檢查您的程式碼。

一個RESTful API——《紐約時報》

現在讓我們看另一個API示例——《紐約時報》API。這個API允許你檢索《紐約時報》新聞報道資訊並在你的網站上顯示。這種型別的API被稱為RESTful API——我們不是像Mapquest那樣使用JavaScript庫的功能來獲取資料,而是透過向特定的URL發出HTTP請求來獲取資料,其中搜索詞和其他屬性等資料編碼在URL中(通常作為URL引數)。這是你將在API中遇到的常見模式。

下面我們將透過一個練習向您展示如何使用NYTimes API,該練習還提供了一套更通用的步驟,您可以將其作為處理新API的方法。

查詢文件

當您想使用第三方API時,找到文件至關重要,這樣您才能瞭解API有哪些功能,以及如何使用它們等等。《紐約時報》API的文件地址是https://developer.nytimes.com/

獲取開發者金鑰

出於安全和問責制的原因,大多數API都要求您使用某種開發者金鑰。要註冊NYTimes API金鑰,請遵循https://developer.nytimes.com/get-started上的說明。

  1. 讓我們為“文章搜尋API”請求一個金鑰——建立一個新應用,選擇此API作為您要使用的API(填寫名稱和描述,將“文章搜尋API”下的開關切換到“開”位置,然後點選“建立”)。
  2. 從結果頁面獲取API金鑰。
  3. 現在,要開始這個示例,請複製nytimes/start目錄中的所有檔案。如果你已經克隆了示例倉庫,你將已經擁有這些檔案的副本,你可以在 javascript/apis/third-party-apis/nytimes/start 目錄中找到它們。最初,script.js 檔案包含設定示例所需的一些變數;下面我們將填寫所需的功能。

該應用最終將允許您輸入搜尋詞和可選的開始和結束日期,然後它將使用這些資訊查詢文章搜尋API並顯示搜尋結果。

A screenshot of a sample search query and search results as retrieved from the New York Article Search API.

將API連線到你的應用程式

首先,你需要建立API和你的應用程式之間的連線。對於這個API,你需要在每次從正確的URL服務請求資料時,將API金鑰作為get引數包含進去。

  1. 找到以下這行

    js
    const key = "INSERT-YOUR-API-KEY-HERE";
    

    將現有API金鑰替換為您在上一節中獲取的實際API金鑰。

  2. // Event listeners to control the functionality 註釋下方,將以下行新增到您的 JavaScript 中。當表單提交時(按鈕被按下),這將執行一個名為 submitSearch() 的函式。

    js
    searchForm.addEventListener("submit", submitSearch);
    
  3. 現在在上一行下方新增 submitSearch()fetchResults() 函式定義。

    js
    function submitSearch(e) {
      pageNumber = 0;
      fetchResults(e);
    }
    
    function fetchResults(e) {
      // Use preventDefault() to stop the form submitting
      e.preventDefault();
    
      // Assemble the full URL
      let url = `${baseURL}?api-key=${key}&page=${pageNumber}&q=${searchTerm.value}&fq=document_type:("article")`;
    
      if (startDate.value !== "") {
        url = `${url}&begin_date=${startDate.value}`;
      }
    
      if (endDate.value !== "") {
        url = `${url}&end_date=${endDate.value}`;
      }
    }
    

submitSearch() 首先將頁碼重置為0,然後呼叫 fetchResults()。後者首先對事件物件呼叫 preventDefault(),以阻止表單實際提交(這會破壞示例)。接下來,我們使用一些字串操作來組裝我們將要發出請求的完整URL。我們首先組裝我們認為此演示強制要求的部分:

  • 基本URL(取自baseURL變數)。
  • API金鑰,必須在api-key URL引數中指定(值取自key變數)。
  • 頁碼,必須在 page URL 引數中指定(該值取自 pageNumber 變數)。
  • 搜尋詞,必須在 q URL 引數中指定(該值取自 searchTerm 文字 <input> 的值)。
  • 要返回結果的文件型別,在透過 fq URL 引數傳入的表示式中指定。在本例中,我們希望返回文章。

接下來,我們使用幾個if ()語句來檢查startDateendDate元素是否已填寫值。如果填寫了,我們將它們的值分別附加到URL中,分別在begin_dateend_date URL引數中指定。

所以,一個完整的URL最終會看起來像這樣:

url
https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=YOUR-API-KEY-HERE&page=0&q=cats&fq=document_type:("article")&begin_date=20170301&end_date=20170312

注意:您可以在《紐約時報》開發者文件中找到有關可包含哪些URL引數的更多詳細資訊。

注意:該示例具有基本的表單資料驗證——搜尋詞欄位必須在表單提交之前填寫(透過使用required屬性實現),日期欄位指定了pattern屬性,這意味著它們的值必須由8個數字組成才能提交(pattern="[0-9]{8}")。有關這些工作原理的更多詳細資訊,請參閱表單資料驗證

從API請求資料

現在我們已經構建了URL,讓我們向它發出請求。我們將使用Fetch API來完成此操作。

將以下程式碼塊新增到 fetchResults() 函式內部,緊接在關閉的花括號之前:

js
// Use fetch() to make the request to the API
fetch(url)
  .then((response) => response.json())
  .then((json) => displayResults(json))
  .catch((error) => console.error(`Error fetching data: ${error.message}`));

在這裡,我們透過將 url 變數傳遞給 fetch() 來執行請求,使用 json() 函式將響應體轉換為 JSON,然後將生成的 JSON 傳遞給 displayResults() 函式,以便資料可以在我們的 UI 中顯示。我們還會捕獲並記錄可能丟擲的任何錯誤。

顯示資料

好的,讓我們看看如何顯示資料。將以下函式新增到您的 fetchResults() 函式下方。

js
function displayResults(json) {
  while (section.firstChild) {
    section.removeChild(section.firstChild);
  }

  const articles = json.response.docs;

  nav.style.display = articles.length === 10 ? "block" : "none";

  if (articles.length === 0) {
    const para = document.createElement("p");
    para.textContent = "No results returned.";
    section.appendChild(para);
  } else {
    for (const current of articles) {
      const article = document.createElement("article");
      const heading = document.createElement("h2");
      const link = document.createElement("a");
      const img = document.createElement("img");
      const para = document.createElement("p");
      const keywordPara = document.createElement("p");
      keywordPara.classList.add("keywords");

      console.log(current);

      link.href = current.web_url;
      link.textContent = current.headline.main;
      para.textContent = current.snippet;
      keywordPara.textContent = "Keywords: ";
      for (const keyword of current.keywords) {
        const span = document.createElement("span");
        span.textContent = `${keyword.value} `;
        keywordPara.appendChild(span);
      }

      if (current.multimedia.length > 0) {
        img.src = `http://www.nytimes.com/${current.multimedia[0].url}`;
        img.alt = current.headline.main;
      }

      article.appendChild(heading);
      heading.appendChild(link);
      article.appendChild(img);
      article.appendChild(para);
      article.appendChild(keywordPara);
      section.appendChild(article);
    }
  }
}

這裡有很多程式碼;我們一步一步來解釋。

  • while 迴圈是一種常用的模式,用於刪除DOM元素的所有內容,在本例中是<section>元素。我們不斷檢查<section>是否有一個子節點,如果有,我們就刪除第一個子節點。當<section>不再有任何子節點時,迴圈結束。
  • 接下來,我們將 articles 變數設定為等於 json.response.docs——這是一個儲存所有代表搜尋返回的文章的物件的陣列。這樣做純粹是為了使後面的程式碼更簡單一些。
  • 第一個if ()塊檢查是否返回了10篇文章(API一次最多返回10篇文章)。如果是,我們就顯示包含“上一頁10條”/“下一頁10條”分頁按鈕的<nav>。如果返回的文章少於10篇,它們將全部顯示在一個頁面上,所以我們不需要顯示分頁按鈕。我們將在下一節中連線分頁功能。
  • 下一個 if () 塊檢查是否沒有返回任何文章。如果是,我們就不嘗試顯示任何內容——我們建立一個包含文字“未返回任何結果”的 <p>,並將其插入到 <section> 中。
  • 如果返回了一些文章,我們首先建立所有我們想要用來顯示每篇新聞故事的元素,將正確的內容插入到每個元素中,然後將它們插入到DOM中的適當位置。為了找出文章物件中哪些屬性包含正確的資料以供顯示,我們查閱了文章搜尋API參考(請參閱NYTimes APIs)。這些操作中的大多數都相當明顯,但有幾個值得特別指出:
    • 我們使用了一個for...of迴圈來遍歷與每篇文章關聯的所有關鍵詞,並將每個關鍵詞插入到它自己的<span>中,再插入到一個<p>中。這樣做是為了方便對每個關鍵詞進行樣式設定。
    • 我們使用了一個 if () 塊(if (current.multimedia.length > 0) { })來檢查每篇文章是否關聯了任何影像,因為有些故事沒有。我們只在影像存在時顯示第一張影像;否則,就會丟擲錯誤。

連線分頁按鈕

為了使分頁按鈕工作,我們將增加(或減少)pageNumber變數的值,然後重新執行包含新值的fetch請求,該值將包含在頁面URL引數中。這之所以有效,是因為NYTimes API一次只返回10個結果——如果超過10個結果可用,當page URL引數設定為0(或根本不包含——0是預設值)時,它將返回前10個(0-9),當設定為1時,則返回接下來的10個(10-19),依此類推。

這使我們能夠編寫一個簡單的分頁函式。

  1. 在現有的 addEventListener() 呼叫下方,新增這兩個新的呼叫,它們將在點選相關按鈕時呼叫 nextPage()previousPage() 函式。

    js
    nextBtn.addEventListener("click", nextPage);
    previousBtn.addEventListener("click", previousPage);
    
  2. 在你之前新增的程式碼下方,讓我們定義這兩個函式——現在新增這段程式碼。

    js
    function nextPage(e) {
      pageNumber++;
      fetchResults(e);
    }
    
    function previousPage(e) {
      if (pageNumber > 0) {
        pageNumber--;
      } else {
        return;
      }
      fetchResults(e);
    }
    

    第一個函式增加 pageNumber 變數的值,然後再次執行 fetchResults() 函式以顯示下一頁的結果。

    第二個函式以相反的方式工作,幾乎完全相同,但我們還需要額外檢查 pageNumber 是否已經為零,然後再遞減它——如果 fetch 請求以負數的 page URL 引數執行,可能會導致錯誤。如果 pageNumber 已經為0,我們就會從函式中 return——如果已經處於第一頁,我們就不需要再次載入相同的結果。

YouTube示例

我們還為您構建了另一個示例供您學習——請參閱我們的YouTube影片搜尋示例。它使用了兩個相關的API:

這個例子很有趣,因為它展示了兩個相關的第三方API如何協同工作來構建一個應用程式。第一個是RESTful API,而第二個則更像Mapquest(具有API特定的方法等)。然而值得注意的是,這兩個API都需要將JavaScript庫應用於頁面。RESTful API提供了函式來處理HTTP請求和返回結果。

A screenshot of a sample YouTube video search using two related APIs. The left side of the image has a sample search query using the YouTube Data API. The right side of the image displays the search results using the YouTube Iframe Player API.

我們不會在文章中過多地討論這個示例——原始碼中插入了詳細的註釋來解釋其工作原理。

要使其執行,您需要:

  • 閱讀YouTube資料API概述文件。
  • 請務必訪問已啟用的API頁面,並在API列表中確保YouTube資料API v3的狀態為“開啟”。
  • Google Cloud獲取API金鑰。
  • 在原始碼中找到字串 ENTER-API-KEY-HERE,並將其替換為您的API金鑰。
  • 透過網路伺服器執行示例。如果您直接在瀏覽器中執行它(即透過 file:// URL),它將無法工作。

總結

本文為您提供了使用第三方API向您的網站新增功能的實用介紹。