共用方式為


教學課程:單頁 Web 應用程式

警告

在 2020 年 10 月 30 日,Bing 搜尋 API 從 Azure AI 服務移至 Bing 搜尋服務。 本檔僅供參考。 如需更新的檔案,請參閱 Bing 搜尋 API 檔案。 如需建立 Bing 搜尋新 Azure 資源的指示,請參閱透過 Azure Marketplace 建立 Bing 搜尋資源

Bing 實體搜尋 API 可讓您搜尋 Web 以取得 實體地點的相關信息。 您可以在指定的查詢中要求某種結果或兩者。 以下提供地點和實體的定義。

結果 說明
實體 您依名稱找到的知名人士、地點和事物
地方 依名稱 類型找到的餐館、酒店和其他當地企業(義大利餐廳)

在本教學課程中,我們會建置單頁 Web 應用程式,以使用 Bing 實體搜尋 API 在頁面中顯示搜尋結果。 應用程式包含 HTML、CSS 和 JavaScript 元件。

API 可讓您依位置排列結果的優先順序。 在行動裝置應用程式中,您可以要求裝置自己的位置。 在 Web 應用程式中,您可以使用 函 getPosition() 式。 但此呼叫僅適用於安全的內容,而且可能無法提供精確的位置。 此外,使用者可能想要搜尋位於自己以外的位置附近的實體。

因此,我們的應用程式會呼叫 Bing 地圖服務,以從使用者輸入的位置取得緯度和經度。 然後,使用者可以輸入地標名稱(“Space Needle”)或完整或部分位址(“紐約市”),而 Bing 地圖服務 API 會提供座標。

備註

按兩下頁面底部的 JSON 和 HTTP 標題會顯示 JSON 回應和 HTTP 要求資訊。 這些詳細數據在探索服務時很有用。

本教學課程應用程式說明如何:

  • 在 JavaScript 中執行 Bing 實體搜尋 API 呼叫
  • 在 JavaScript 中執行 Bing 地圖服務 locationQuery API 呼叫
  • 將搜尋選項傳遞至 API 呼叫
  • 顯示搜尋結果
  • 處理 Bing 用戶端識別碼和 API 訂用帳戶金鑰
  • 處理可能發生的任何錯誤

教學課程頁面是完全獨立的;它不會使用任何外部架構、樣式表單,或甚至圖像檔。 它只使用廣受支援的 JavaScript 語言功能,並搭配所有主要網頁瀏覽器的目前版本使用。

在本教學課程中,我們只討論原始程式碼的選取部分。 完整的原始碼可在 不同的頁面上取得。 將此程式代碼複製並貼到文字編輯器中,並將它儲存為 bing.html

備註

本教學課程與 單頁 Bing Web 搜尋應用程式教學課程大致類似,但只處理實體搜尋結果。

先決條件

若要遵循本教學課程,您需要 Bing 搜尋 API 和 Bing 地圖服務 API 的訂用帳戶密鑰。

應用程式元件

如同任何單頁 Web 應用程式,教學課程應用程式包含三個部分:

  • HTML - 定義頁面的結構和內容
  • CSS - 定義頁面的外觀
  • JavaScript - 定義頁面的行為

本教學課程不會詳細說明大部分的 HTML 或 CSS,因為它們很簡單。

HTML 包含使用者輸入查詢並選擇搜尋選項的搜尋表單。 表單透過 <form> 標籤的 onsubmit 屬性連接到實際執行搜尋的 JavaScript:

<form name="bing" onsubmit="return newBingEntitySearch(this)">

onsubmit 處理程式會傳回 false,防止表單提交至伺服器。 JavaScript 程式代碼實際上會執行從表單收集必要資訊並執行搜尋的工作。

搜尋會分兩個階段完成。 首先,如果使用者已輸入位置限制,Bing 地圖服務查詢就會將它轉換成座標。 然後,此查詢的回呼會啟動 Bing 實體搜尋查詢。

HTML 也包含搜尋結果呈現的區塊(HTML <div> 標籤)。

管理訂用帳戶金鑰

備註

此應用程式需要 Bing 搜尋 API 和 Bing 地圖服務 API 的訂用帳戶密鑰。

為了避免在程式代碼中包含 Bing 搜尋和 Bing 地圖服務 API 訂用帳戶金鑰,我們會使用瀏覽器的永續性記憶體來儲存它們。 如果其中一個金鑰尚未儲存,我們會提示它,並儲存它以供稍後使用。 如果 API 稍後拒絕金鑰,我們會使儲存的金鑰失效,以便在使用者下次搜尋時要求該密鑰。

我們會定義 storeValueretrieveValue 函式,以使用 localStorage 物件(如果瀏覽器支援它)或 Cookie。 我們的 getSubscriptionKey() 函式會使用這些函式來儲存和擷取使用者的密鑰。 您可以使用下方的全域端點,或資源 Azure 入口網站中顯示的 自定義子域 端點。

// cookie names for data we store
SEARCH_API_KEY_COOKIE = "bing-search-api-key";
MAPS_API_KEY_COOKIE   = "bing-maps-api-key";
CLIENT_ID_COOKIE      = "bing-search-client-id";

// API endpoints
SEARCH_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/entities";
MAPS_ENDPOINT   = "https://dev.virtualearth.net/REST/v1/Locations";

// ... omitted definitions of storeValue() and retrieveValue()

// get stored API subscription key, or prompt if it's not found
function getSubscriptionKey(cookie_name, key_length, api_name) {
    var key = retrieveValue(cookie_name);
    while (key.length !== key_length) {
        key = prompt("Enter " + api_name + " API subscription key:", "").trim();
    }
    // always set the cookie in order to update the expiration date
    storeValue(cookie_name, key);
    return key;
}

function getMapsSubscriptionKey() {
    return getSubscriptionKey(MAPS_API_KEY_COOKIE, 64, "Bing Maps");
}

function getSearchSubscriptionKey() {
    return getSubscriptionKey(SEARCH_API_KEY_COOKIE, 32, "Bing Search");
}

HTML <body> 標籤包含一個 onload 屬性,當頁面完成載入時會呼叫 getSearchSubscriptionKey()getMapsSubscriptionKey()。 如果尚未輸入金鑰,這些呼叫會立即提示使用者輸入金鑰。

<body onload="document.forms.bing.query.focus(); getSearchSubscriptionKey(); getMapsSubscriptionKey();">

選取搜尋選項

[Bing 實體搜尋表單]

HTML 表單包含下列控制項:

控制 說明
where 用於選取用於搜尋之市場(位置和語言)的下拉功能表。
query 要在其中輸入搜尋字詞的文字欄位。
safe 複選框,指出是否開啟SafeSearch(限制「成人」結果)
what 選擇搜尋實體、地點或兩者的功能表。
mapquery 用戶可以在其中輸入完整或部分位址、地標等文字字段,以協助 Bing 實體搜尋傳回更相關的結果。

備註

地點結果目前僅適用於美國。 wherewhat 功能表有程式代碼可強制執行此限制。 如果您在 what 功能表中選擇 [地點],並選擇非美國市場,則 what 會改為 [任何專案]。 如果您在功能表中選取非美國市場時選擇 where [地點],則 where 變更為 [美國]。

我們的 JavaScript 函式 bingSearchOptions() 會將這些欄位轉換成 Bing 搜尋 API 的部分查詢字串。

// build query options from the HTML form
function bingSearchOptions(form) {

    var options = [];
    options.push("mkt=" + form.where.value);
    options.push("SafeSearch=" + (form.safe.checked ? "strict" : "off"));
    if (form.what.selectedIndex) options.push("responseFilter=" + form.what.value);
    return options.join("&");
}

例如,SafeSearch 功能可以是 strictmoderateoff,其 moderate 為預設值。 但是我們的表單使用複選框,其只有兩個狀態。 JavaScript 程式代碼會將此設定 strict 轉換成 或 off (我們不會使用 moderate)。

欄位 mapquery 不會在 中 bingSearchOptions() 處理,因為它用於 Bing 地圖服務位置查詢,而不是 Bing 實體搜尋。

取得位置

Bing 地圖服務 API 提供一種方法locationQuery,我們用來尋找使用者輸入之位置的緯度和經度。 然後,這些座標會隨著使用者的要求傳遞至 Bing 實體搜尋 API。 搜尋結果會設定接近指定位置的實體和位置的優先順序。

我們無法在 Web 應用程式中使用一 XMLHttpRequest 般查詢來存取 Bing 地圖服務 API,因為服務不支援跨原始來源查詢。 幸運的是,它支援 JSONP(「P」代表「padded」)。 JSONP 回應是包裝在函數調用中的一般 JSON 回應。 要求是透過在文件中插入 <script> 標記來發出。 (載入文稿不受瀏覽器安全策略約束。

函式 bingMapsLocate() 會建立並插入查詢的 <script> 標籤。 jsonp=bingMapsCallback查詢字串的區段會指定要以回應呼叫的函式名稱。

function bingMapsLocate(where) {

    where = where.trim();
    var url = MAPS_ENDPOINT + "?q=" + encodeURIComponent(where) + 
                "&jsonp=bingMapsCallback&maxResults=1&key=" + getMapsSubscriptionKey();

    var script = document.getElementById("bingMapsResult")
    if (script) script.parentElement.removeChild(script);

    // global variable holds reference to timer that will complete the search if the maps query fails
    timer = setTimeout(function() {
        timer = null;
        var form = document.forms.bing;
        bingEntitySearch(form.query.value, "", bingSearchOptions(form), getSearchSubscriptionKey());
    }, 5000);

    script = document.createElement("script");
    script.setAttribute("type", "text/javascript");
    script.setAttribute("id", "bingMapsResult");
    script.setAttribute("src", url);
    script.setAttribute("onerror", "BingMapsCallback(null)");
    document.body.appendChild(script);

    return false;
}

備註

如果 Bing 地圖服務 API 沒有回應,則永遠不會呼叫 函 bingMapsCallBack() 式。 一般而言,這表示 bingEntitySearch() 不會呼叫,且實體搜尋結果不會出現。 若要避免這種情況, bingMapsLocate() 也設定定時器在五秒後呼叫 bingEntitySearch() 。 回呼函式中有邏輯可避免執行實體搜尋兩次。

當查詢完成時, bingMapsCallback() 會依要求呼叫 函式。

function bingMapsCallback(response) {

    if (timer) {    // we beat the timer; stop it from firing
        clearTimeout(timer);
        timer = null;
    } else {        // the timer beat us; don't do anything
        return; 
    }

    var location = "";
    var name = "";
    var radius = 1000;

    if (response) {
        try {
            if (response.statusCode === 401) {
                invalidateMapsKey();
            } else if (response.statusCode === 200) {
                var resource = response.resourceSets[0].resources[0];
                var coords   = resource.point.coordinates;
                name         = resource.name;

                // the radius is the largest of the distances between the location and the corners
                // of its bounding box (in case it's not in the center) with a minimum of 1 km
                try {
                    var bbox    = resource.bbox;
                    radius  = Math.max(haversineDistance(bbox[0], bbox[1], coords[0], coords[1]),
                                       haversineDistance(coords[0], coords[1], bbox[2], bbox[1]),
                                       haversineDistance(bbox[0], bbox[3], coords[0], coords[1]),
                                       haversineDistance(coords[0], coords[1], bbox[2], bbox[3]), 1000);
                } catch(e) {  }
                var location = "lat:" + coords[0] + ";long:" + coords[1] + ";re:" + Math.round(radius);
            }
        }
        catch (e) { }   // response is unexpected. this isn't fatal, so just don't provide location
    }

    var form = document.forms.bing;
    if (name) form.mapquery.value = name;
    bingEntitySearch(form.query.value, location, bingSearchOptions(form), getSearchSubscriptionKey());

}

除了緯度和經度,Bing 實體搜尋查詢還需要一個半徑,以指示位置資訊的精確程度。 我們會使用 Bing 地圖服務回應中提供的 周框方塊 來計算半徑。 周框方塊是圍繞整個位置的矩形。 例如,如果使用者輸入 NYC,則結果會包含紐約市的大致中央座標,以及包含城市的周框方塊。

我們會先使用 haversineDistance() 函式來計算從主要座標到周框方塊四個角落之間的距離(未顯示)。 我們使用這四個距離中最大的一個做為半徑。 最小半徑為一公里。 如果在回應中未提供周框方塊,這個值也會作為預設值。

取得座標和半徑之後,我們會呼叫 bingEntitySearch() 來執行實際搜尋。

假設查詢、位置、選項字串和 API 金鑰,函 BingEntitySearch() 式會提出 Bing 實體搜尋要求。

// perform a search given query, location, options string, and API keys
function bingEntitySearch(query, latlong, options, key) {

    // scroll to top of window
    window.scrollTo(0, 0);
    if (!query.trim().length) return false;     // empty query, do nothing

    showDiv("noresults", "Working. Please wait.");
    hideDivs("pole", "mainline", "sidebar", "_json", "_http", "error");

    var request = new XMLHttpRequest();
    var queryurl = SEARCH_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;

    // open the request
    try {
        request.open("GET", queryurl);
    } 
    catch (e) {
        renderErrorMessage("Bad request (invalid URL)\n" + queryurl);
        return false;
    }

    // add request headers
    request.setRequestHeader("Ocp-Apim-Subscription-Key", key);
    request.setRequestHeader("Accept", "application/json");

    var clientid = retrieveValue(CLIENT_ID_COOKIE);
    if (clientid) request.setRequestHeader("X-MSEdge-ClientID", clientid);

    if (latlong) request.setRequestHeader("X-Search-Location", latlong);

    // event handler for successful response
    request.addEventListener("load", handleBingResponse);
    
    // event handler for erorrs
    request.addEventListener("error", function() {
        renderErrorMessage("Error completing request");
    });

    // event handler for aborted request
    request.addEventListener("abort", function() {
        renderErrorMessage("Request aborted");
    });

    // send the request
    request.send();
    return false;
}

HTTP 要求成功完成時,JavaScript 會呼叫我們的 load 事件處理程式 函 handleBingResponse() 式,以處理 API 的成功 HTTP GET 要求。

// handle Bing search request results
function handleBingResponse() {
    hideDivs("noresults");

    var json = this.responseText.trim();
    var jsobj = {};

    // try to parse JSON results
    try {
        if (json.length) jsobj = JSON.parse(json);
    } catch(e) {
        renderErrorMessage("Invalid JSON response");
    }

    // show raw JSON and HTTP request
    showDiv("json", preFormat(JSON.stringify(jsobj, null, 2)));
    showDiv("http", preFormat("GET " + this.responseURL + "\n\nStatus: " + this.status + " " + 
        this.statusText + "\n" + this.getAllResponseHeaders()));

    // if HTTP response is 200 OK, try to render search results
    if (this.status === 200) {
        var clientid = this.getResponseHeader("X-MSEdge-ClientID");
        if (clientid) retrieveValue(CLIENT_ID_COOKIE, clientid);
        if (json.length) {
            if (jsobj._type === "SearchResponse") {
                renderSearchResults(jsobj);
            } else {
                renderErrorMessage("No search results in JSON response");
            }
        } else {
            renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
        }
    if (divHidden("pole") && divHidden("mainline") && divHidden("sidebar")) 
        showDiv("noresults", "No results.<p><small>Looking for restaurants or other local businesses? Those currently areen't supported outside the US.</small>");
    }

    // Any other HTTP status is an error
    else {
        // 401 is unauthorized; force re-prompt for API key for next request
        if (this.status === 401) invalidateSearchKey();

        // some error responses don't have a top-level errors object, so gin one up
        var errors = jsobj.errors || [jsobj];
        var errmsg = [];

        // display HTTP status code
        errmsg.push("HTTP Status " + this.status + " " + this.statusText + "\n");

        // add all fields from all error responses
        for (var i = 0; i < errors.length; i++) {
            if (i) errmsg.push("\n");
            for (var k in errors[i]) errmsg.push(k + ": " + errors[i][k]);
        }

        // also display Bing Trace ID if it isn't blocked by CORS
        var traceid = this.getResponseHeader("BingAPIs-TraceId");
        if (traceid) errmsg.push("\nTrace ID " + traceid);

        // and display the error message
        renderErrorMessage(errmsg.join("\n"));
    }
}

這很重要

成功的 HTTP 要求 不一定 表示搜尋本身成功。 如果在搜尋作業中發生錯誤,Bing 實體搜尋 API 會傳回非 200 HTTP 狀態代碼,並在 JSON 回應中包含錯誤資訊。 此外,如果要求受到速率限制,API 會傳回空的回應。

上述兩個函式中的大部分程式代碼都專用於錯誤處理。 錯誤可能會在下列階段發生:

舞臺 潛在錯誤 處理者
建置 JavaScript 要求物件 URL 無效 try / catch 區塊
提出要求 網路錯誤,中止的連線 errorabort 事件處理程式
執行搜尋 無效的要求、無效的 JSON、速率限制 load 事件處理程式中的測試

透過呼叫 renderErrorMessage() 來處理錯誤,並提供任何已知的錯誤詳細資訊。 如果回應通過重重關卡的錯誤測試,我們會調用 renderSearchResults() 在頁面中顯示搜尋結果。

顯示搜尋結果

Bing 實體搜尋 API 會要求您以指定的順序顯示結果。 由於 API 可能會傳回兩種不同類型的回應,因此不足以逐一查看 JSON 回應中的最上層 EntitiesPlaces 集合,並顯示這些結果。 (如果您只想要一種類型的結果,請使用 responseFilter 查詢參數。

相反地,我們會在 rankingResponse 搜尋結果中使用 集合來排序要顯示的結果。 這個物件是指 Entitiess 和/或 Places 集合中的項目。

rankingResponse 最多可以包含三個搜尋結果集合,指定 polemainlinesidebar

pole如果存在,則為最相關的搜尋結果,且應該以醒目方式顯示。 mainline 是指大部分的搜尋結果。 主線結果應緊接在 pole 之後顯示(如果 pole 不存在,則為第一個)。

最後。 sidebar 是指輔助搜尋結果。 它們可能會顯示在實際的側邊欄中,或只是在主線結果之後顯示。 我們已為教學課程應用程式選擇後者。

集合中的每個 rankingResponse 項目都會以兩種不同但等效的方式來參考實際的搜尋結果項目。

項目 說明
id id看起來像 URL,但不應該用於連結。 id排名結果的類型符合id答案集合中搜尋結果專案的類型,整個答案集合(例如Entities)。
answerType
resultIndex
answerType是指包含結果的最上層回應集合(例如 , EntitiesresultIndex 指的是該集合中結果的索引。 如果 resultIndex 省略,排名結果會參考整個集合。

備註

如需此部分搜尋回應的詳細資訊,請參閱 排名結果

您可以使用最方便您的應用程式的方式定位所參考的搜尋結果專案。 在我們的教學課程程序代碼中,我們會使用 answerTyperesultIndex 來找出每個搜尋結果。

最後,是時候查看函式 renderSearchResults()了。 此函式會遍歷代表搜尋結果三個區段的三個rankingResponse集合。 針對每個區段,我們會呼叫 renderResultsItems() 來轉譯該區段的結果。

// render the search results given the parsed JSON response
function renderSearchResults(results) {

    // if spelling was corrected, update search field
    if (results.queryContext.alteredQuery) 
        document.forms.bing.query.value = results.queryContext.alteredQuery;

    // for each possible section, render the results from that section
    for (section in {pole: 0, mainline: 0, sidebar: 0}) {
        if (results.rankingResponse[section])
            showDiv(section, renderResultsItems(section, results));
    }
}

渲染結果項目

在我們的 JavaScript 程式代碼中是物件, searchItemRenderers其中包含 轉譯器: 針對每種搜尋結果產生 HTML 的函式。

searchItemRenderers = { 
    entities: function(item) { ... },
    places: function(item) { ... }
}

轉譯器函式可能接受下列參數:

參數 說明
item 包含項目屬性的 JavaScript 物件,例如其 URL 和其描述。
index 其集合中結果項目的索引。
count 搜尋結果專案集合中的項目數。

indexcount 參數可用來編號結果、為集合的開頭或結尾產生特殊的 HTML、在特定數目的項目之後插入換行符等等。 如果轉譯器不需要此功能,則不需要接受這兩個參數。 事實上,我們不會在教學課程應用程式的轉譯器中使用它們。

讓我們進一步了解 entities 轉譯器:

    entities: function(item) {
        var html = [];
        html.push("<p class='entity'>");
        if (item.image) {
            var img = item.image;
            if (img.hostPageUrl) html.push("<a href='" + img.hostPageUrl + "'>");
            html.push("<img src='" + img.thumbnailUrl +  "' title='" + img.name + "' height=" + img.height + " width= " + img.width + ">");
            if (img.hostPageUrl) html.push("</a>");
            if (img.provider) {
                var provider = img.provider[0];
                html.push("<small>Image from ");
                if (provider.url) html.push("<a href='" + provider.url + "'>");
                html.push(provider.name ? provider.name : getHost(provider.url));
                if (provider.url) html.push("</a>");
                html.push("</small>");
            }
        }
        html.push("<p>");
        if (item.entityPresentationInfo) {
            var pi = item.entityPresentationInfo;
            if (pi.entityTypeHints || pi.entityTypeDisplayHint) {
                html.push("<i>");
                if (pi.entityTypeDisplayHint) html.push(pi.entityTypeDisplayHint);
                else if (pi.entityTypeHints) html.push(pi.entityTypeHints.join("/"));
                html.push("</i> - ");
            }
        }
        html.push(item.description);
        if (item.webSearchUrl) html.push("&nbsp;<a href='" + item.webSearchUrl + "'>More</a>")
        if (item.contractualRules) {
            html.push("<p><small>");
            var rules = [];
            for (var i = 0; i < item.contractualRules.length; i++) {
                var rule = item.contractualRules[i];
                var link = [];
                if (rule.license) rule = rule.license;
                if (rule.url) link.push("<a href='" + rule.url + "'>");
                link.push(rule.name || rule.text || rule.targetPropertyName + " source");
                if (rule.url) link.push("</a>");
                rules.push(link.join(""));
            }
            html.push("License: " + rules.join(" - "));
            html.push("</small>");
        }
        return html.join("");
    }, // places renderer omitted

我們的實體轉譯器函式:

  • 建置 HTML <img> 標記以顯示影像縮圖,如果有的話。
  • <a>建置 HTML 標籤,連結至包含影像的頁面。
  • 建立描述,以顯示圖片及其所在網站的相關信息。
  • 若有提示,則使用顯示提示將實體的分類納入。
  • 包含 Bing 搜尋的連結,以取得實體的詳細資訊。
  • 顯示數據源所需的任何授權或屬性資訊。

持續保存用戶端識別碼

來自 Bing 搜尋 API 的回應可能包含 X-MSEdge-ClientID 標頭,應該在後續請求中傳回給 API。 如果使用多個 Bing 搜尋 API,則應該盡可能使用相同的用戶端識別碼來搭配所有 API。

X-MSEdge-ClientID提供標頭可讓 Bing API 讓所有使用者的搜尋產生關聯,這有兩個重要優點。

首先,它可讓 Bing 搜尋引擎套用過去的內容來搜尋,以尋找更符合用戶的結果。 例如,如果使用者先前已搜尋與帆船相關的字詞,則稍後搜尋「碼頭」可能會優先傳回有關停泊帆船地點的資訊。

其次,Bing 可能會隨機選取用戶來體驗新功能,然後再廣泛提供這些功能。 為每個要求提供相同的用戶端標識碼,可確保已選擇查看功能的使用者一律會看到此功能。 若沒有用戶端標識碼,使用者可能會在其搜尋結果中看到功能出現並消失,看似隨機。

瀏覽器安全策略 (CORS) 可能會防止 X-MSEdge-ClientID 標頭可供 JavaScript 使用。 當搜尋回應與要求該回應的頁面不同來源時,就會發生此限制。 在生產環境中,您應該裝載伺服器端腳本,在與網頁相同的網域上執行 API 呼叫,藉此解決此原則。 由於腳本的原點與網頁相同,因此 X-MSEdge-ClientID 標頭接著可供 JavaScript 使用。

備註

在生產環境的 Web 應用程式中,您應該在伺服器端執行此請求。 否則,您的 Bing 搜尋 API 金鑰必須包含在網頁中,供任何檢視來源的人員使用。 您的 API 訂閱金鑰下的所有使用量,甚至是未經授權使用者提出的要求,因此請務必保護您的密鑰不被公開。

基於開發目的,您可以透過 CORS Proxy 提出 Bing Web 搜尋 API 要求。 來自這類 Proxy 的回應具有 Access-Control-Expose-Headers 標頭,可允許列出響應標頭,並將其提供給 JavaScript 使用。

很容易安裝 CORS 代理伺服器,以允許我們的教學應用程式存取客戶端 ID 標頭。 首先,如果您還沒有它,安裝 Node.js。 然後在指令視窗中發出下列命令:

npm install -g cors-proxy-server

接下來,將 HTML 檔案中的 Bing Web 搜尋端點變更為:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

最後,使用下列命令啟動 CORS Proxy:

cors-proxy-server

當您使用教學課程應用程式時,請保持命令視窗開啟。關閉視窗會停止代理伺服器。 在搜尋結果下方的可展開 HTTP 標頭區段中,您現在可以看到 X-MSEdge-ClientID 標頭(等等),並確認每個要求都相同。

後續步驟