Руководство по Создание одностраничного веб-приложения с помощью API Bing для поиска изображений
Предупреждение
30 октября 2020 г. API-интерфейсы Поиск Bing перемещены из служб ИИ Azure в службы Поиск Bing. Эта документация приводится только для справки. Обновленную информацию см. в документации по API Поиска Bing. Инструкции по созданию ресурсов Azure для Поиска Bing приведены в статье Создание ресурса для Поиска Bing с помощью Azure Marketplace.
API Bing для поиска изображений позволяет искать в Интернете соответствующие изображения высокого качества. Используйте это руководство для создания одностраничного веб-приложения, которое может отправлять поисковые запросы к API и отображать результаты на веб-странице. Это руководство аналогично соответствующему руководству по API Bing для поиска в Интернете.
На примере учебного приложения показано, как выполнить такие задачи:
- Вызов API Bing для поиска изображений из JavaScript.
- Улучшение результатов поиска с помощью параметров поиска.
- Отображение и переход по страницам результатов поиска.
- Запрос и обработка идентификатора клиента Bing и ключа подписки API.
Предварительные требования
- Последняя версия Node.js.
- Платформа Express.js для Node.js. Инструкции по установке для исходного кода доступны в файле сведений примера на 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
, что препятствует отправке формы.
Выбор параметров поиска
API Bing для поиска изображений предоставляет несколько параметров запроса фильтров для сужения и фильтрации результатов поиска. HTML-формы в этом приложении используют и отображают следующие параметры:
Параметр | Описание |
---|---|
where |
Раскрывающееся меню для выбора рынка (расположения и языка), который используется для поиска. |
query |
Текстовое поле для ввода условий поиска. |
aspect |
Переключатели для выбора пропорций найденных изображений: примерно квадратные, вертикальные или горизонтальны. |
color |
|
when |
Раскрывающееся меню для ограничения поиска результатами за последний день, неделю или месяц (необязательный параметр). |
safe |
Флажок, указывающий, следует ли использовать функцию безопасного поиска 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()
, чтобы передать успешный 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
После этого в HTML-файле измените конечную точку службы "Поиск в Интернете Bing" на следующую:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search
И наконец, запустите прокси-сервер CORS с помощью следующей команды:
cors-proxy-server
Не закрывайте командное окно, пока используете учебное приложение. Это приведет к остановке прокси-сервера. Теперь в развертываемом разделе заголовков HTTP под результатами поиска можно увидеть заголовок X-MSEdge-ClientID
(среди прочих) и убедиться, что он одинаковый для всех запросов.
Дальнейшие действия
Image Search API v7 reference (Руководство по API для поиска изображений версии 7)