Поделиться через


Руководство. Создание одностраничного приложения с помощью API поиска изображений Bing

Предупреждение

30 октября 2020 г. API поиска Bing перемещены из служб ИИ Azure в службы поиска Bing. Эта документация предоставляется только для справки. Обновленную информацию см. в документации по API Поиска Bing. Инструкции по созданию ресурсов Azure для Поиска Bing приведены в статье Создание ресурса для Поиска Bing с помощью Azure Marketplace.

API поиска изображений Bing позволяет искать в Интернете высококачественные изображения. Используйте это руководство для создания одностраничного веб-приложения, которое может отправлять поисковые запросы в API и отображать результаты на веб-странице. Это руководство похоже на соответствующее руководство по поиску в Интернете Bing.

В приложении учебника показано, как:

  • Выполнение вызова API поиска изображений Bing в JavaScript
  • Улучшение результатов поиска с помощью параметров поиска
  • Просмотр и перелистывание результатов поиска
  • Запросите и обработайте ключ подписки API и идентификатор клиента Bing.

Предпосылки

  • Последняя версия Node.js.
  • Платформа Express.js для Node.js. Инструкции по установке исходного кода доступны в примере файла readme GitHub.

Управляйте и храните ключи подписок пользователей

Это приложение использует постоянное хранилище веб-браузеров для хранения ключей подписки API. Если ключ не хранится, веб-страница предложит пользователю ввести ключ и сохранить его для последующего использования. Если ключ позже отклоняется API, приложение удаляет его из хранилища. В этом примере используется глобальная конечная точка. Вы также можете использовать конечную точку пользовательского поддомена, отображаемую на портале Azure для вашего ресурса.

Определите storeValue и retrieveValue функции для использования localStorage объекта (если браузер поддерживает его) или файла 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.


// 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 для возврата результатов поиска. bingWebSearch использует getSubscriptionKey для проверки подлинности каждого запроса. Как показано в предыдущем определении, getSubscriptionKey запрашивает пользователю ключ, если ключ не введен. Затем ключ сохраняется для продолжения использования приложением.

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

Отправка поисковых запросов

Это приложение использует HTML <form> для первоначальной отправки запросов поиска пользователей, используя onsubmit атрибут для вызова newBingImageSearch().

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

По умолчанию onsubmit обработчик возвращает false, предотвращая отправку формы.

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

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

API поиска изображений Bing предлагает несколько параметров запроса фильтра , чтобы сузить и фильтровать результаты поиска. Html-форма в этом приложении использует и отображает следующие параметры параметров:

Вариант Описание
where Раскрывающееся меню для выбора рынка (расположения и языка), используемого для поиска.
query Текстовое поле, в котором нужно ввести условия поиска.
aspect Переключатели для выбора пропорций найденных изображений: примерно квадратный, широкий или высокий.
color
when Раскрывающееся меню для дополнительного ограничения поиска до последнего дня, недели или месяца.
safe Флажок, указывающий, следует ли использовать функцию SafeSearch Bing для фильтрации результатов "взрослых".
count Скрытое поле. Количество результатов поиска, возвращаемых по каждому запросу. Измените значение, чтобы отобразить меньше или больше результатов на страницу.
offset Скрытое поле. Смещение первого результата поиска в запросе; используется для разбиения по страницам. Он сбрасывается на 0 при новом запросе.
nextoffset Скрытое поле. После получения результата поиска это поле задается значением nextOffset в ответе. Использование этого поля позволяет избежать перекрывающихся результатов на последующих страницах.
stack Скрытое поле. Список смещения предыдущих страниц результатов поиска в кодировке JSON для перехода на предыдущие страницы.

Функция bingSearchOptions() форматирует эти параметры в частичной строке запроса, которая может использоваться в запросах 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.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("&");
}

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

Используя поисковый запрос, строку параметров и ключ API, BingImageSearch() функция использует объект XMLHttpRequest для выполнения запроса к конечной точке поиска изображений Bing.

// 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() событий load для обработки успешного 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 === "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-запросы могут содержать неудачные сведения о поиске. Если во время операции поиска возникает ошибка, API поиска изображений Bing вернет код состояния HTTP, отличный от 200, и сведения об ошибке в ответе JSON. Кроме того, если запрос был ограничен скоростью, API вернет пустой ответ.

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

Результаты поиска отображаются renderSearchResults() функцией, которая принимает JSON, возвращенный службой поиска изображений Bing, и вызывает соответствующую функцию отрисовщика для всех возвращаемых изображений и связанных поисковых запросов.

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. Они передаются в renderImageResults(), который выполняет итерацию результатов и преобразует каждый элемент в 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 может возвращать четыре типа предложений поиска, помогающих пользователям выполнять поиск, каждый из которых содержит собственный объект верхнего уровня:

Предложение Описание
pivotSuggestions Запросы, в которых исходное ключевое слово заменяется другим. Например, если вы ищете "красные цветы", ключевым словом может быть "красные", а предложением-заменой может быть "желтые цветы".
queryExpansions Запросы, сужающие исходный поиск, добавляя дополнительные термины. Например, если вы ищете "Microsoft Surface", расширение запроса может быть "Microsoft Surface Pro".
relatedSearches Запросы, которые также были введены другими пользователями, которые ввели исходный поиск. Например, если вы ищете «гора Рейнир», связанный поиск может быть «г. Рейнир». Сент-Хеленс".
similarTerms Запросы, аналогичные исходному поиску. Например, если вы ищете «котята», аналогичный термин может быть «милые».

Это приложение отображает только предложения relatedItems и помещает полученные ссылки в боковую панель страницы.

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

В этом приложении searchItemRenderers объект содержит функции отрисовщика, которые создают HTML для каждого типа результата поиска.

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

Эти функции отрисовщика принимают следующие параметры:

Параметр Описание
item Объект JavaScript, содержащий свойства элемента, например URL-адрес и его описание.
index Индекс элемента результата в коллекции.
count Количество элементов в коллекции результатов поиска.

Параметры index и count используются для нумерации результатов, создания HTML для коллекций и упорядочивания содержимого. В частности, это:

  • Вычисляет размер эскиза изображения (ширина изменяется, начиная от 120 пикселей, а высота фиксирована на уровне 90 пикселей).
  • Создает тег HTML-<img> для отображения эскиза изображения.
  • Создает теги HTML-<a>, которые связываются с изображением и страницей, содержащей ее.
  • Создает описание, отображающее сведения о изображении и сайте, на котором он находится.
    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-адресе эскиза. Это позволяет Bing отобразить эскиз точно такого размера.

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

Ответы от API поиска Bing могут содержать X-MSEdge-ClientID заголовок, который должен быть отправлен обратно в API с последовательными запросами. Если используются несколько API-интерфейсов поиска Bing, при возможности следует использовать один и тот же идентификатор клиента.

X-MSEdge-ClientID Предоставление заголовка позволяет API Bing связать все поисковые запросы пользователя, что полезно в

Во-первых, он позволяет поисковой системе Bing применять прошлый контекст к поиску результатов, которые лучше удовлетворяют пользователю. Если пользователь ранее искал термины, связанные с парусным спортом, например, более поздний поиск "узлов" может предпочтительно возвращать информацию о узлах, используемых в парусном спорте.

Во-вторых, Bing может случайным образом выбрать пользователей для взаимодействия с новыми функциями, прежде чем они становятся широко доступными. Предоставление одного и того же идентификатора клиента с каждым запросом гарантирует, что пользователи, выбранные для просмотра функции, всегда видят ее. Без идентификатора клиента пользователь может увидеть, что функция появится и исчезнет, казалось бы, случайным образом в результатах поиска.

Политики безопасности браузера (CORS) могут предотвратить доступность заголовка X-MSEdge-ClientID в JavaScript. Это ограничение возникает, когда ответ поиска имеет другой источник от страницы, запрашивающей ее. В рабочей среде следует решить эту политику, размещая серверный скрипт, который вызывает API в том же домене, что и веб-страница. Так как скрипт имеет тот же источник, что и веб-страница, X-MSEdge-ClientID заголовок будет доступен для JavaScript.

Примечание.

В рабочем веб-приложении запрос следует выполнять на стороне сервера в любом случае. В противном случае ключ API поиска Bing должен быть включен на веб-страницу, где он доступен любому, кто просматривает источник. Вам выставляется счет за все использование, связанное с ключом подписки API, включая запросы, сделанные несанкционированными сторонами, поэтому важно не разглашать ключ.

В целях разработки можно сделать запрос API Bing для поиска в Интернете с помощью прокси-сервера CORS. Ответ от такого прокси-сервера содержит заголовок Access-Control-Expose-Headers, который обеспечивает доступ к заголовкам ответов и предоставляет их для JavaScript.

Легко установить прокси-сервер CORS, чтобы разрешить нашему учебному приложению обращаться к заголовку идентификатора клиента. Во-первых, если у вас его еще нет, установите Node.js. Затем выполните следующую команду в командном окне:

npm install -g cors-proxy-server

Затем измените конечную точку для веб-поиска Bing в файле HTML на следующую:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Наконец, запустите прокси-сервер CORS с помощью следующей команды:

cors-proxy-server

Оставьте окно командной строки открытым при использовании приложения учебника; закрытие окна останавливает прокси-сервер. В разделе расширенных заголовков HTTP под результатами поиска теперь можно увидеть заголовок X-MSEdge-ClientID (среди прочего) и убедиться, что он одинаковый для каждого запроса.

Дальнейшие действия

См. также