Condividi tramite


Esercitazione: Creare un'app a pagina singola usando l'API Ricerca Web Bing

Avvertimento

Il 30 ottobre 2020, le API di ricerca Bing sono state spostate dai servizi di intelligenza artificiale di Azure ai servizi di ricerca Bing. Questa documentazione è disponibile solo per riferimento. Per la documentazione aggiornata, vedere la Documentazione dell'API di Ricerca Bing. Per istruzioni sulla creazione di nuove risorse di Azure per Ricerca Bing, vedere Creare una risorsa di Ricerca Bing tramite Azure Marketplace.

Questa app a pagina singola illustra come recuperare, analizzare e visualizzare i risultati della ricerca dall'API Ricerca Web Bing. L'esercitazione utilizza i boilerplate di HTML e CSS e si concentra sul codice JavaScript. I file HTML, CSS e JS sono disponibili in GitHub con istruzioni di avvio rapido.

Questa app di esempio può:

  • Chiamare l'API Ricerca Web Bing con le opzioni di ricerca
  • Visualizzare risultati Web, immagine, notizie e video
  • Risultati impaginati
  • Gestire le chiavi di sottoscrizione
  • Gestire gli errori

Per usare questa app, è necessario un account dei servizi di intelligenza artificiale di Azure con le API di ricerca Bing.

Prerequisiti

Ecco alcune cose di cui avrai bisogno per eseguire l'app:

Il primo passaggio consiste nel clonare il repository con il codice sorgente dell'app di esempio.

git clone https://github.com/Azure-Samples/cognitive-services-REST-api-samples.git

Quindi eseguire npm install. Per questo tutorial, Express.js è l'unica dipendenza.

cd <path-to-repo>/cognitive-services-REST-api-samples/Tutorials/bing-web-search
npm install

Componenti dell'app

L'app di esempio che si sta creando è costituita da quattro parti:

  • bing-web-search.js: l'app Express.js. Gestisce la logica di richiesta/risposta e il routing.
  • public/index.html - Scheletro dell'app; definisce la modalità di presentazione dei dati all'utente.
  • public/css/styles.css: definisce gli stili di pagina, ad esempio tipi di carattere, colori, dimensioni del testo.
  • public/js/scripts.js: contiene la logica per effettuare richieste all'API Ricerca Web Bing, gestire le chiavi di sottoscrizione, gestire e analizzare le risposte e visualizzare i risultati.

Questa esercitazione è incentrata su scripts.js e sulla logica necessaria per chiamare l'API Ricerca Web Bing e gestire la risposta.

Modulo HTML

Il index.html include un modulo che consente agli utenti di cercare e selezionare le opzioni di ricerca. L'attributo onsubmit si attiva quando il modulo viene inviato, chiamando il metodo bingWebSearch() definito in scripts.js. Accetta tre argomenti:

  • Query di ricerca
  • Opzioni selezionate
  • Chiave di sottoscrizione
<form name="bing" onsubmit="return bingWebSearch(this.query.value,
    bingSearchOptions(this), getSubscriptionKey())">

Opzioni di query

Il modulo HTML include opzioni che corrispondono ai parametri di query nell'API di Ricerca Web di Bing v7 . Questa tabella fornisce una suddivisione del modo in cui gli utenti possono filtrare i risultati della ricerca usando l'app di esempio:

Parametro Descrizione
query Campo di testo per immettere una stringa di query.
where Menu a discesa per selezionare il mercato (località e lingua).
what Caselle di controllo per promuovere tipi specifici di risultati. La promozione delle immagini, ad esempio, aumenta la classificazione delle immagini nei risultati della ricerca.
when Menu a discesa che consente all'utente di limitare i risultati della ricerca a oggi, questa settimana o questo mese.
safe Casella di controllo per abilitare Bing SafeSearch, che filtra il contenuto per adulti.
count Campo nascosto. Numero di risultati della ricerca da restituire in ogni richiesta. Modificare questo valore per visualizzare meno o più risultati per pagina.
offset Campo nascosto. Offset del primo risultato della ricerca nella richiesta, che viene usato per la paginazione. Viene reimpostato per 0 con ogni nuova richiesta.

Nota

L'API Ricerca Web Bing offre parametri di query aggiuntivi per ottimizzare i risultati della ricerca. Questo esempio usa solo alcuni. Per un elenco completo dei parametri disponibili, vedere riferimento all'API Ricerca Web Bing v7.

La funzione bingSearchOptions() converte queste opzioni in modo che corrispondano al formato richiesto dall'API Ricerca Bing.

// Build query options from selections in the HTML form.
function bingSearchOptions(form) {

    var options = [];
    // Where option.
    options.push("mkt=" + form.where.value);
    // SafeSearch option.
    options.push("SafeSearch=" + (form.safe.checked ? "strict" : "moderate"));
    // Freshness option.
    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);
    // Promote option.
    if (what.length) {
        options.push("promote=" + what.join(","));
        options.push("answerCount=9");
    }
    // Count option.
    options.push("count=" + form.count.value);
    // Offset option.
    options.push("offset=" + form.offset.value);
    // Hardcoded text decoration option.
    options.push("textDecorations=true");
    // Hardcoded text format option.
    options.push("textFormat=HTML");
    return options.join("&");
}

SafeSearch può essere impostato su strict, moderateo off, con moderate come impostazione predefinita per Ricerca Web Bing. In questo modulo viene utilizzata una casella di controllo con due stati: strict o moderate.

Se una delle caselle di controllo Promuovi è selezionata, il parametro answerCount viene aggiunto alla query. answerCount è necessario quando si usa il parametro promote. In questo frammento di codice il valore è impostato su 9 per restituire tutti i tipi di risultati disponibili.

Nota

La promozione di un tipo di risultato non garantisce che verrà inclusa nei risultati della ricerca. Invece, la promozione aumenta la classificazione di questi tipi di risultati rispetto alla loro classificazione consueta. Per limitare le ricerche a determinati tipi di risultati, usare il parametro di query responseFilter oppure chiamare un endpoint più specifico, ad esempio Ricerca immagini Bing o Ricerca notizie Bing.

I parametri di query textDecoration e textFormat sono hardcoded nello script e fanno sì che il termine di ricerca venga evidenziato in grassetto nei risultati della ricerca. Questi parametri non sono obbligatori.

Gestire le chiavi di sottoscrizione

Per evitare di codificare in modo fisso la chiave di abbonamento dell'API Ricerca Bing, questa app di esempio utilizza la memoria permanente di un browser per conservarla. Se non viene archiviata alcuna chiave di sottoscrizione, all'utente viene richiesto di immetterne uno. Se la chiave di sottoscrizione viene rifiutata dall'API, all'utente viene richiesto di immettere nuovamente una chiave di sottoscrizione.

La funzione getSubscriptionKey() usa le funzioni di storeValue e retrieveValue per archiviare e recuperare la chiave di sottoscrizione di un utente. Queste funzioni utilizzano l'oggetto localStorage, se supportato, o i cookie.

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

BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/search";

// See source code for storeValue and retrieveValue definitions.

// Get stored subscription key, or prompt if it isn't 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;
}

Come illustrato in precedenza, quando il modulo viene inviato, onsubmit si attiva, chiamando bingWebSearch. Questa funzione inizializza e invia la richiesta. getSubscriptionKey viene chiamato su ogni invio per autenticare la richiesta.

Data la query, la stringa di opzioni e la chiave di sottoscrizione, la funzione BingWebSearch crea un oggetto XMLHttpRequest per chiamare l'endpoint di Ricerca Web Bing.

// Perform a search constructed from the query, options, and subscription key.
function bingWebSearch(query, options, key) {
    window.scrollTo(0, 0);
    if (!query.trim().length) return false;

    showDiv("noresults", "Working. Please wait.");
    hideDivs("pole", "mainline", "sidebar", "_json", "_http", "paging1", "paging2", "error");

    var request = new XMLHttpRequest();
    var queryurl = BING_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;

    // Initialize 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 errors.
    request.addEventListener("error", function() {
        renderErrorMessage("Error completing request");
    });

    // Event handler for an aborted request.
    request.addEventListener("abort", function() {
        renderErrorMessage("Request aborted");
    });

    // Send the request.
    request.send();
    return false;
}

Dopo una richiesta riuscita, il gestore eventi load viene attivato e chiama la funzione handleBingResponse. handleBingResponse analizza l'oggetto risultato, visualizza i risultati e contiene la logica di errore per le richieste non riuscite.

function handleBingResponse() {
    hideDivs("noresults");

    var json = this.responseText.trim();
    var jsobj = {};

    // Try to parse results object.
    try {
        if (json.length) jsobj = JSON.parse(json);
    } catch(e) {
        renderErrorMessage("Invalid JSON response");
        return;
    }

    // Show raw JSON and the 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 the HTTP response is 200 OK, try to render the 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 === "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 considered an error.
    else {
        // 401 is unauthorized; force a re-prompt for the user's subscription
        // key on the next request.
        if (this.status === 401) invalidateSubscriptionKey();

        // Some error responses don't have a top-level errors object, if absent
        // create one.
        var errors = jsobj.errors || [jsobj];
        var errmsg = [];

        // Display the 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]);
        }

        // Display Bing Trace ID if it isn't blocked by CORS.
        var traceid = this.getResponseHeader("BingAPIs-TraceId");
        if (traceid) errmsg.push("\nTrace ID " + traceid);

        // Display the error message.
        renderErrorMessage(errmsg.join("\n"));
    }
}

Importante

Una richiesta HTTP riuscita non significa che la ricerca stessa è riuscita. Se si verifica un errore nell'operazione di ricerca, l'API Ricerca Web Bing restituisce un codice di stato HTTP diverso da 200 e include informazioni sull'errore nella risposta JSON. Se la richiesta era limitata alla frequenza, l'API restituisce una risposta vuota.

Gran parte del codice in entrambe le funzioni precedenti è dedicata alla gestione degli errori. Gli errori possono verificarsi nelle fasi seguenti:

Fase Potenziali errori Gestito da
Compilazione dell'oggetto di richiesta URL non valido blocco try / catch
Effettuare la richiesta Errori di rete, connessioni interrotte gestori eventi error e abort
Esecuzione della ricerca Richiesta non valida, JSON non valido, limiti di frequenza Test nel gestore eventi load

Gli errori vengono gestiti chiamando renderErrorMessage(). Se la risposta supera tutti i test di errore, renderSearchResults() viene chiamato per visualizzare i risultati della ricerca.

Visualizzare i risultati della ricerca

Esistono requisiti di utilizzo e visualizzazione per i risultati restituiti dall'API Ricerca Web Bing. Poiché una risposta può includere vari tipi di risultati, non è sufficiente iterare attraverso la raccolta di WebPages di primo livello. Invece, l'app di esempio usa RankingResponse per ordinare i risultati secondo le specifiche.

Nota

Se si vuole un solo tipo di risultato, usare il parametro di query responseFilter o valutare l'uso di uno degli altri endpoint di Ricerca Bing, ad esempio Ricerca immagini Bing.

Ogni risposta ha un oggetto RankingResponse che può includere fino a tre raccolte: pole, mainlinee sidebar. pole, se presente, è il risultato di ricerca più rilevante e deve essere visualizzato in primo piano. mainline contiene la maggior parte dei risultati della ricerca e viene visualizzato immediatamente dopo pole. sidebar include risultati di ricerca ausiliari. Se possibile, questi risultati devono essere visualizzati nella barra laterale. Se i limiti dello schermo rendono una barra laterale poco pratica, questi risultati verranno visualizzati dopo i risultati mainline.

Ogni RankingResponse include una matrice di RankingItem che specifica la modalità di ordinamento dei risultati. L'app di esempio usa i parametri answerType e resultIndex per identificare il risultato.

Nota

Esistono altri modi per identificare e classificare i risultati. Per ulteriori informazioni, vedere Uso della classificazione per visualizzare i risultati.

Di seguito viene esaminato il codice:

// Render the search results from the JSON response.
function renderSearchResults(results) {

    // If spelling was corrected, update the search field.
    if (results.queryContext.alteredQuery)
        document.forms.bing.query.value = results.queryContext.alteredQuery;

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

    // Render the results for each section.
    for (section in {pole: 0, mainline: 0, sidebar: 0}) {
        if (results.rankingResponse[section])
            showDiv(section, renderResultsItems(section, results));
    }
}

La funzione renderResultsItems() esegue l'iterazione degli elementi in ogni raccolta RankingResponse, esegue il mapping di ogni risultato di classificazione a un risultato di ricerca usando i valori answerType e resultIndex e chiama la funzione di rendering appropriata per generare il codice HTML. Se resultIndex non è specificato per un elemento, renderResultsItems() esegue l'iterazione di tutti i risultati di tale tipo e chiama la funzione di rendering per ogni elemento. Il codice HTML risultante viene inserito nell'elemento <div> appropriato in index.html.

// Render search results from the RankingResponse object per rank response and
// use and display requirements.
function renderResultsItems(section, results) {

    var items = results.rankingResponse[section].items;
    var html = [];
    for (var i = 0; i < items.length; i++) {
        var item = items[i];
        // Collection name has lowercase first letter while answerType has uppercase
        // e.g. `WebPages` RankingResult type is in the `webPages` top-level collection.
        var type = item.answerType[0].toLowerCase() + item.answerType.slice(1);
        if (type in results && type in searchItemRenderers) {
            var render = searchItemRenderers[type];
            // This ranking item refers to ONE result of the specified type.
            if ("resultIndex" in item) {
                html.push(render(results[type].value[item.resultIndex], section));
            // This ranking item refers to ALL results of the specified type.
            } else {
                var len = results[type].value.length;
                for (var j = 0; j < len; j++) {
                    html.push(render(results[type].value[j], section, j, len));
                }
            }
        }
    }
    return html.join("\n\n");
}

Esaminare le funzioni del renderer

Nell'app di esempio l'oggetto searchItemRenderers include funzioni che generano codice HTML per ogni tipo di risultato della ricerca.

// Render functions for each result type.
searchItemRenderers = {
    webPages: function(item) { ... },
    news: function(item) { ... },
    images: function(item, section, index, count) { ... },
    videos: function(item, section, index, count) { ... },
    relatedSearches: function(item, section, index, count) { ... }
}

Importante

L'app di esempio include renderer per pagine Web, notizie, immagini, video e ricerche correlate. L'applicazione necessita di renderer per qualsiasi tipo di risultati che può ricevere, che può includere calcoli, suggerimenti ortografici, entità, fusi orari e definizioni.

Alcune funzioni di rendering accettano solo il parametro item. Altri accettano parametri aggiuntivi, che possono essere usati per eseguire il rendering degli elementi in modo diverso in base al contesto. Un renderer che non usa queste informazioni non deve accettare questi parametri.

Gli argomenti di contesto sono:

Parametro Descrizione
section Sezione dei risultati (pole, mainlineo sidebar) in cui viene visualizzato l'elemento.
index
count
Disponibile quando l'elemento RankingResponse specifica che tutti i risultati di una determinata raccolta devono essere visualizzati; undefined in caso contrario. Indice dell'elemento all'interno della raccolta e numero totale di elementi nell'insieme. È possibile usare queste informazioni per numerare i risultati, generare codice HTML diverso per il primo o l'ultimo risultato e così via.

Nell'app di esempio i renderer images e relatedSearches usano gli argomenti di contesto per personalizzare il codice HTML generato. Esaminiamo più in dettaglio il renderer images:

searchItemRenderers = {
    // Render image result with thumbnail.
    images: function(item, section, index, count) {
        var height = 60;
        var width = Math.round(height * item.thumbnail.width / item.thumbnail.height);
        var html = [];
        if (section === "sidebar") {
            if (index) html.push("<br>");
        } else {
            if (!index) html.push("<p class='images'>");
        }
        html.push("<a href='" + item.hostPageUrl + "'>");
        var title = escape(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>");
        return html.join("");
    },
    // Other renderers are omitted from this sample...
}

Il renderizzatore di immagini

  • Calcola le dimensioni dell'anteprima dell'immagine (la larghezza varia, mentre l'altezza è fissa a 60 pixel).
  • Inserisce il codice HTML che precede il risultato dell'immagine in base al contesto.
  • Compila il tag html <a> che collega alla pagina che contiene l'immagine.
  • Compila il tag html <img> per visualizzare l'anteprima dell'immagine.

Il renderer di immagini usa le variabili section e index per visualizzare i risultati in modo diverso a seconda della posizione in cui vengono visualizzati. Un'interruzione di riga (tag<br>) viene inserita tra i risultati immagine nella barra laterale, in modo che nella barra laterale venga visualizzata una colonna di immagini. In altre sezioni il primo risultato dell'immagine (index === 0) è preceduto da un tag <p>.

Le dimensioni dell'anteprima vengono usate sia nel tag <img> che nei campi h e w nell'URL dell'anteprima. Gli attributi title e alt (una descrizione testuale dell'immagine) vengono costruiti dal nome dell'immagine e dal nome host nell'URL.

Ecco un esempio di come vengono visualizzate le immagini nell'app di esempio:

[risultati immagine Bing]

Rendere persistente l'ID client

Le risposte dalle API di ricerca Bing possono includere un'intestazione X-MSEdge-ClientID che deve essere inviata all'API con ogni richiesta successiva. Se più API di ricerca Bing vengono usate dall'app, assicurarsi che lo stesso ID client venga inviato con ogni richiesta tra i servizi.

Fornire l'intestazione X-MSEdge-ClientID consente alle API Bing di associare le ricerche di un utente. In primo luogo, consente al motore di ricerca Bing di applicare il contesto passato alle ricerche per trovare i risultati che soddisfano meglio la richiesta. Se in precedenza un utente ha cercato termini relativi alla navigazione, ad esempio, una ricerca successiva di "nodi" potrebbe restituire preferibilmente informazioni sui nodi utilizzati nella vela. In secondo luogo, Bing può selezionare in modo casuale gli utenti per sperimentare nuove funzionalità prima che vengano rese ampiamente disponibili. Specificando lo stesso ID client con ogni richiesta, gli utenti che sono stati scelti per visualizzare una funzionalità lo vedranno sempre. Senza l'ID client, l'utente potrebbe visualizzare una funzionalità e scomparire, apparentemente in modo casuale, nei risultati della ricerca.

I criteri di sicurezza del browser, ad esempio condivisione di risorse tra le origini (CORS), potrebbero impedire all'app di esempio di accedere all'intestazione X-MSEdge-ClientID. Questa limitazione si verifica quando la risposta di ricerca ha un'origine diversa dalla pagina che l'ha richiesta. In un ambiente di produzione, è necessario gestire questo criterio ospitando uno script lato server che esegue la chiamata API nello stesso dominio della pagina Web. Poiché lo script ha la stessa origine della pagina Web, l'intestazione X-MSEdge-ClientID è quindi disponibile per JavaScript.

Nota

In un'applicazione Web di produzione, è necessario eseguire comunque il lato server di richiesta. In caso contrario, la chiave di sottoscrizione dell'API Ricerca Bing deve essere inclusa nella pagina web, dove è accessibile a chiunque visualizzi il codice sorgente. Vengono fatturati tutti gli utilizzi con la chiave di sottoscrizione API, anche le richieste effettuate da parti non autorizzate, quindi è importante non esporre la chiave.

A scopo di sviluppo, è possibile effettuare una richiesta tramite un proxy CORS. La risposta da questo tipo di proxy ha un'intestazione Access-Control-Expose-Headers che filtra le intestazioni di risposta e le rende disponibili per JavaScript.

È facile installare un proxy CORS per consentire all'app di esempio di accedere all'intestazione DELL'ID client. Eseguire questo comando:

npm install -g cors-proxy-server

Modificare quindi l'endpoint di Ricerca Web Bing in script.js in:

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

Avviare il proxy CORS con questo comando:

cors-proxy-server

Lasciare aperta la finestra di comando mentre si usa l'app di esempio; la chiusura della finestra arresta il proxy. Nella sezione Intestazioni HTTP espandibili sotto i risultati della ricerca, l'intestazione X-MSEdge-ClientID deve essere visibile. Verificare che sia lo stesso per ogni richiesta.

Passaggi successivi

di riferimento dell'API Ricerca Web Bing v7