Руководство. Создание одностраничного приложения с помощью 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
, предотвращая отправку формы.
Выбор параметров поиска
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
(среди прочего) и убедиться, что он одинаковый для каждого запроса.