教學課程:使用 Bing Web 搜尋 API 建立單頁應用程式Tutorial: Create a single-page app using the Bing Web Search API

此單頁應用程式示範如何從 Bing Web 搜尋 API 擷取、剖析及顯示搜尋結果。This single-page app demonstrates how to retrieve, parse, and display search results from the Bing Web Search API. 本教學課程使用樣本 HTML 和 CSS,並將重點放在 JavaScript 程式碼上。The tutorial uses boilerplate HTML and CSS, and focuses on the JavaScript code. HTML、CSS 和 JS 檔案均可於 GitHub 取得,其中含有快速入門指示。HTML, CSS, and JS files are available on GitHub with quickstart instructions.

此範例應用程式可以:This sample app can:

  • 使用搜尋選項呼叫 Bing Web 搜尋 APICall the Bing Web Search API with search options
  • 顯示 Web、影像、新聞和視訊結果Display web, image, news, and video results
  • 將結果編頁Paginate results
  • 管理訂用帳戶金鑰Manage subscription keys
  • 處理錯誤Handle errors

若要使用此應用程式,需要具備 Azure 認知服務帳戶及 Bing 搜尋 API。To use this app, an Azure Cognitive Services account with Bing Search APIs is required. 如果您還沒有帳戶,可以使用免費試用來取得訂用帳戶金鑰。If you don't have an account, you can use the free trial to get a subscription key.

必要條件Prerequisites

以下是數個您執行應用程式所需的項目:Here are a few things that you'll need to run the app:

  • Node.js 8 或更新版本Node.js 8 or later
  • 訂用帳戶金鑰A subscription key

取得原始程式碼並安裝相依性Get the source code and install dependencies

第一個步驟是使用範例應用程式的原始程式碼來複製存放庫。The first step is to clone the repository with the sample app's source code.

git clone https://github.com/Azure-Samples/cognitive-services-REST-api-samples.git

然後執行 npm installThen run npm install. 對於本教學課程,Express.js 是唯一的相依性。For this tutorial, Express.js is the only dependency.

cd <path-to-repo>/cognitive-services-REST-api-samples/Tutorials/Bing-Web-Search
npm install

應用程式元件App components

我們要建置的範例應用程式由四個部分所組成:The sample app we're building is made up of four parts:

  • bing-web-search.js - 我們的 Express.js 應用程式。bing-web-search.js - Our Express.js app. 它會處理要求/回應邏輯和路由。It handles request/response logic and routing.
  • public/index.html - 我們應用程式的基本架構;它會定義向使用者呈現資料的方式。public/index.html - The skeleton of our app; it defines how data is presented to the user.
  • public/css/styles.css - 定義頁面樣式,例如字型、色彩、文字大小。public/css/styles.css - Defines page styles, such as fonts, colors, text size.
  • public/js/scripts.js - 包含邏輯以對 Bing Web 搜尋 API 提出要求、管理訂用帳戶金鑰、處理和剖析回應,以及顯示結果。public/js/scripts.js - Contains the logic to make requests to the Bing Web Search API, manage subscription keys, handle and parse responses, and display results.

本教學課程著重於 scripts.js,以及呼叫 Bing Web 搜尋 API 並處理回應所需的邏輯。This tutorial focuses on scripts.js and the logic required to call the Bing Web Search API and handle the response.

HTML 表單HTML form

index.html 包含一個表單,讓使用者能夠用來搜尋並選取搜尋選項。The index.html includes a form that enables users to search and select search options. onsubmit 屬性會在提交表單時引發,以呼叫 scripts.js 中所定義的 bingWebSearch() 方法。The onsubmit attribute fires when the form is submitted, calling the bingWebSearch() method defined in scripts.js. 它會採用三個引數︰It takes three arguments:

  • 搜尋查詢Search query
  • 選取的選項Selected options
  • 訂用帳戶金鑰Subscription key
<form name="bing" onsubmit="return bingWebSearch(this.query.value,
    bingSearchOptions(this), getSubscriptionKey())">

查詢選項Query options

HTML 表單包含對應到 Bing Web 搜尋 API v7 (英文) 中查詢參數的選項。The HTML form includes options that map to query parameters in the Bing Web Search API v7. 下表提供使用者如何使用範例應用程式來篩選搜尋結果的明細:This table provides a breakdown of how users can filter search results using the sample app:

參數Parameter 說明Description
query 可輸入查詢字串的文字欄位。A text field to enter a query string.
where 可選取市場 (位置和語言) 的下拉式選單。A drop-down menu to select the market (location and language).
what 要將特定結果類型升階的核取方塊。Checkboxes to promote specific result types. 例如,將影像升階會在搜尋結果中提高影像的排名。Promoting images, for example, increases the ranking of images in search results.
when 可讓使用者將搜尋結果限制為今天、本週或本月的下拉式選單。A drop-down menu that allows the user to limit the search results to today, this week, or this month.
safe 可啟用 Bing SafeSearch 的核取方塊,其會篩選出成人內容。A checkbox to enable Bing SafeSearch, which filters out adult content.
count 隱藏的欄位。Hidden field. 毎個要求要傳回的搜尋結果數目。The number of search results to return on each request. 變更此值,以便為每頁顯示更多或更少結果。Change this value to display fewer or more results per page.
offset 隱藏的欄位。Hidden field. 要求中第一個搜尋結果的位移,其可用於分頁。The offset of the first search result in the request, which is used for paging. 它會隨著每個新要求重設為 0It's reset to 0 with each new request.

注意

Bing Web 搜尋 API 會提供其他查詢參數來協助精簡搜尋結果。The Bing Web Search API offers additional query parameters to help refine search results. 此範例只使用數個參數。This sample only uses a few. 如需可用參數的完整清單,請參閱 Bing Web 搜尋 API v7 參考 (英文)。For a complete list of available parameters, see Bing Web Search API v7 reference.

bingSearchOptions() 函式會轉換這些選項,以符合 Bing 搜尋 API 所需的格式。The bingSearchOptions() function converts these options to match the format required by the Bing Search API.

// Build query options from selections in the HTML form.
function bingSearchOptions(form) {

    var options = [];
    // Where option.
    options.push("mkt=" + form.where.value);
    // SafeSearch option.
    options.push("SafeSearch=" + (form.safe.checked ? "strict" : "off"));
    // Freshness option.
    if (form.when.value.length) options.push("freshness=" + form.when.value);
    var what = [];
    for (var i = 0; i < form.what.length; i++)
        if (form.what[i].checked) what.push(form.what[i].value);
    // Promote option.
    if (what.length) {
        options.push("promote=" + what.join(","));
        options.push("answerCount=9");
    }
    // Count option.
    options.push("count=" + form.count.value);
    // Offset option.
    options.push("offset=" + form.offset.value);
    // Hardcoded text decoration option.
    options.push("textDecorations=true");
    // Hardcoded text format option.
    options.push("textFormat=HTML");
    return options.join("&");
}

您可以使用 moderate (此為 Bing Web 搜尋的預設設定),將 SafeSearch 設定為 strictmoderateoffSafeSearch can be set to strict, moderate, or off, with moderate being the default setting for Bing Web Search. 此表單使用具備兩種狀態的核取方塊。This form uses a checkbox, which has two states. 在此程式碼片段中,會將 SafeSearch 設定為 strictoff,但不使用 moderateIn this snippet, SafeSearch is set to strict or off, moderate isn't used.

如果選取了任何 [升階] 核取方塊,即會將 answerCount 參數加入至查詢。If any of the Promote checkboxes are selected, the answerCount parameter is added to the query. 如果使用 promote 參數,則必須使用 answerCountanswerCount is required when using the promote parameter. 在此程式碼片段中,會將值設定為 9,以傳回所有可用的結果類型。In this snippet, the value is set to 9 to return all available result types.

注意

將結果類型升階,不「保證」 它將包含於搜尋結果中。Promoting a result type doesn't guarantee that it will be included in the search results. 實際情況是,升階會提高此類結果相對於其正常狀態下的排名。Rather, promotion increases the ranking of those kinds of results relative to their usual ranking. 若要將搜尋限定於特定類型的結果,請使用 responseFilter 查詢參數,或呼叫更明確的端點,例如 Bing 影像搜尋或 Bing 新聞搜尋。To limit searches to particular kinds of results, use the responseFilter query parameter, or call a more specific endpoint such as Bing Image Search or Bing News Search.

textDecorationtextFormat 查詢參數都會硬式編碼於指令碼中,並使搜尋字詞在搜尋結果中以粗體顯示。The textDecoration and textFormat query parameters are hardcoded into the script, and cause the search term to be boldfaced in the search results. 這些都不是必要參數。These parameters aren't required.

管理訂用帳戶金鑰Manage subscription keys

為了避免對 Bing 搜尋 API 訂用帳戶金鑰進行硬式編碼,此範例應用程式會使用瀏覽器的永續性儲存體來儲存訂用帳戶金鑰。To avoid hardcoding the Bing Search API subscription key, this sample app uses a browser's persistent storage to store the subscription key. 如果未儲存任何訂用帳戶金鑰,系統會提示使用者輸入一個。If no subscription key is stored, the user is prompted to enter one. 如果 API 拒絕該訂用帳戶金鑰,系統會提示使用者重新輸入訂用帳戶金鑰。If the subscription key is rejected by the API, the user is prompted to re-enter a subscription key.

getSubscriptionKey() 函式會使用 storeValueretrieveValue 函式,來儲存及擷取使用者的訂用帳戶金鑰。The getSubscriptionKey() function uses the storeValue and retrieveValue functions to store and retrieve a user's subscription key. 這些函式會使用 localStorage 物件 (如果支援) 或 Cookie。These functions use the localStorage object, if supported, or cookies.

// Cookie names for stored data.
API_KEY_COOKIE   = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";

BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/search";

// See source code for storeValue and retrieveValue definitions.

// Get stored subscription key, or prompt if it isn't found.
function getSubscriptionKey() {
    var key = retrieveValue(API_KEY_COOKIE);
    while (key.length !== 32) {
        key = prompt("Enter Bing Search API subscription key:", "").trim();
    }
    // Always set the cookie in order to update the expiration date.
    storeValue(API_KEY_COOKIE, key);
    return key;
}

如我們先前所見,提交表單時,onsubmit 會引發並呼叫 bingWebSearchAs we saw earlier, when the form is submitted, onsubmit fires, calling bingWebSearch. 此函式會初始化並傳送要求。This function initializes and sends the request. getSubscriptionKey 會在每個提交上呼叫來驗證要求。getSubscriptionKey is called on each submission to authenticate the request.

假設有查詢、options 字串和 API 金鑰,BingWebSearch 函式就會建立 XMLHttpRequest 物件來呼叫 Bing Web 搜尋端點。Given the query, the options string, and the subscription key, the BingWebSearch function creates an XMLHttpRequest object to call the Bing Web Search endpoint.

// Perform a search constructed from the query, options, and subscription key.
function bingWebSearch(query, options, key) {
    window.scrollTo(0, 0);
    if (!query.trim().length) return false;

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

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

    // Initialize 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);

    // Event handler for successful response.
    request.addEventListener("load", handleBingResponse);

    // Event handler for errors.
    request.addEventListener("error", function() {
        renderErrorMessage("Error completing request");
    });

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

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

當要求成功之後,load 事件處理常式就會觸發並呼叫 handleBingResponse 函式。Following a successful request, the load event handler fires and calls the handleBingResponse function. handleBingResponse 會剖析結果物件、顯示結果,並包含失敗要求的錯誤邏輯。handleBingResponse parses the result object, displays the results, and contains error logic for failed requests.

function handleBingResponse() {
    hideDivs("noresults");

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

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

    // Show raw JSON and the 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 the HTTP response is 200 OK, try to render the 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" && "rankingResponse" in jsobj) {
                renderSearchResults(jsobj);
            } else {
                renderErrorMessage("No search results in JSON response");
            }
        } else {
            renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
        }
    }

    // Any other HTTP response is considered an error.
    else {
        // 401 is unauthorized; force a re-prompt for the user's subscription
        // key on the next request.
        if (this.status === 401) invalidateSubscriptionKey();

        // Some error responses don't have a top-level errors object, if absent
        // create one.
        var errors = jsobj.errors || [jsobj];
        var errmsg = [];

        // Display the 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]);
        }

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

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

重要

成功的 HTTP 要求「不」 代表搜尋本身成功。A successful HTTP request doesn't mean that the search itself succeeded. 如果搜尋作業中發生錯誤,Bing Web 搜尋 API 會傳回非 200 HTTP 狀態碼,並在 JSON 回應中包含錯誤資訊。If an error occurs in the search operation, the Bing Web Search API returns a non-200 HTTP status code and includes error information in the JSON response. 若要求速率受到限制,API 會傳回空白回應。If the request was rate-limited, the API returns an empty response.

上述兩個函式中的大部分程式碼都是專用於錯誤處理。Much of the code in both of the preceding functions is dedicated to error handling. 下列階段可能會發生錯誤:Errors may occur at the following stages:

階段Stage 可能的錯誤Potential error(s) 處理者Handled by
建置要求物件Building the request object 無效的 URLInvalid URL try / catch 區塊try / catch block
提出要求Making the request 網路錯誤、已中止連線Network errors, aborted connections errorabort 事件處理常式error and abort event handlers
執行搜尋Performing the search 無效的要求、無效的 JSON、速率限制Invalid request, invalid JSON, rate limits load 事件處理常式中的測試Tests in load event handler

錯誤會藉由呼叫 renderErrorMessage() 來處理。Errors are handled by calling renderErrorMessage(). 如果回應通過所有錯誤測試,即會呼叫 renderSearchResults() 來顯示搜尋結果。If the response passes all of the error tests, renderSearchResults() is called to display the search results.

顯示搜尋結果Display search results

對於 Bing Web 搜尋 API 所傳回的結果有使用和顯示需求There are use and display requirements for results returned by the Bing Web Search API. 由於回應可能包含各種結果類型,因而不足以逐一查看最上層 WebPages集合。Since a response may include various result types, it isn't enough to iterate through the top-level WebPages collection. 範例應用程式會改為使用 RankingResponse,來將結果排序為規格。Instead, the sample app uses RankingResponse to order the results to spec.

注意

如果您只想要單一結果類型,請使用 responseFilter 查詢參數,或考慮使用其他 Bing 搜尋端點之一,例如 Bing 影像搜尋。If you only want a single result type, use the responseFilter query parameter, or consider using one of the other Bing Search endpoints, such as Bing Image Search.

每個回應都有一個 RankingResponse 物件,其中最多可能包含三個集合:polemainlinesidebarEach response has a RankingResponse object that may include up to three collections: pole, mainline, and sidebar. pole (若存在) 是最相關的搜尋結果,必須優先顯示。pole, if present, is the most relevant search result and must be prominently displayed. mainline 包含大多數的搜尋結果,並在 pole 之後立即顯示。mainline contains most of the search results, and is displayed immediately after pole. sidebar 包含輔助搜尋結果。sidebar includes auxiliary search results. 可能的話,應該將這些結果顯示於資訊看板中。If possible, these results should be displayed in the sidebar. 如果因螢幕限制而無法顯示資訊看板,則這些結果應顯示在 mainline 結果後面。If screen limits make a sidebar impractical, these results should appear after the mainline results.

每個 RankingResponse 都包含 RankingItem 陣列,其會指定必須排序結果的方式。Each RankingResponse includes a RankingItem array that specifies how results must be ordered. 我們的範例應用程式會使用 answerTyperesultIndex 參數來識別結果。Our sample app uses the answerType and resultIndex parameters to identify the result.

注意

還有其他方式可用以識別並排名結果。There are additional ways to identify and rank results. 如需詳細資訊,請參閱使用排名顯示結果For more information, see Using ranking to display results.

讓我們看看此程式碼:Let's take a look at the code:

// Render the search results from the JSON response.
function renderSearchResults(results) {

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

    // Add Prev / Next links with result count.
    var pagingLinks = renderPagingLinks(results);
    showDiv("paging1", pagingLinks);
    showDiv("paging2", pagingLinks);

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

renderResultsItems() 函式會依序逐一查看每個 RankingResponse 集合中的項目、使用 answerTyperesultIndex 值來將每個排名結果對應至搜尋結果,然後呼叫適當的轉譯函式以產生 HTML。The renderResultsItems() function iterates through the items in each RankingResponse collection, maps each ranking result to a search result using the answerType and resultIndex values, and calls the appropriate rendering function to generate the HTML. 若未針對項目指定 resultIndex,則 renderResultsItems() 會逐一查看該類型的所有結果,並為每個項目呼叫轉譯函式。If resultIndex isn't specified for an item, renderResultsItems() iterates through all results of that type and calls the rendering function for each item. 產生的 HTML 會插入到 index.html 中適當的 <div> 元素。The resulting HTML is inserted into the appropriate <div> element in index.html.

// Render search results from the RankingResponse object per rank response and
// use and display requirements.
function renderResultsItems(section, results) {

    var items = results.rankingResponse[section].items;
    var html = [];
    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        // Collection name has lowercase first letter while answerType has uppercase
        // e.g. `WebPages` RankingResult type is in the `webPages` top-level collection.
        var type = item.answerType[0].toLowerCase() + item.answerType.slice(1);
        if (type in results && type in searchItemRenderers) {
            var render = searchItemRenderers[type];
            // This ranking item refers to ONE result of the specified type.
            if ("resultIndex" in item) {
                html.push(render(results[type].value[item.resultIndex], section));
            // This ranking item refers to ALL results of the specified type.
            } else {
                var len = results[type].value.length;
                for (var j = 0; j < len; j++) {
                    html.push(render(results[type].value[j], section, j, len));
                }
            }
        }
    }
    return html.join("\n\n");
}

檢視轉譯器函式Review renderer functions

在我們的範例應用程式中,searchItemRenderers 物件所包含的函式會針對每種類型的搜尋結果產生 HTML。In our sample app, the searchItemRenderers object includes functions that generate HTML for each type of search result.

// Render functions for each result type.
searchItemRenderers = {
    webPages: function(item) { ... },
    news: function(item) { ... },
    images: function(item, section, index, count) { ... },
    videos: function(item, section, index, count) { ... },
    relatedSearches: function(item, section, index, count) { ... }
}

重要

此範例應用程式具有適用於網頁、新聞、影像、視訊和相關搜尋的轉譯器。The sample app has renderers for web pages, news, images, videos, and related searches. 您的應用程式將需要轉譯器以用於可能接收到的各種類型結果,其中可能包括計算、拼字建議、實體、時區和定義。Your application will need renderers for any type of results it may receive, which could include computations, spelling suggestions, entities, time zones, and definitions.

部分轉譯函式只接受 item 參數。Some of the rendering functions accept only the item parameter. 其餘函式會接受其他參數,可用來根據內容以不同方式轉譯項目。Others accept additional parameters, which can be used to render items differently based on context. 未使用此資訊的轉譯器不需要接受這些參數。A renderer that doesn't use this information doesn't need to accept these parameters.

內容引數包括:The context arguments are:

參數Parameter 說明Description
section 顯示項目的結果區段 (polemainlinesidebar)。The results section (pole, mainline, or sidebar) in which the item appears.
index
count
如果 RankingResponse 項目指定要顯示給定集合中的所有結果,則可供使用,否則會使用 undefinedAvailable when the RankingResponse item specifies that all results in a given collection are to be displayed; undefined otherwise. 其集合內的項目索引,以及該集合中的項目總數。The index of the item within its collection and the total number of items in that collection. 您可以使用此資訊來為結果編號,以便為第一個或最後一個結果 (或其他結果) 產生不同的 HTML。You can use this information to number the results, to generate different HTML for the first or last result, and so on.

在此範例應用程式中,imagesrelatedSearches 轉譯器都會使用內容引數來自訂產生的 HTML。In the sample app, both the images and relatedSearches renderers use the context arguments to customize the generated HTML. 以下我們將深入觀察 images 轉譯器:Let's take a closer look at the images renderer:

searchItemRenderers = {
    // Render image result with thumbnail.
    images: function(item, section, index, count) {
        var height = 60;
        var width = Math.round(height * item.thumbnail.width / item.thumbnail.height);
        var html = [];
        if (section === "sidebar") {
            if (index) html.push("<br>");
        } else {
            if (!index) html.push("<p class='images'>");
        }
        html.push("<a href='" + item.hostPageUrl + "'>");
        var title = escape(item.name) + "\n" + getHost(item.hostPageDisplayUrl);
        html.push("<img src='"+ item.thumbnailUrl + "&h=" + height + "&w=" + width +
            "' height=" + height + " width=" + width + " title='" + title + "' alt='" + title + "'>");
        html.push("</a>");
        return html.join("");
    },
    // Other renderers are omitted from this sample...
}

影像轉譯器:The image renderer:

  • 計算影像縮圖大小 (寬度會變動,高度則固定為 60 像素)。Calculates the image thumbnail size (width varies, while height is fixed at 60 pixels).
  • 根據內容,在影像結果前面插入 HTML。Inserts the HTML that precedes the image result based on context.
  • 建置 HTML <a> 標籤,以連結至內含影像的頁面。Builds the HTML <a> tag that links to the page that contains the image.
  • 建置 HTML <img> 標籤,以顯示影像縮圖。Builds the HTML <img> tag to display the image thumbnail.

影像轉譯器會使用 sectionindex 變數,根據結果的顯示位置以不同方式顯示結果。The image renderer uses the section and index variables to display results differently depending on where they appear. 資訊看板中的影像結果之間會插入分行符號 (<br> 標記),讓資訊看板顯示影像的資料行。A line break (<br> tag) is inserted between image results in the sidebar, so that the sidebar displays a column of images. 在其他區段中,第一個影像結果 (index === 0)前面會有 <p> 標記。In other sections, the first image result (index === 0) is preceded by a <p> tag.

縮圖大小用於這兩個 <img> 標籤,以及縮圖 URL 中的 hw 欄位。The thumbnail size is used in both the <img> tag and the h and w fields in the thumbnail's URL. titlealt 屬性 (影像的文字描述) 會從影像的名稱和 URL 中的主機名稱建構。The title and alt attributes (a textual description of the image) are constructed from the image's name and the hostname in the URL.

以下是如何在範例應用程式中顯示影像的範例:Here's an example of how images are displayed in the sample app:

[Bing 影像結果]

保存用戶端識別碼Persist the client ID

來自 Bing 搜尋 API 的回應可能包含應在每個後續要求中傳回至 API 的 X-MSEdge-ClientID 標頭。Responses from the Bing search APIs may include a X-MSEdge-ClientID header that should be sent back to the API with each successive request. 如果您的應用程式使用一個以上的 Bing 搜尋 API,請確定會在服務之間隨著每個要求傳送相同的用戶端識別碼。If more than one of the Bing Search APIs is used by your app, make sure the same client ID is sent with each request across services.

提供 X-MSEdge-ClientID 標頭可讓 Bing API 關聯到使用者的搜尋。Providing the X-MSEdge-ClientID header allows the Bing APIs to associate a user's searches. 首先,它可讓 Bing 搜尋引擎將過去的內容套用至搜尋,以尋找更能滿足要求的結果。First, it allows the Bing search engine to apply past context to searches to find results that better satisfy the request. 例如,若使用者之前搜尋與航行相關的字詞,稍後搜尋「節」可能會優先傳回航行中所使用節數的相關資訊。If a user has previously searched for terms related to sailing, for example, a later search for "knots" might preferentially return information about knots used in sailing. 其次,Bing 可能會隨機選取使用者來體驗新功能,再廣泛提供這些功能。Second, Bing may randomly select users to experience new features before they are made widely available. 隨著每個要求提供相同的用戶端識別碼,可確保已選擇看到功能的使用者將一律看到該功能。Providing the same client ID with each request ensures that users who have been chosen to see a feature will always see it. 若沒有用戶端識別碼,使用者可能會在其搜尋結果中隨機看到功能出現並消失。Without the client ID, the user might see a feature appear and disappear, seemingly at random, in their search results.

瀏覽器安全性原則 (例如,跨原始來源資源共用 (CORS)) 可能會防止範例應用程式存取 X-MSEdge-ClientID 標頭。Browser security policies, such as Cross-Origin Resource Sharing (CORS), may prevent the sample app from accessing the X-MSEdge-ClientID header. 當搜尋回應的來源與要求回應的頁面不同時,就會發生這項限制。This limitation occurs when the search response has a different origin from the page that requested it. 在生產環境中,您應該裝載伺服器端指令碼,在與網頁相同的網域上執行 API 呼叫,以處理此原則。In a production environment, you should address this policy by hosting a server-side script that does the API call on the same domain as the Web page. 由於指令碼的來源與網頁相同,因此 X-MSEdge-ClientID 標頭會接著提供給 JavaScript 使用。Since the script has the same origin as the Web page, the X-MSEdge-ClientID header is then available to JavaScript.

注意

在生產 Web 應用程式中,無論如何都應該執行要求伺服器端。In a production Web application, you should perform the request server-side anyway. 否則,您的 Bing 搜尋 API 訂用帳戶金鑰必須包含在網頁中,以提供給檢視來源的任何人。Otherwise, your Bing Search API subscription key must be included in the web page, where it's available to anyone who views source. 您會根據 API 訂用帳戶金鑰的所有使用量付費,即使是未經授權的合作對象所提出的要求,因此請務必不要公開您的金鑰。You are billed for all usage under your API subscription key, even requests made by unauthorized parties, so it is important not to expose your key.

您可以基於開發目的,透過 CORS Proxy 提出要求。For development purposes, you can make a request through a CORS proxy. 來自這個類型 Proxy 的回應包含 Access-Control-Expose-Headers 標頭,可將回應標頭列入白名單並提供給 JavaScript 使用。The response from this type of proxy has an Access-Control-Expose-Headers header that whitelists response headers and makes them available to JavaScript.

您可以輕鬆安裝 CORS Proxy,讓我們的範例應用程式能夠存取用戶端識別碼標頭。It's easy to install a CORS proxy to allow our sample app to access the client ID header. 請執行這個命令:Run this command:

npm install -g cors-proxy-server

接下來,將 script.js 中的 Bing Web 搜尋端點變更為:Next, change the Bing Web Search endpoint in script.js to:

http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

使用此命令來啟動 CORS Proxy:Start the CORS proxy with this command:

cors-proxy-server

當您使用範例應用程式時,請將命令視窗保持開啟;關閉該視窗即會停止 Proxy。Leave the command window open while you use the sample app; closing the window stops the proxy. 在搜尋結果下方可展開的 [HTTP 標頭] 區段中,應該可以看見 X-MSEdge-ClientID 標頭。In the expandable HTTP Headers section below the search results, the X-MSEdge-ClientID header should be visible. 請確認它在每個要求中都一樣。Verify that it's the same for each request.

後續步驟Next steps