Dela via


Självstudier: Skapa en ensidesapp med hjälp av API för webbsökning i Bing

Varning

Den 30 oktober 2020 flyttade Bing-sökning API:er från Azure AI-tjänster till Bing-sökning Services. Den här dokumentationen tillhandahålls endast som referens. Uppdaterad dokumentation finns i dokumentationen för API:et för Bing-sökning. Anvisningar om hur du skapar nya Azure-resurser för Bing-sökning finns i Skapa en Bing-sökning resurs via Azure Marketplace.

Den här ensidesappen visar hur du hämtar, analyserar och visar sökresultat från API för webbsökning i Bing. Självstudien använder formaterad HTML och CSS och fokuserar på JavaScript-koden. HTML-, CSS- och JS-filer finns på GitHub med snabbstartsinstruktioner.

Den här exempelappen kan:

  • Anropa API för webbsökning i Bing med sökalternativ
  • Visa webb, bild, nyheter och videoresultat
  • Sidnumrera resultat
  • Hantera prenumerationsnycklar
  • Hantera fel

Om du vill använda den här appen krävs ett Azure AI-tjänstkonto med Bing-sökning API:er.

Förutsättningar

Här följer några saker som du kan behöva för att köra appen:

Det första steget är att klona lagringsplatsen med exempelappens källkod.

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

Kör sedan npm install. För den här självstudien är Express.js det enda beroendet.

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

Appkomponenter

Exempelappen som vi bygger består av fyra delar:

  • bing-web-search.js - Vår Express.js-app. Den hanterar begäran/svarslogik och routning.
  • public/index.html - Stommen i vår app. Den definierar hur data ska visas för användaren.
  • public/css/styles.css -Definierar sidans format, såsom teckensnitt, färger, textstorlek.
  • public/js/scripts.js - Innehåller logik för att göra begäranden till API för webbsökning i Bing, hantera prenumerationsnycklar, hantera och parsa svar och visa resultat.

Den här självstudien fokuserar på scripts.js och den logik som krävs för att anropa API för webbsökning i Bing och hantera svaret.

HTML-formulär

index.html innehåller ett formulär som gör det möjligt för användare att söka efter och välja alternativ för sökning. Attributet onsubmit utlöses när formuläret skickas och anropar den bingWebSearch()-metod som definieras i scripts.js. Den tar tre argument:

  • Sökfråga
  • Valda alternativ
  • Prenumerationsnyckel
<form name="bing" onsubmit="return bingWebSearch(this.query.value,
    bingSearchOptions(this), getSubscriptionKey())">

Frågealternativ

HTML-formuläret innehåller alternativ som mappar till frågeparametrar i API för webbsökning i Bing v7. Den här tabellen innehåller en detaljerad analys av hur användarna kan filtrera sökresultat med hjälp av exempelappen:

Parameter Beskrivning
query Ett textfält för att ange en frågesträng.
where En nedrullningsbar meny för att välja marknaden (plats och språk).
what Kryssrutorna för att flytta upp specifika resultattyper. Uppgradera bilder ökar till exempel rangordningen av bilder i sökresultaten.
when En nedrullningsbar meny som används för att begränsa sökresultaten till dagens datum, den här veckan eller den här månaden.
safe En kryssruta för att aktivera Bing SafeSearch som filtrerar ut innehåll som är olämpligt för barn.
count Dolt fält. Antal sökresultat som returneras för varje begäran. Ändra det här värdet om du vill visa färre eller fler resultat per sida.
offset Dolt fält. Förskjutningen av det första sökresultatet i begäran, vilket används för växling. Den återställs till 0 för varje ny begäran.

Anteckning

API för webbsökning i Bing erbjuder ytterligare frågeparametrar för att begränsa sökresultaten. I det här exemplet används bara några få. En fullständig lista över tillgängliga parametrar finns i referensen API för webbsökning i Bing v7.

Funktionen bingSearchOptions() konverterar dessa alternativ för att matcha det format som krävs av API för sökning i 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 kan anges till strict, moderate eller off, med moderate som standardinställningen för webbsökning i Bing. Det här formuläret använder en kryssruta som har två tillstånd: strict eller moderate.

Om något av kryssrutorna befordra är markerade har parametern answerCount lagts till i frågan. answerCount krävs när du använder parametern promote. I det här kodfragmentet anges värdet till 9 för att returnera alla tillgängliga resultattyper.

Anteckning

Att uppgradera en resultattyp garanterar inte att den tas med i sökresultatet. I stället ökar befordran rangordningen för denna typ av resultat i förhållande till deras vanliga rangordning. För att begränsa sökningen till olika typer av resultat, använder du frågeparametern responseFilter eller anropar en mer specifik slutpunkt för bildsökning i Bing t.ex nyhetssökning i Bing.

Frågeparametrarna textDecoration och textFormat är hårdkodade i skriptet och gör så att söktermen är i fetstilt i sökresultatet. Dessa parametrar behövs inte.

Hantera prenumerationsnycklar

För att undvika hårdkodning av prenumerationsnyckeln för API:er för sökning i Bing använder den här exempelappen beständig lagring i en webbläsare för att lagra prenumerationsnyckeln. Om ingen prenumerationsnyckel lagras, uppmanas användaren att ange en. Om prenumerationsnyckeln har avvisats av API:et, ombeds användaren att ange en prenumerationsnyckel på nytt.

Funktionen getSubscriptionKey() använder funktionerna storeValue och retrieveValue för att lagra och hämta en användares prenumerationsnyckel. Dessa funktioner använder objektet localStorage, om det stöds, eller cookies.

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

Som vi såg tidigare, när formuläret skickas utlöses onsubmit och anropar bingWebSearch. Den här funktionen initierar och skickar en begäran. getSubscriptionKey anropas vid varje överföring för att autentisera begäran.

Baserat på frågan, alternativsträngen och prenumerationsnyckeln, skapar funktionen BingWebSearch ett XMLHttpRequest-objekt för att anropa slutpunkten för webbsökning i 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;
}

Efter en lyckad begäran, utlöses load-händelsehanteraren och anropar funktionen handleBingResponse. handleBingResponse parsar resultatobjektet, visar resultatet och innehåller fellogik för misslyckade begäranden.

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"));
    }
}

Viktigt

En lyckad HTTP-begäran betyder inte att själva sökningen lyckades. Om ett fel uppstår i sökåtgärden returnerar API för webbsökning i Bing en icke-200-HTTP-statuskod och inkluderar felinformation i JSON-svaret. Om begäran var begränsad returnerar API:et ett tomt svar.

En stor del av koden i de båda föregående funktionerna är dedikerade för felhantering. Fel kan inträffa i följande steg:

Fas Potentiella fel Hanterat av
Skapa objektbegäran Ogiltig URL try / catch blockera
Skapa begäran Nätverksfel, avbrutna anslutningar Händelsehanterare för error och abort
Genomföra sökningen Ogiltig begäran, ogiltig JSON, hastighetsbegränsningar tests i load händelsehanterare

Fel hanteras genom att anropa renderErrorMessage(). Om svaret klarar samtliga av dessa feltest, anropas renderSearchResults() för att visa sökresultaten.

Visa sökresultat

Det finns användar- och visningskrav för resultaten som returnerades av API för webbsökning i Bing. Eftersom ett svar kan innehålla olika resultattyper, räcker det inte att gå igenom den översta WebPages-samlingen. I stället använder exempelappen RankingResponse för att sortera resultaten till specifikationen.

Anteckning

Om du bara vill ha en resultattyp, använder du frågeparametern responseFilter eller överväger att använda en av de andra slutpunkterna för sökning i Bing, till exempel bildsökning i Bing.

Varje svar har ett RankingResponse-objekt som kan innehålla upp till tre samlingar: pole, mainline och sidebar. pole, om det finns, är det mest relevanta sökresultatet och måste visas på en framträdande plats. mainline innehåller de flesta av sökresultaten och visas omedelbart efter pole. sidebar innehåller extra sökresultat. Om möjligt ska de här resultaten visas i sidopanelen. Om skärmens gränser gör det opraktiskt med en sidopanel, visas de här resultaten efter mainline-resultaten.

Varje RankingResponse innehåller en RankingItem-matris som anger hur resultat måste ordnas. Vårt exempel använder parametrarna answerType och resultIndex för att identifiera resultatet.

Anteckning

Det finns andra sätt att identifiera och rangordna resultat. Mer information finns i Använda rangordning för att visa resultat.

Låt oss ta en titt på koden:

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

Funktionen renderResultsItems() går igenom objekten i varje RankingResponse-samling, mappar varje rangordningsresultat till ett sökresultat med hjälp av värdena answerType och resultIndex och anropar lämplig renderingsfunktion för att generera HTML. Om resultIndex inte har angetts för ett objekt, går renderResultsItems() igenom alla resultat av denna typ och anropar renderingsfunktionen för varje objekt. Resulterande HTML matas in i aktuellt <div>-element i 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");
}

Granska renderingsfunktioner

I vår exempelapp innehåller searchItemRenderers-objektet funktioner som genererar HTML för varje typ av sökresultat.

// 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) { ... }
}

Viktigt

Exempelappen har renderare för webbsidor, nyheter, bilder, videoklipp och relaterade sökningar. Ditt program behöver renderare för alla typer av resultat det kan få, vilket kan omfatta beräkningar, stavningsförslag, entiteter, tidszoner och definitioner.

Några av funktionerna för rendering accepterar endast parametern item. Andra accepterar extraparametrar som kan användas för att återge objekt på olika sätt beroende på kontext. Ett renderingsprogram som inte använder den här informationen behöver inte godkänna dessa parametrar.

Kontextargumenten är:

Parameter Beskrivning
section Resultatavsnittet (pole, mainline, eller sidebar) i vilket objektet visas.
index
count
Tillgängligt när RankingResponse-objektet anger att alla resultat i en viss samling ska visas; undefined annars. Index för objektet i en samling och det totala antalet objekt i samlingen. Du kan använda den här informationen för att numrera resultaten för att generera olika HTML för det första eller sista resultatet och så vidare.

I exempelappen, använder både renderare images och relatedSearches kontextargumenten för att anpassa genererad HTML. Låt oss ta en närmare titt på renderare 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...
}

Bildåtergivare:

  • Beräknar storleken på miniatyrbilderna (bredd varierar, medan höjd är högst 60 bildpunkter).
  • Infogar den HTML som föregår bildresultatet baserat på kontext.
  • Skapar en HTML <a>-tagg som länkar till den sida som innehåller bilden.
  • Skapar HTML <img>-taggen för att visa miniatyrbilden.

Bildåtergivning använder variablerna section och index för att visa resultat på olika sätt beroende på var de förekommer. En radbrytning (<br>-tagg) infogas mellan bildresultat i sidopanelen, så att sidopanelen visar en kolumn med bilder. I andra avsnitt, föregås det första bildresultatet (index === 0) av en <p>-tagg.

Storlek på miniatyrbilderna används i både <img>-taggen och fälten h och w i miniatyrbildens webbadress. Attribut title och alt (en textbeskrivning av bilden) skapas från bildens namn och värdnamnet i URL:en.

Här är ett exempel på hur bilder visas i exempelappen:

[Bing bildresultat]

Spara klient-ID:t

Svar från API:er för sökning i Bing kan innehålla ett X-MSEdge-ClientID-huvud som ska skickas tillbaka till API:et med varje efterföljande begäran. Om mer än ett av API:erna för sökning i Bing används av din app, kontrollerar du att samma klient-ID har skickats med varje begäran över tjänster.

Genom att tillhandahålla X-MSEdge-ClientID-huvudet kan Bing-API:er associera en användares sökningar. Först hjälper Bing-sökmotorn till med att tillämpa den senaste kontext på sökningarna för att hitta resultat som bättre tillfredsställer begäran. Om en användare tidigare har sökt efter termer som exempelvis relaterar till segling kan en senare sökning efter ”knopar” returnera information om knopar som används vid segling. Därefter väljer Bing slumpmässigt ut användare som ska prova nya funktioner innan de blir allmänt tillgängliga. Genom att tillhandahålla samma klient-ID med varje begäran säkerställs att användare som har valts för att se en funktion alltid ser den. Utan klient-ID kan användaren se en funktion som sedan försvinner, till synes slumpmässigt, i sökresultatet.

Webbläsarens säkerhetsprinciper, till exempel resursdelning för korsande ursprung (CORS), kan hindra exempelappen från att komma åt rubriken X-MSEdge-ClientID. Den här begränsningen uppstår när söksvaret har ett annat ursprung än sidan som begärt det. I en produktionsmiljö bör du hantera den här principen genom att lägga upp ett serverskript som gör API-anrop på samma domän som webbsidan. Eftersom skriptet har samma ursprung som webbsidan är sedan X-MSEdge-ClientID-huvudet tillgängligt för JavaScript.

Anteckning

Du bör utföra begäran på serversidan i ett produktionsklart webbprogram ändå. I annat fall måste API-prenumerationsnyckeln för Bing-sökning inkluderas i webbsidan där den är tillgänglig för alla som visar källan. Du debiteras för all användning under din API-prenumerationsnyckel, även begäranden som görs av obehöriga personer, så det är viktigt att inte exponera nyckeln.

För utvecklingssyften kan du göra en begäran via en proxyserver för CORS. Svaret från den här typen av proxy har en Access-Control-Expose-Headers rubrik som filtrerar svarshuvuden och gör dem tillgängliga för JavaScript.

Det är enkelt att installera en CORS-proxy för att tillåta att exempelappen får åtkomst till klientens ID-huvud. Kör följande kommando:

npm install -g cors-proxy-server

Ändra slutpunkten för webbsökning i Bing i script.js till:

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

Starta CORS-proxyn med det här kommandot:

cors-proxy-server

Lämna kommandofönstret öppet medan du använder exempelappen. Om du stänger fönstret stoppas proxyn. I avsnittet utbyggbara HTTP-huvuden under sökresultaten bör sidhuvudet X-MSEdge-ClientID visas. Kontrollera att det är samma för varje begäran.

Nästa steg