Руководство. Создание одностраничного веб-приложения с помощью API Bing для поиска изображенийTutorial: Create a single-page app using the Bing Image Search API

API Bing для поиска изображений позволяет искать в Интернете соответствующие изображения высокого качества.The Bing Image Search API enables you to search the web for high-quality, relevant images. Используйте это руководство для создания одностраничного веб-приложения, которое может отправлять поисковые запросы к API и отображать результаты на веб-странице.Use this tutorial to build a single-page web application that can send search queries to the API, and display the results within the webpage. Это руководство аналогично соответствующему руководству по API Bing для поиска в Интернете.This tutorial is similar to the corresponding tutorial for Bing Web Search.

На примере учебного приложения показано, как выполнить такие задачи:The tutorial app illustrates how to:

  • Вызов API Bing для поиска изображений из JavaScript.Perform a Bing Image Search API call in JavaScript
  • Улучшение результатов поиска с помощью параметров поиска.Improve search results using search options
  • Отображение и переход по страницам результатов поиска.Display and page through search results
  • Запрос и обработка идентификатора клиента Bing и ключа подписки API.Request and handle an API subscription key, and Bing client ID.

Полный исходный код этого руководства доступен на GitHub.The full source code for this tutorial is available on GitHub.

Предварительные требованияPrerequisites

  • Последняя версия Node.js.The latest version of Node.js.
  • Платформа Express.js для Node.js.The Express.js framework for Node.js. Инструкции по установке для исходного кода доступны в файле сведений примера на GitHub.Installation instructions for the source code are available in the GitHub sample readme file.

Необходимо иметь учетную запись API Cognitive Services с доступом к API-интерфейсам поиска Bing.You must have a Cognitive Services API account with access to the Bing Search APIs. Если у вас нет подписки Azure, создайте бесплатную учетную запись.If you don't have an Azure subscription, you can create an account for free. Перед тем как продолжить, потребуется ключ доступа, предоставляемый после активации бесплатной пробной версии или ключ платной подписки, указанный на панели мониторинга Azure.Before continuing, You will need the access key provided after activating your free trial, or a paid subscription key from your Azure dashboard.

Управление ключами подписки пользователя и их хранениеManage and store user subscription keys

Это приложение использует постоянное хранилище веб-браузера для хранения ключей подписки API.This application uses web browsers' persistent storage to store API subscription keys. Если ключа нет в хранилище, веб-страница будет запрашивать у пользователя ключ и сохранит его для последующего использования.If no key is stored, the webpage will prompt the user for their key and store it for later use. Если позже ключ будет отклонен API, то приложение удалит его из хранилища.If the key is later rejected by the API, The app will remove it from storage.

Определите функции storeValue и retrieveValue, которые используют объект localStorage (если браузер поддерживает его) или файл cookie.Define storeValue and retrieveValue functions to use either the localStorage object (if the browser supports it) or a cookie.

// Cookie names for data being stored
API_KEY_COOKIE   = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";
// The Bing Image Search API endpoint
BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/images/search";

try { //Try to use localStorage first
    localStorage.getItem;   

    window.retrieveValue = function (name) {
        return localStorage.getItem(name) || "";
    }
    window.storeValue = function(name, value) {
        localStorage.setItem(name, value);
    }
} catch (e) {
    //If the browser doesn't support localStorage, try a cookie
    window.retrieveValue = function (name) {
        var cookies = document.cookie.split(";");
        for (var i = 0; i < cookies.length; i++) {
            var keyvalue = cookies[i].split("=");
            if (keyvalue[0].trim() === name) return keyvalue[1];
        }
        return "";
    }
    window.storeValue = function (name, value) {
        var expiry = new Date();
        expiry.setFullYear(expiry.getFullYear() + 1);
        document.cookie = name + "=" + value.trim() + "; expires=" + expiry.toUTCString();
    }
}

Функция getSubscriptionKey() пытается извлечь ранее сохраненный ключ с помощью retrieveValue. Если ключ не найден, пользователю будет предложено ввести его и сохранить с помощью storeValue.If one isn't found, it will prompt the user for their key, and store it using storeValue.


// Get the stored API subscription key, or prompt if it's not 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;
}

В теге HTML <form> атрибут onsubmit вызывает функцию bingWebSearch, чтобы возвращать результаты поиска.The HTML <form> tag onsubmit calls the bingWebSearch function to return search results. Функция bingWebSearch использует getSubscriptionKey для аутентификации каждого запроса.bingWebSearch uses getSubscriptionKey to authenticate each query. Как показано в предыдущем определении, getSubscriptionKey запрашивает у пользователя ключ, если он еще не был введен.As shown in the previous definition, getSubscriptionKey prompts the user for the key if the key hasn't been entered. Затем ключ сохраняется для последующего использования приложением.The key is then stored for continuing use by the application.

<form name="bing" onsubmit="this.offset.value = 0; return bingWebSearch(this.query.value,
bingSearchOptions(this), getSubscriptionKey())">

Оправка поисковых запросовSend search requests

Это приложение использует HTML-форму <form> для изначальной отправки пользовательских поисковых запросов с помощью атрибута onsubmit для вызова newBingImageSearch().This application uses an HTML <form> to initially send user search requests, using the onsubmit attribute to call newBingImageSearch().

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

По умолчанию обработчик onsubmit возвращает значение false, что препятствует отправке формы.By default, the onsubmit handler returns false, keeping the form from being submitted.

Выбор параметров поискаSelect search options

[Форма Bing для поиска изображений]

API Bing для поиска изображений предоставляет несколько параметров запроса фильтров для сужения и фильтрации результатов поиска.The Bing Image Search API offers several filter query parameters to narrow and filter search results. HTML-формы в этом приложении используют и отображают следующие параметры:The HTML form in this application uses and displays the following parameter options:

where Раскрывающееся меню для выбора рынка (расположения и языка), который используется для поиска.A drop-down menu for selecting the market (location and language) used for the search.
query Текстовое поле для ввода условий поиска.The text field in which to enter the search terms.
aspect Переключатели для выбора пропорций найденных изображений: примерно квадратные, вертикальные или горизонтальны.Radio buttons for choosing the proportions of the found images: roughly square, wide, or tall.
color
when Раскрывающееся меню для ограничения поиска результатами за последний день, неделю или месяц (необязательный параметр).Drop-down menu for optionally limiting the search to the most recent day, week, or month.
safe Флажок, указывающий, следует ли использовать функцию безопасного поиска Bing для фильтрации результатов с материалами для взрослых.A checkbox indicating whether to use Bing's SafeSearch feature to filter out "adult" results.
count Скрытое поле.Hidden field. Количество результатов поиска для каждого запроса.The number of search results to return on each request. Измените значение, чтобы на странице отображалось меньше или больше результатов.Change to display fewer or more results per page.
offset Скрытое поле.Hidden field. Смещение первого результата поиска в запросе. Используется для разбиения по страницам.The offset of the first search result in the request; used for paging. Сбрасывается на 0 при новом запросе.It's reset to 0 on a new request.
nextoffset Скрытое поле.Hidden field. При получении результатов поиска для этого поля устанавливается значение nextOffset из ответа.Upon receiving a search result, this field is set to the value of the nextOffset in the response. Это поле позволяет избежать перекрывающихся результатов на соседних страницах.Using this field avoids overlapping results on successive pages.
stack Скрытое поле.Hidden field. Список смещений для предыдущих страниц результатов поиска в формате JSON. Используется для перехода на предыдущие страницы.A JSON-encoded list of the offsets of preceding pages of search results, for navigating back to previous pages.

Функция bingSearchOptions() форматирует эти параметры в частичную строку запроса, которую можно использовать в запросах API приложения.The bingSearchOptions() function formats these options into a partial query string, which can be used in the app's API requests.

// 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.when.value.length) options.push("freshness=" + form.when.value);
    var aspect = "all";
    for (var i = 0; i < form.aspect.length; i++) {
        if (form.aspect[i].checked) {
            aspect = form.aspect[i].value;
            break;
        }
    }
    options.push("aspect=" + aspect);
    if (form.color.value) options.push("color=" + form.color.value);
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    return options.join("&");
}

Выполнение запросаPerforming the request

Используя поисковой запрос, строку параметров и ключ API, функция BingImageSearch() применяет объект XMLHttpRequest для выполнения запроса к конечной точке Поиска изображений Bing.Using the search query, options string, and API key, the BingImageSearch() function uses an XMLHttpRequest object to make the request to the Bing Image Search endpoint.

// perform a search given query, options string, and API key
function bingImageSearch(query, 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("results", "related", "_json", "_http", "paging1", "paging2", "error");

    var request = new XMLHttpRequest();
    var queryurl = BING_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);

    // 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 вызывает "загруженный" обработчик событий handleBingResponse(), чтобы передать успешный HTTP-запрос GET.Upon successful completion of the HTTP request, JavaScript calls the "load" event handler handleBingResponse() to handle a successful HTTP GET request.

// 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 === "Images") {
                if (jsobj.nextOffset) document.forms.bing.nextoffset.value = jsobj.nextOffset;
                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 an error
    else {
        // 401 is unauthorized; force re-prompt for API key for next request
        if (this.status === 401) invalidateSubscriptionKey();

        // 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-запросы могут содержать сведения о неудачном поиске.Successful HTTP requests may contain failed search information. Если при выполнении поиска возникает ошибка, то API Bing для поиска изображений возвратит код состояния, отличный от "HTTP: 200", и сведения об ошибке в ответе JSON.If an error occurs during the search operation, the Bing Image Search API will return a non-200 HTTP status code and error information in the JSON response. Кроме того, если у запроса есть ограничение по скорости, то API возвратит пустой ответ.Additionally, if the request was rate-limited, the API will return an empty response.

Отображение результатов поискаDisplay the search results

Результаты поиска отображаются в функции renderSearchResults(), которая принимает данные JSON, возвращаемые службой Поиск изображений Bing, и вызывает соответствующую функцию отрисовщика для любых возвращенных изображений и связанных поисковых запросов.Search results are displayed by the renderSearchResults() function, which takes the JSON returned by the Bing Image Search service and calls an appropriate renderer function on any returned images and related searches.

function renderSearchResults(results) {

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

    showDiv("results", renderImageResults(results.value));
    if (results.relatedSearches)
        showDiv("sidebar", renderRelatedItems(results.relatedSearches));
}

Результаты поиска изображений содержаться в объекте верхнего уровня value в ответе JSON.Image search results are contained in the top-level value object within the JSON response. Они передаются в функцию renderImageResults(), которая просматривает все результаты и преобразует каждый элемент в формат HTML.These are passed to renderImageResults(), which iterates through the results and converts each item into HTML.

function renderImageResults(items) {
    var len = items.length;
    var html = [];
    if (!len) {
        showDiv("noresults", "No results.");
        hideDivs("paging1", "paging2");
        return "";
    }
    for (var i = 0; i < len; i++) {
        html.push(searchItemRenderers.images(items[i], i, len));
    }
    return html.join("\n\n");
}

API Bing для поиска изображений может возвращать четыре типа предложений поиска, которые помогают направлять пользовательский поиск, каждое из них в собственном объекте верхнего уровня:The Bing Image Search API can return four types of search suggestions to help guide users' search experiences, each in its own top-level object:

ПредложениеSuggestion ОПИСАНИЕDescription
pivotSuggestions Запросы, которые заменяют сводное слово в исходном поиске другим.Queries that replace a pivot word in original search with a different one. Например, если вы ищете "красные цветы", сводным словом может быть "красные", а сводным предложением может быть "желтые цветы".For example, if you search for "red flowers," a pivot word might be "red," and a pivot suggestion might be "yellow flowers."
queryExpansions Запросы, которые сужают исходный поиск, добавляя больше условий.Queries that narrow the original search by adding more terms. Например, если вы ищете "Microsoft Surface", расширением запроса может быть "Microsoft Surface Pro".For example, if you search for "Microsoft Surface," a query expansion might be "Microsoft Surface Pro."
relatedSearches Запросы, которые вводились другими пользователями, использовавшими ранее исходный поиск.Queries that have also been entered by other users who entered the original search. Например, если вы ищете "гора Рейнир", может применяться связанный поиск "гораFor example, if you search for "Mount Rainier," a related search might be "Mt. Святой Елены".Saint Helens."
similarTerms Запросы, похожие по смыслу на исходный запрос.Queries that are similar in meaning to the original search. Например, если вы ищете "котята", аналогичным термином может быть "милота".For example, if you search for "kittens," a similar term might be "cute."

Это приложение преобразовывает для просмотра только предложения relatedItems и размещает результирующие ссылки на боковой панели страницы.This application only renders the relatedItems suggestions, and places the resulting links in the page's sidebar.

Преобразование для просмотра результатов поискаRendering search results

В этом приложении есть объект searchItemRenderers, который содержит функции-отрисовщики, создающие HTML-код для каждого типа результатов поиска.In this application, the searchItemRenderers object contains renderer functions that generate HTML for each kind of search result.

searchItemRenderers = {
    images: function(item, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Эта функция-отрисовщик может принимать следующие параметры:These renderer functions accept the following parameters:

ПараметрParameter ОПИСАНИЕDescription
item Объект JavaScript, содержащий свойства элемента, такие как URL-адрес и его описание.The JavaScript object containing the item's properties, such as its URL and its description.
index Индекс элемента результата в коллекции.The index of the result item within its collection.
count Число элементов в коллекции результатов поиска.The number of items in the search result item's collection.

Параметры index и count используются для нумерации результатов, создания специального HTML-кода для коллекций и упорядочения содержимого.The index and count parameters are used to number results, generate HTML for collections, and organize the content. В частности, код:Specifically, it:

  • вычисляет размер эскиза изображения (переменная ширина — не менее 120 пикселей, и фиксированная высота — 90 пикселей);Calculates the image thumbnail size (width varies, with a minimum of 120 pixels, while height is fixed at 90 pixels).
  • создает HTML-тег <img> для отображения эскиза изображения;Builds the HTML <img> tag to display the image thumbnail.
  • создает HTML-теги <a> со ссылкой на изображение и страницей, которая его содержит;Builds the HTML <a> tags that link to the image and the page that contains it.
  • создает описание со сведениями об этом изображении и о сайте, на котором оно размещено.Builds the description that displays information about the image and the site it's on.
    images: function (item, index, count) {
        var height = 120;
        var width = Math.max(Math.round(height * item.thumbnail.width / item.thumbnail.height), 120);
        var html = [];
        if (index === 0) html.push("<p class='images'>");
        var title = escape(item.name) + "\n" + getHost(item.hostPageDisplayUrl);
        html.push("<p class='images' style='max-width: " + width + "px'>");
        html.push("<img src='"+ item.thumbnailUrl + "&h=" + height + "&w=" + width +
            "' height=" + height + " width=" + width + "'>");
        html.push("<br>");
        html.push("<nobr><a href='" + item.contentUrl + "'>Image</a> - ");
        html.push("<a href='" + item.hostPageUrl + "'>Page</a></nobr><br>");
        html.push(title.replace("\n", " (").replace(/([a-z0-9])\.([a-z0-9])/g, "$1.<wbr>$2") + ")</p>");
        return html.join("");
    }, // relatedSearches renderer omitted

Параметры height и width эскиза изображения указываются в теге <img> и в полях h и w в URL-адресе эскиза.The thumbnail image's height and width are used in both the <img> tag and the h and w fields in the thumbnail's URL. Это позволяет Bing вернуть эскиз точно такого размера.This enables Bing to return a thumbnail of exactly that size.

Сохранение идентификатора клиентаPersisting client ID

Ответы от интерфейсов API поиска Bing могут содержать заголовок X-MSEdge-ClientID, который необходимо отправить обратно в API с последующими запросами.Responses from the Bing search APIs may include a X-MSEdge-ClientID header that should be sent back to the API with successive requests. Если используется несколько API-интерфейсов поиска Bing, то по возможности со всеми ими необходимо использовать один идентификатор клиента.If multiple Bing Search APIs are being used, the same client ID should be used with all of them, if possible.

Наличие заголовка X-MSEdge-ClientID позволяет API-интерфейсам Bing связывать все поисковые запросы пользователя, что является полезным.Providing the X-MSEdge-ClientID header allows the Bing APIs to associate all of a user's searches, which is useful in

Во-первых, поисковая система Bing может применять к операциям поиска прошлый контекст и находить результаты, которые лучше соответствуют требованиям пользователя.First, it allows the Bing search engine to apply past context to searches to find results that better satisfy the user. Если пользователь ранее уже вводил поисковые запросы, например, связанные с мореплаванием под парусом, то при последующем поиске по слову "морские узлы" в приоритетном порядке могут возвращаться сведения об узлах, которые используются в мореплавании.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 that have been chosen to see a feature 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 может быть недоступным для кода JavaScript.Browser security policies (CORS) may prevent the X-MSEdge-ClientID header from being available to JavaScript. Это ограничение возникает, когда ответ поиска и страница, его запросившая, имеют разные источники.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.

Примечание

В рабочем веб-приложении запрос следует всегда выполнять на стороне сервера.In a production Web application, you should perform the request server-side anyway. В противном случае вам придется включать в веб-страницу ключ API поиска Bing, где он будет доступен для всех, кто просматривает исходный код.Otherwise, your Bing Search API key must be included in the Web page, where it is 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.

В целях разработки запрос к API Bing для поиска в Интернете можно выполнить через прокси-сервер CORS.For development purposes, you can make the Bing Web Search API request through a CORS proxy. Ответ от прокси-сервера содержит заголовок Access-Control-Expose-Headers, который поддерживает заголовки ответов и делает их доступными для JavaScript.The response from such a proxy has an Access-Control-Expose-Headers header that allows response headers and makes them available to JavaScript.

Установить прокси-сервер CORS довольно просто. Это позволит нашему учебному приложению иметь доступ к заголовку идентификатора клиента.It's easy to install a CORS proxy to allow our tutorial app to access the client ID header. Для начала установите платформу Node.js, если она еще не установлена.First, if you don't already have it, install Node.js. Затем введите следующую команду в командном окне:Then issue the following command in a command window:

npm install -g cors-proxy-server

После этого в HTML-файле измените конечную точку службы "Поиск в Интернете Bing" на следующую:Next, change the Bing Web Search endpoint in the HTML file to:

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

И наконец, запустите прокси-сервер CORS с помощью следующей команды:Finally, start the CORS proxy with the following command:

cors-proxy-server

Не закрывайте командное окно, пока используете учебное приложение. Это приведет к остановке прокси-сервера.Leave the command window open while you use the tutorial app; closing the window stops the proxy. Теперь в развертываемом разделе заголовков HTTP под результатами поиска можно увидеть заголовок X-MSEdge-ClientID (среди прочих) и убедиться, что он одинаковый для всех запросов.In the expandable HTTP Headers section below the search results, you can now see the X-MSEdge-ClientID header (among others) and verify that it is the same for each request.

Дополнительная информацияNext steps

Image Search API v7 reference (Руководство по API для поиска изображений версии 7)Extract Image details using the Bing Image Search API

См. такжеSee also