Delen via


Zelfstudie: Een app met één pagina maken met behulp van de Bing Webzoekopdrachten-API

Waarschuwing

Op 30 oktober 2020 zijn de Bing Search-API's verplaatst van Azure AI-services naar Bing Search Services. Deze documentatie wordt alleen ter referentie verstrekt. Zie de bing zoek-API-documentatie voor bijgewerkte documentatie. Voor instructies voor het maken van nieuwe Azure-resources voor Bing, zie Een Bing Search-resource maken via de Azure Marketplace.

Deze app met één pagina laat zien hoe u zoekresultaten ophaalt, parseert en weergeeft uit de Bing Webzoekopdrachten-API. De zelfstudie maakt gebruik van standaard HTML en CSS en richt zich op de JavaScript-code. HTML-, CSS- en JS-bestanden zijn beschikbaar op GitHub- met quickstart-instructies.

Deze voorbeeld-app kan het volgende doen:

  • De Bing Webzoekopdrachten-API aanroepen met zoekopties
  • Web-, afbeeldings-, nieuws- en videoresultaten weergeven
  • Resultaten pagineren
  • Abonnementssleutels beheren
  • Fouten verwerken

Als u deze app wilt gebruiken, is een Azure AI-servicesaccount met Bing Search-API's vereist.

Benodigdheden

Hier volgen enkele dingen die u nodig hebt om de app uit te voeren:

  • Een Azure-abonnement- Gratis maken

  • Zodra u uw Azure-abonnement hebt, een Bing Search-resource maken in Azure Portal om uw sleutel en eindpunt op te halen. Nadat deze wordt geïmplementeerd, klikt u op Ga naar resource.

  • Node.js 8 of hoger

De eerste stap is het klonen van de opslagplaats met de broncode van de voorbeeld-app.

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

Voer vervolgens npm installuit. Voor deze zelfstudie is Express.js de enige afhankelijkheid.

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

App-onderdelen

De voorbeeld-app die we bouwen bestaat uit vier onderdelen:

  • bing-web-search.js - Onze Express.js-app. Het verwerkt aanvraag-/antwoordlogica en -routering.
  • public/index.html - Het skelet van onze app; hiermee wordt gedefinieerd hoe gegevens aan de gebruiker worden gepresenteerd.
  • public/css/styles.css - Hiermee definieert u paginastijlen, zoals lettertypen, kleuren, tekstgrootte.
  • public/js/scripts.js: bevat de logica voor het indienen van aanvragen bij de Bing Webzoekopdrachten-API, het beheren van abonnementssleutels, het verwerken en parseren van antwoorden en het weergeven van resultaten.

Deze zelfstudie is gericht op scripts.js en de logica die nodig is om de Bing Webzoekopdrachten-API aan te roepen en het antwoord af te handelen.

HTML-formulier

De index.html bevat een formulier waarmee gebruikers zoekopties kunnen zoeken en selecteren. Het onsubmit kenmerk wordt geactiveerd wanneer het formulier wordt verzonden, waarbij de bingWebSearch() methode wordt aangeroepen die is gedefinieerd in scripts.js. Er zijn drie argumenten nodig:

  • Zoekquery
  • Geselecteerde opties
  • Abonnementssleutel
<form name="bing" onsubmit="return bingWebSearch(this.query.value,
    bingSearchOptions(this), getSubscriptionKey())">

Queryopties

Het HTML-formulier bevat opties die zijn toegewezen aan queryparameters in de Bing Webzoekopdrachten-API v7. Deze tabel bevat een uitsplitsing van hoe gebruikers zoekresultaten kunnen filteren met behulp van de voorbeeld-app:

Maatstaf Beschrijving
query Een tekstveld om een querytekenreeks in te voeren.
where Een vervolgkeuzemenu om de markt te selecteren (locatie en taal).
what Selectievakjes om specifieke resultaattypen te promoten. Het promoten van afbeeldingen verhoogt bijvoorbeeld de rangorde van afbeeldingen in zoekresultaten.
when Een vervolgkeuzelijst waarmee de gebruiker de zoekresultaten kan beperken tot vandaag, deze week of deze maand.
safe Een selectievakje om Bing SafeSearch in te schakelen, waarmee inhoud voor volwassenen wordt gefilterd.
count Verborgen veld. Het aantal zoekresultaten dat bij elke aanvraag moet worden geretourneerd. Wijzig deze waarde om minder of meer resultaten per pagina weer te geven.
offset Verborgen veld. De verschuiving van het eerste zoekresultaat in het verzoek, die wordt gebruikt voor paginering. Het wordt opnieuw ingesteld op 0 bij elke nieuwe aanvraag.

Notitie

De Bing Webzoekopdrachten-API biedt aanvullende queryparameters om zoekresultaten te verfijnen. In dit voorbeeld worden er slechts enkele gebruikt. Zie Bing Webzoek-API v7 referentievoor een volledige lijst van beschikbare parameters.

De functie bingSearchOptions() converteert deze opties zodat deze overeenkomen met de indeling die is vereist voor de Bing Search-API.

// 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 worden ingesteld op strict, moderateof off, waarbij moderate de standaardinstelling voor Bing Webzoekopdrachten is. Dit formulier maakt gebruik van een selectievakje met twee statussen: strict of moderate.

Als een van de selectievakjes Niveau verhogen is ingeschakeld, wordt de parameter answerCount toegevoegd aan de query. answerCount is vereist wanneer u de parameter promote gebruikt. In dit fragment wordt de waarde ingesteld op 9 om alle beschikbare resultaattypen te retourneren.

Notitie

Het promoten van een resultaattype garandeert niet dat het wordt opgenomen in de zoekresultaten. In plaats daarvan verhoogt promotie de rangschikking van dit soort resultaten ten opzichte van hun gebruikelijke rangschikking. Als u zoekopdrachten wilt beperken tot bepaalde soorten resultaten, gebruikt u de responseFilter queryparameter of roept u een specifieker eindpunt aan, zoals Bing Afbeeldingen zoeken of Bing Nieuws zoeken.

De queryparameters textDecoration en textFormat zijn vastgelegd in het script en zorgen ervoor dat de zoekterm vetgedrukt wordt weergegeven in de zoekresultaten. Deze parameters zijn niet vereist.

Abonnementssleutels beheren

Om hardcoding van de abonnementssleutel voor de Bing Search-API te voorkomen, gebruikt deze voorbeeld-app de permanente opslag van een browser om de abonnementssleutel op te slaan. Als er geen abonnementssleutel is opgeslagen, wordt de gebruiker gevraagd er een in te voeren. Als de abonnementssleutel wordt geweigerd door de API, wordt de gebruiker gevraagd een abonnementssleutel opnieuw in te voeren.

De functie getSubscriptionKey() maakt gebruik van de functies storeValue en retrieveValue om de abonnementssleutel van een gebruiker op te slaan en op te halen. Deze functies maken gebruik van het localStorage object, indien ondersteund of 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;
}

Zoals we eerder hebben gezien, wordt onsubmit geactiveerd wanneer het formulier wordt ingediend en roept het bingWebSearchaan. Met deze functie wordt de aanvraag geïnitialiseerd en verzonden. getSubscriptionKey wordt bij elke inzending aangeroepen om de aanvraag te verifiëren.

Gezien de query, de tekenreeks voor opties en de abonnementssleutel, maakt de functie BingWebSearch een XMLHttpRequest-object om het Bing Webzoekopdrachten-eindpunt aan te roepen.

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

Na een succesvolle aanvraag wordt de load-eventhandler geactiveerd en wordt de handleBingResponse-functie aangeroepen. handleBingResponse het resultaatobject parseert, de resultaten weergeeft en foutlogica bevat voor mislukte aanvragen.

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

Belangrijk

Een geslaagde HTTP-aanvraag betekent niet dat de zoekopdracht zelf is geslaagd. Als er een fout optreedt in de zoekbewerking, retourneert de Bing Webzoekopdrachten-API een niet-200 HTTP-statuscode en bevat deze foutinformatie in het JSON-antwoord. Als de aanvraag aan een snelheidslimiet werd onderworpen, retourneert de API een leeg antwoord.

Veel van de code in beide voorgaande functies is toegewezen aan foutafhandeling. In de volgende fasen kunnen fouten optreden:

Fase Mogelijke fout(en) Verwerkt door
Het aanvraagobject bouwen Ongeldige URL try / catch blok
De aanvraag indienen Netwerkfouten, afgebroken verbindingen error en abort gebeurtenis-handlers
De zoekopdracht uitvoeren Ongeldige aanvraag, ongeldige JSON, frequentielimieten Tests in load gebeurtenishandler

Fouten worden verwerkt door renderErrorMessage()aan te roepen. Als het antwoord alle fouttests doorstaat, wordt renderSearchResults() aangeroepen om de zoekresultaten weer te geven.

Zoekresultaten weergeven

Er zijn gebruiks- en weergavevereisten voor resultaten die worden geretourneerd door de Bing Webzoekopdrachten-API. Omdat een antwoord verschillende resultaattypen kan bevatten, is het niet voldoende om de verzameling op het hoogste niveau WebPages te doorlopen. In plaats daarvan gebruikt de voorbeeld-app RankingResponse om de resultaten te ordenen op specificatie.

Notitie

Als u slechts één resultaattype wilt, gebruikt u de responseFilter queryparameter of kunt u overwegen een van de andere Bing Search-eindpunten te gebruiken, zoals Bing Afbeeldingen zoeken.

Elk antwoord heeft een RankingResponse-object dat maximaal drie verzamelingen kan bevatten: pole, mainlineen sidebar. pole, indien aanwezig, het meest relevante zoekresultaat is en moet prominent worden weergegeven. mainline de meeste zoekresultaten bevat en direct na polewordt weergegeven. sidebar bevat hulpzoekresultaten. Indien mogelijk moeten deze resultaten worden weergegeven in de zijbalk. Als schermlimieten een zijbalk onpraktisch maken, worden deze resultaten weergegeven na de mainline resultaten.

Elke RankingResponse bevat een RankingItem matrix die aangeeft hoe resultaten moeten worden geordend. In onze voorbeeld-app worden de parameters answerType en resultIndex gebruikt om het resultaat te identificeren.

Notitie

Er zijn extra manieren om resultaten te identificeren en te rangschikken. Zie Classificatie gebruiken om resultaten weer te gevenvoor meer informatie.

Laten we eens kijken naar de code:

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

De renderResultsItems() functie doorloopt de items in elke RankingResponse verzameling, wijst elk classificatieresultaat toe aan een zoekresultaat met behulp van de waarden answerType en resultIndex en roept de juiste renderingfunctie aan om de HTML te genereren. Als resultIndex niet is opgegeven voor een item, renderResultsItems() alle resultaten van dat type doorloopt en de renderingfunctie voor elk item aanroept. De resulterende HTML wordt ingevoegd in het juiste <div> element 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");
}

Rendererfuncties controleren

In onze voorbeeld-app bevat het searchItemRenderers-object functies die HTML genereren voor elk type zoekresultaat.

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

Belangrijk

De voorbeeld-app bevat renderers voor webpagina's, nieuws, afbeeldingen, video's en gerelateerde zoekopdrachten. Uw toepassing heeft renderers nodig voor elk type resultaten dat kan worden ontvangen, waaronder berekeningen, spellingsuggesties, entiteiten, tijdzones en definities.

Sommige renderingfuncties accepteren alleen de parameter item. Anderen accepteren aanvullende parameters, die kunnen worden gebruikt om items anders weer te geven op basis van context. Een renderer die deze informatie niet gebruikt, hoeft deze parameters niet te accepteren.

De contextargumenten zijn:

Maatstaf Beschrijving
section De sectie met resultaten (pole, mainlineof sidebar) waarin het item wordt weergegeven.
index
count
Beschikbaar wanneer het RankingResponse item aangeeft dat alle resultaten in een bepaalde verzameling moeten worden weergegeven; undefined anders. De index van het item in de verzameling en het totale aantal items in die verzameling. U kunt deze informatie gebruiken om de resultaten te nummeren, om verschillende HTML-code te genereren voor het eerste of laatste resultaat, enzovoort.

In de voorbeeld-app gebruiken zowel de images als relatedSearches renderers de contextargumenten om de gegenereerde HTML aan te passen. Laten we de images renderer eens nader bekijken:

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...
}

De afbeeldingsweergave:

  • Berekent de miniatuurgrootte van de afbeelding (breedte varieert, terwijl de hoogte is vastgesteld op 60 pixels).
  • Hiermee wordt de HTML ingevoegd die voorafgaat aan het afbeeldingsresultaat op basis van context.
  • Hiermee wordt de HTML-<a>-tag gebouwd die is gekoppeld aan de pagina die de afbeelding bevat.
  • Hiermee wordt de HTML-<img>-tag gebouwd om de miniatuur van de afbeelding weer te geven.

De weergaveweergave van de afbeelding maakt gebruik van de variabelen section en index om de resultaten anders weer te geven, afhankelijk van waar ze worden weergegeven. Er wordt een regeleinde (<br> tag) ingevoegd tussen afbeeldingsresultaten in de zijbalk, zodat in de zijbalk een kolom met afbeeldingen wordt weergegeven. In andere secties wordt het eerste afbeeldingsresultaat (index === 0) voorafgegaan door een <p> tag.

De miniatuurgrootte wordt gebruikt in zowel de <img> tag als de velden h en w in de URL van de miniatuur. De kenmerken title en alt (een tekstbeschrijving van de afbeelding) worden samengesteld op basis van de naam van de afbeelding en de hostnaam in de URL.

Hier volgt een voorbeeld van hoe afbeeldingen worden weergegeven in de voorbeeld-app:

[Bing-afbeeldingsresultaten]

De client-id behouden

Antwoorden van de Bing-zoek-API's kunnen een X-MSEdge-ClientID header bevatten die bij elke opeenvolgende aanvraag naar de API moet worden teruggestuurd. Als meer dan een van de Bing Search-API's door uw app wordt gebruikt, moet u ervoor zorgen dat dezelfde client-id wordt verzonden bij elke aanvraag in alle services.

Door de X-MSEdge-ClientID-header op te geven, kunnen de Bing-API's de zoekopdrachten van een gebruiker koppelen. Ten eerste kan de Bing-zoekmachine eerdere context toepassen op zoekopdrachten om resultaten te vinden die beter voldoen aan de aanvraag. Als een gebruiker eerder heeft gezocht naar termen die met zeilen te maken hebben, kan bijvoorbeeld een latere zoekopdracht naar "knopen" voornamelijk resultaten tonen over knopen die bij het zeilen worden gebruikt. Ten tweede kan Bing willekeurig gebruikers selecteren om nieuwe functies te ervaren voordat ze algemeen beschikbaar worden gesteld. Als u dezelfde client-id opgeeft bij elke aanvraag, zorgt u ervoor dat gebruikers die zijn gekozen om een functie te zien, deze altijd zien. Zonder de client-id kan de gebruiker een functie zien verschijnen en verdwijnen, schijnbaar willekeurig, in de zoekresultaten.

Beveiligingsbeleid voor browsers, zoals CORS (Cross-Origin Resource Sharing), kan voorkomen dat de voorbeeld-app toegang heeft tot de X-MSEdge-ClientID-header. Deze beperking treedt op wanneer het zoekantwoord een andere oorsprong heeft dan de pagina waarop deze is aangevraagd. In een productieomgeving moet u dit beleid aanpakken door een script aan de serverzijde te hosten dat de API-aanroep uitvoert op hetzelfde domein als de webpagina. Omdat het script dezelfde oorsprong heeft als de webpagina, is de X-MSEdge-ClientID header vervolgens beschikbaar voor JavaScript.

Notitie

In een productie-webapplicatie moet je de aanvraag aan de serverzijde afhandelen. Anders moet de abonnementssleutel voor de Bing Search-API worden opgenomen op de webpagina, waar deze beschikbaar is voor iedereen die de bron bekijkt. U wordt gefactureerd voor al het gebruik onder uw API-abonnementssleutel, zelfs aanvragen van onbevoegde partijen, dus het is belangrijk om uw sleutel niet beschikbaar te maken.

Voor ontwikkelingsdoeleinden kunt u een aanvraag indienen via een CORS-proxy. Het antwoord van dit type proxy heeft een Access-Control-Expose-Headers header waarmee antwoordheaders worden gefilterd en beschikbaar worden gesteld voor JavaScript.

Het is eenvoudig om een CORS-proxy te installeren zodat onze voorbeeld-app toegang heeft tot de header van de client-id. Voer deze opdracht uit:

npm install -g cors-proxy-server

Wijzig vervolgens het Bing Webzoekopdrachten-eindpunt in script.js in:

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

Start de CORS-proxy met deze opdracht:

cors-proxy-server

Laat het opdrachtvenster geopend terwijl u de voorbeeld-app gebruikt; als u het venster sluit, wordt de proxy gestopt. In de uitbreidbare sectie HTTP-headers onder de zoekresultaten moet de X-MSEdge-ClientID header zichtbaar zijn. Controleer of deze voor elke aanvraag hetzelfde is.

Volgende stappen