Tutorial: aplicação de página única da Pesquisa de Vídeos

Aviso

A 30 de outubro de 2020, as APIs de Pesquisa do Bing passaram dos serviços de IA do Azure para os Serviços Pesquisa do Bing. Esta documentação é fornecida apenas para referência. Para obter documentação atualizada, veja a documentação da API de pesquisa do Bing. Para obter instruções sobre como criar novos recursos do Azure para a pesquisa do Bing, veja Criar um recurso de Pesquisa do Bing através do Azure Marketplace.

A API de Pesquisa de Vídeos do Bing permite-lhe pesquisar na Web e obter resultados de vídeos relevantes para uma consulta de pesquisa. Neste tutorial, vamos compilar uma aplicação Web de página única que utiliza a API de Pesquisa do Bing para apresentar resultados da pesquisa na página. A aplicação inclui componentes HTML, CSS e JavaScript.

Nota

Clicar nos cabeçalhos JSON e HTTP na parte inferior da página mostra a resposta JSON e as informações do pedido HTTP. Estes detalhes podem ser úteis para explorar o serviço.

Resultados não processados de JSON e HTTP

Esta aplicação de tutorial ilustra como:

  • Fazer uma chamada à API de Pesquisa de Vídeos do Bing em JavaScript
  • Transmitir opções de pesquisa à API de Pesquisa do Bing
  • Apresentar resultados da pesquisa de vídeo ou, opcionalmente, incluir páginas Web, notícias ou imagens
  • Procurar intervalos de tempo de 24 horas, semana passada, mês ou todo o tempo disponível
  • Navegar pelos resultados da pesquisa
  • Gerir o ID de cliente do Bing e a chave de subscrição da API
  • Resolver erros que possam aparecer

A página do tutorial é completamente independente. Não utiliza arquiteturas, folhas de estilo nem ficheiros de imagens externos. Só utiliza funcionalidades da linguagem JavaScript amplamente suportadas e funciona com as versões atuais dos principais browsers.

Neste tutorial, vamos abordar determinadas partes do código de origem. O código de origem completo está disponível. Para executar o exemplo, copie e cole o código de origem num editor de texto e guarde-o como bing.html.

Componentes da aplicação

Tal como qualquer aplicação Web de página única, esta aplicação de tutorial tem três partes:

  • HTML - define a estrutura e os conteúdos da página
  • CSS - define o aspeto da página
  • JavaScript - define o comportamento da página

A maioria do código HTML e do CSS é convencional, pelo que não são abordados no tutorial. O HTML contém o formulário de pesquisa no qual o utilizador introduz uma consulta e escolhe as opções de pesquisa. O formulário está ligado ao JavaScript que faz a pesquisa com o atributo onsubmit da tag <form>:

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

O processador onsubmit devolve false, que impede o formulário de ser submetido para um servidor. O código JavaScript recolhe as informações necessárias do formulário e executa a pesquisa.

O HTML também inclui as divisões (tags <div> de HTML) nas quais os resultados da pesquisa aparecem.

Gerir a chave de subscrição

Para evitar ter de incluir a chave de subscrição da API de Pesquisa do Bing no código, armazenamo-la no armazenamento persistente do browser. Antes de a chave ser armazenada, pedimos a chave do utilizador. Se a API rejeitar a chave mais tarde, invalidamos a chave armazenada e voltamos a pedi-la ao utilizador.

Definimos as funções storeValue e retrieveValue que utilizam o objeto localStorage (não suportada em todos os browsers) ou um cookie. A função getSubscriptionKey() utiliza essas funções para armazenar e obter a chave do utilizador.

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

// ... omitted definitions of store value and retrieve value
// Browsers differ in their support for persistent storage by 
// local HTML files. See the source code for browser-specific
// options.

// Get 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;
}

A tag <form> de HTML onsubmit chama a função bingWebSearch, para devolver os resultados da pesquisa. bingWebSearch utiliza getSubscriptionKey() para autenticar cada consulta. Conforme mostrado na definição anterior, getSubscriptionKey pede a chave ao utilizador caso a mesma não tenha sido introduzida. Depois, a chave é armazenada e utilizada continuamente pela aplicação.

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

Selecionar as opções de pesquisa

A imagem abaixo mostra a caixa de texto da consulta e as opções que definem a pesquisa.

Opções de Pesquisa de Notícias do Bing

O formulário HTML inclui elementos com os nomes abaixo:

Elemento Descrição
where Um menu pendente para selecionar o mercado (localização e idioma) utilizado para a pesquisa.
query O campo de texto no qual introduzir os termos da pesquisa.
modules Caixas de verificação para promover módulos específicos de resultados, todos os resultados ou vídeos relacionados.
when Menu pendente para limitar, opcionalmente, a pesquisa ao dia, semana ou mês mais recente.
safe Uma caixa de verificação que indica se deve ser utilizada a funcionalidade SafeSearch do Bing para filtrar resultados de conteúdos para adultos.
count Campo oculto. O número de resultados de pesquisa a devolver em cada pedido. Altere-o para mostrar menos ou mais resultados por página.
offset Campo oculto. O desfasamento do primeiro resultado da pesquisa no pedido, utilizado para paginação. É reposto para 0 nos pedidos novos.

Nota

A Pesquisa na Web do Bing disponibiliza outros parâmetros de consulta. Só vamos utilizar alguns desses parâmetros.

// build query options from the HTML form
// 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" : "moderate"));

    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);
    if (what.length) {
        options.push("modules=" + what.join(","));
        options.push("answerCount=9");
    }
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    options.push("textDecorations=true");
    options.push("textFormat=HTML");
    return options.join("&");
}

Por exemplo, o SafeSearch parâmetro numa chamada à API real pode ser strict, ou moderate, sendo moderate a predefinição.

Fazer o pedido

Tendo a consulta, a cadeia de opções e a chave de API, a função BingWebSearch utiliza um objeto XMLHttpRequest para fazer o pedido ao ponto final da Pesquisa do Bing. Pode utilizar o ponto final global abaixo ou o ponto final de subdomínio personalizado apresentado no portal do Azure do recurso.

// Search on the query, using search options, authenticated by the key.
function bingWebSearch(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("pole", "mainline", "sidebar", "_json", "_headers", "paging1", "paging2", "error");

    var endpoint = "https://api.cognitive.microsoft.com/bing/v7.0/videos/search";
    var request = new XMLHttpRequest();
    var queryurl = endpoint + "?q=" + encodeURIComponent(query) + "&" + options;

    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", handleOnLoad);

    // 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;
}

Após a conclusão do pedido HTTP, o JavaScript chama o processador de eventos load, handleOnLoad(), para processar um pedido HTTP GET bem-sucedido à API.

// handle Bing search request results
function handleOnLoad() {
    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 headers
    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 === "Videos") {//"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 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"));
    }
}

Importante

Se ocorrer um erro na operação de pesquisa, a API de Pesquisa de Notícias do Bing devolve um código de estado HTTP que não 200 e inclui as informações do erro na resposta JSON. Além disso, se o pedido tiver uma limitação de frequência, a API devolve uma resposta vazia. Um pedido HTTP bem-sucedido não significa necessariamente que a pesquisa em si foi concluída com êxito.

A maioria do código nas funções anteriores é dedicado ao processamento de erros. Poderão ocorrer erros nas seguintes fases:

Fase Potenciais erros Resolvido por
Criar o objeto de pedido JavaScript URL inválido bloco try/catch
Fazer o pedido Erros de rede, ligações abortadas Processadores de eventos error e abort
Fazer a pesquisa Pedido inválido, JSON inválido, limites de frequência testes no processador de eventos load

Os erros são processados mediante a chamada de renderErrorMessage() com todos os detalhes dos mesmos conhecidos. Se a resposta passar todos os testes de erros, chamamos renderSearchResults() para apresentar os resultados da pesquisa na página.

Apresentar os resultados da pesquisa

A principal função para apresentar os resultados da pesquisa é renderSearchResults(). Esta função utiliza o JSON que o serviço Pesquisa de Notícias do Bing devolve e compõe os resultados de notícias e as pesquisas relacionadas, se existirem.

// render the search results given the parsed JSON response
function renderSearchResults(results) {

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

    // Render the results to the mainline section
    for (section in { mainline: 0 }) {
         showDiv(section, renderResultsItems(section, results));
    }
}

Os resultados da pesquisa são devolvidos como o objeto value de nível superior na resposta JSON. Transmitimo-los à nossa função renderResultsItems(), que itera pelos mesmos e chama uma função para compor cada item em HTML. O HTML resultante é devolvido a renderSearchResults(), onde é inserido na divisão results da página.

// render search results
    function renderResultsItems(section, results) {   

        var items = results.value;
        var html = [];
        for (var i = 0; i < items.length; i++) { 
            var item = items[i];
            // collection name has lowercase first letter
            var type = "videos";
            var render = searchItemRenderers[type];
            html.push(render(item, section));  
        }
    return html.join("\n\n");
}

A API de Pesquisa de Notícias do Bing devolve até quatro tipos diferentes de resultados relacionados, cada um no seu próprio objeto de nível superior. A saber:

Relação Description
pivotSuggestions Consultas que substituem uma palavra “pivô” na pesquisa original por outra diferente. Por exemplo, se procurar "flores vermelhas", uma palavra pivô poderá ser "vermelhas" e uma sugestão pivô "flores amarelas".
queryExpansions Consultas que reduzem a consulta original mediante a adição de mais termos. Por exemplo, se procurar "Microsoft Surface", uma expansão da consulta poderá ser "Microsoft Surface Pro".
relatedSearches Consultas que também foram introduzidas por outros utilizadores que introduziram a pesquisa original. Por exemplo, se procurar "Monte Rainier", uma consulta relacionada poderá ser "Monte de Santa Helena".
similarTerms Consultas cujo significado é semelhante ao da pesquisa original. Por exemplo, se pesquisar por “escolas”, um termo semelhante poderá ser “educação”.

Conforme vimos anteriormente em renderSearchResults(), só convertermos as sugestões relatedItems e colocamos as ligações resultantes na barra lateral da página.

Compor os itens dos resultados

No código JavaScript, o objeto searchItemRenderers pode conter compositores, funções que geram o HTML de cada tipo de resultado da pesquisa. A página da pesquisa de vídeo só utiliza videos. Relativamente aos diversos tipos de compositores, veja os outros tutoriais.

searchItemRenderers = {
	news: function(item) { ... },
	webPages: function (item) { ... }, 
    images: function(item, index, count) { ... },
    videos: function (item, section, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Uma função de compositor pode aceitar os seguintes parâmetros:

Parâmetro Description
item O objeto JavaScript que contém as propriedades do item, como o URL e a descrição.
index O índice do item do resultado dentro da respetiva coleção.
count O número de itens na coleção do item do resultado da pesquisa.

Os parâmetros index e count podem ser utilizados para numerar os resultados, para gerar código HTML especial para o início ou o fim de uma coleção, para inserir quebras de linha após um determinado número de itens, etc. Se um compositor não precisar desta funcionalidade, não tem de aceitar estes dois parâmetros.

O video compositor é apresentado no seguinte excerto de JavaScript. Ao utilizar o ponto final dos Vídeos, todos os resultados são do tipo Videos. searchItemRenderers são apresentados no segmento de código seguinte.

// render functions for various types of search results
    searchItemRenderers = {

    videos: function (item, section, index, count) {
        var height = 60;
        var width = Math.round(height * item.thumbnail.width / item.thumbnail.height);
        var html = [];

        html.push("<p class='images'>");
        html.push("<a href='" + item.hostPageUrl + "'>");
        var title = escapeQuotes(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>");
        html.push("<br>");
        html.push("<nobr><a href='" + item.contentUrl + "'>Video page source</a> - ");
        html.push(title.replace("\n", " (").replace(/([a-z0-9])\.([a-z0-9])/g, "$1.<wbr>$2") + ")</p>");
        return html.join("");
    }
}

A função de compositor:

  • Cria uma tag de parágrafo, atribui-a à classe images e envia-a para a matriz html.
  • Calcula o tamanho da miniatura de imagem (a largura é fixa, com 60 pixéis, e a altura é calculada proporcionalmente).
  • Cria a etiqueta HTML <img> para apresentar a miniatura da imagem.
  • Cria as tags <a> de HTML que ligam à imagem e à página que a contém.
  • Cria a descrição que apresenta as informações sobre a imagem e o site no qual a imagem se encontra.

O tamanho da miniatura é utilizado na etiqueta <img> e nos campos h e w do respetivo URL. O Bing devolverá uma miniatura exatamente desse tamanho.

ID de cliente persistente

As respostas das APIs de Pesquisa do Bing podem incluir um cabeçalho X-MSEdge-ClientID, o qual deve ser reenviado à API com os sucessivos pedidos. Se estiverem a ser utilizadas várias APIs de Pesquisa do Bing, deve ser utilizado o mesmo ID de cliente em todas as APIs, se possível.

Fornecer o cabeçalho X-MSEdge-ClientID permite às APIs do Bing associarem todas as pesquisas de um determinado utilizador, o que tem duas importantes vantagens.

Em primeiro lugar, permite que o motor de busca do Bing aplique um contexto passado às pesquisas para encontrar resultados que deixem o utilizador mais satisfeito. Se um utilizador tiver procurado termos relacionados com vela, por exemplo, ao procurar posteriormente a palavra "nós" poderá devolver, de preferência, informações sobre os nós utilizados em vela.

Em segundo lugar, o Bing pode selecionar utilizadores aleatoriamente para experimentarem funcionalidades novas antes de serem disponibilizadas ao grande público. Fornecer o mesmo ID de cliente em todos os pedidos garante que os utilizadores que veem uma funcionalidade a verão sempre. Sem o ID de cliente, os utilizadores poderão ver a funcionalidade aparecer e desaparecer, de forma aparentemente aleatória, nos resultados da pesquisa.

As políticas de segurança do browser (CORS) podem impedir que o cabeçalho X-MSEdge-ClientID esteja disponível para o JavaScript. Esta limitação ocorre quando a origem da resposta da pesquisa é diferente da página que a pediu. Num ambiente de produção, deve abordar esta política ao alojar um script do lado do servidor que faça a chamada à API no mesmo domínio da página Web. Uma vez que a origem do script é a mesma da página Web, o cabeçalho X-MSEdge-ClientID ficará disponível para o JavaScript.

Nota

Numa aplicação Web de produção, deve fazer o pedido no lado do servidor. Caso contrário, a chave da API de Pesquisa do Bing tem de ser incluída na página Web, onde ficará disponível para qualquer pessoa que veja a origem. São-lhe cobradas todas as utilizações feitas com a sua chave de subscrição da API, mesmo os pedidos feitos por partes não autorizadas, pelo que é importante que não revele a sua chave.

Para fins de programação, pode fazer o pedido da API de Pesquisa na Web do Bing através de um proxy do CORS. A resposta de um proxy deste tipo tem um Access-Control-Expose-Headers cabeçalho que permite cabeçalhos de resposta e os disponibiliza para JavaScript.

É fácil instalar um proxy do CORS para permitir que a nossa aplicação de tutorial aceda ao cabeçalho do ID de cliente. Em primeiro lugar, se ainda não o tiver, instale Node.js. Em seguida, emita o comando seguinte numa janela de comando:

npm install -g cors-proxy-server

Depois, altere o ponto final da Pesquisa na Web do Bing no ficheiro HTML para:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Por fim, inicie o proxy do CORS com o comando seguinte:

cors-proxy-server

Deixe a janela de comando aberta enquanto utiliza a aplicação de tutorial. Se a janela for fechada, o proxy é interrompido. Na secção Cabeçalhos HTTP expansíveis, abaixo dos resultados da pesquisa, pode agora ver o cabeçalho X-MSEdge-ClientID (entre outros) e confirmar se é o mesmo em todos os pedidos.

Passos seguintes

Bing Video Search API reference (Referência da API de Pesquisa de Vídeos do Bing)