Freigeben über


Lernprogramm: Erstellen einer einzelseitigen App mithilfe der Bing-Bildsuche-API

Warnung

Am 30. Oktober 2020 wurden die Bing Search-APIs von Azure AI-Diensten zu Bing Search Services verschoben. Diese Dokumentation wird nur zur Referenz bereitgestellt. Eine aktualisierte Dokumentation finden Sie in der Dokumentation zu den Bing-Suche-APIs. Anweisungen zum Erstellen neuer Azure-Ressourcen für die Bing-Suche finden Sie unter Erstellen einer Ressource für die Bing-Suche über Azure Marketplace.

Mit der Bing-Bildsuche-API können Sie das Web nach qualitativ hochwertigen, relevanten Bildern durchsuchen. Verwenden Sie dieses Lernprogramm, um eine einzelseitige Webanwendung zu erstellen, die Suchabfragen an die API senden und die Ergebnisse auf der Webseite anzeigen kann. Dieses Lernprogramm ähnelt dem entsprechenden Lernprogramm für Bing Web Search.

Die Lernprogramm-App veranschaulicht Folgendes:

  • Ausführen eines Bing Image Search-API-Aufrufs in JavaScript
  • Verbessern von Suchergebnissen mithilfe von Suchoptionen
  • Anzeigen und Durchblättern von Suchergebnissen
  • Anfordern und Verarbeiten eines API-Abonnementschlüssels und der Bing-Client-ID.

Voraussetzungen

  • Die neueste Version von Node.js.
  • Das Express.js Framework für Node.js. Installationsanweisungen für den Quellcode sind in der GitHub-Beispiel-Infodatei verfügbar.

Verwalten und Speichern von Benutzerabonnementschlüsseln

Diese Anwendung verwendet den persistenten Speicher von Webbrowsern zum Speichern von API-Abonnementschlüsseln. Wenn kein Schlüssel gespeichert ist, fordert die Webseite den Benutzer zur Eingabe seines Schlüssels auf und speichert ihn zur späteren Verwendung. Wenn der Schlüssel später von der API abgelehnt wird, wird er von der App aus dem Speicher entfernt. In diesem Beispiel wird der globale Endpunkt verwendet. Sie können auch den benutzerdefinierten Unterdomänen- Endpunkt verwenden, der im Azure-Portal für Ihre Ressource angezeigt wird.

Definieren Sie storeValue- und retrieveValue-Funktionen, um entweder das localStorage-Objekt (sofern der Browser es unterstützt) oder ein Cookie zu verwenden.

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

Die getSubscriptionKey()-Funktion versucht, einen zuvor gespeicherten Schlüssel mithilfe von retrieveValueabzurufen. Wenn ein Schlüssel nicht gefunden wird, fordert er den Benutzer zur Eingabe seines Schlüssels auf, und speichert ihn mit 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;
}

Das HTML-<form>-Tag onsubmit ruft die bingWebSearch-Funktion auf, um Suchergebnisse zurückzugeben. bingWebSearch verwendet getSubscriptionKey, um jede Abfrage zu authentifizieren. Wie in der vorherigen Definition gezeigt, fordert getSubscriptionKey den Benutzer zur Eingabe des Schlüssels auf, wenn der Schlüssel nicht eingegeben wurde. Der Schlüssel wird dann zur fortgesetzten Verwendung durch die Anwendung gespeichert.

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

Senden von Suchanforderungen

Diese Anwendung verwendet ein HTML-<form>, um anfänglich Benutzersuchanfragen zu senden, wobei das Attribut onsubmit verwendet wird, um newBingImageSearch()aufzurufen.

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

Standardmäßig gibt der onsubmit-Handler falsezurück, wobei das Formular nicht übermittelt wird.

Auswählen von Suchoptionen

[Bing-Bildsuche]

Die Bing Image Search-API bietet mehrere Filterabfrageparameter, um Suchergebnisse einzugrenzen und zu filtern. Das HTML-Formular in dieser Anwendung verwendet und zeigt die folgenden Parameteroptionen an:

Auswahlmöglichkeit BESCHREIBUNG
where Ein Dropdownmenü zum Auswählen des Marktes (Standort und Sprache), das für die Suche verwendet wird.
query Das Textfeld, in das die Suchbegriffe eingegeben werden sollen.
aspect Optionsfelder zum Auswählen der Proportionen der gefundenen Bilder: ungefähr quadratisch, breit oder hoch.
color
when Dropdownmenü zum optionalen Einschränken der Suche auf den letzten Tag, die letzte Woche oder den letzten Monat.
safe Ein Kontrollkästchen, das angibt, ob das SafeSearch-Feature von Bing verwendet werden soll, um "erwachsene" Ergebnisse herauszufiltern.
count Ausgeblendetes Feld. Die Anzahl der Suchergebnisse, die für jede Anforderung zurückgegeben werden sollen. Ändern, um weniger oder mehr Ergebnisse pro Seite anzuzeigen.
offset Ausgeblendetes Feld. Der Offset des ersten Suchergebnisses in der Anfrage wird zur Seitennummerierung verwendet. Sie wird bei einer neuen Anfrage auf 0 zurückgesetzt.
nextoffset Ausgeblendetes Feld. Beim Empfang eines Suchergebnisses wird dieses Feld auf den Wert des nextOffset in der Antwort festgelegt. Die Verwendung dieses Felds verhindert überlappende Ergebnisse auf aufeinander folgenden Seiten.
stack Ausgeblendetes Feld. Eine JSON-codierte Liste der Offsets der vorherigen Seiten der Suchergebnisse, um zurück zu vorherigen Seiten zu navigieren.

Die bingSearchOptions()-Funktion formatiert diese Optionen in eine partielle Abfragezeichenfolge, die in den API-Anforderungen der App verwendet werden kann.

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

Durchführen der Anforderung

Mithilfe der Suchabfrage, der Optionszeichenfolge und des API-Schlüssels verwendet die BingImageSearch()-Funktion ein XMLHttpRequest-Objekt, um die Anforderung an den Bing Image Search-Endpunkt zu senden.

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

Nach erfolgreichem Abschluss der HTTP-Anforderung ruft JavaScript den "load"-Ereignishandler handleBingResponse() auf, um eine erfolgreiche HTTP GET-Anforderung zu verarbeiten.

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

Wichtig

Erfolgreiche HTTP-Anforderungen enthalten möglicherweise fehlerhafte Suchinformationen. Wenn während des Suchvorgangs ein Fehler auftritt, gibt die Bing Image Search-API einen NICHT-200 HTTP-Statuscode und Fehlerinformationen in der JSON-Antwort zurück. Außerdem, wenn die Anforderung ratenbegrenzt war, gibt die API eine leere Antwort zurück.

Anzeigen der Suchergebnisse

Suchergebnisse werden von der renderSearchResults()-Funktion angezeigt, die den vom Bing Image Search-Dienst zurückgegebenen JSON-Code verwendet und eine entsprechende Rendererfunktion für alle zurückgegebenen Bilder und verwandten Suchvorgänge aufruft.

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

Die Bildsuchergebnisse sind im obersten value-Objekt innerhalb der JSON-Antwort enthalten. Diese werden an renderImageResults()übergeben, der die Ergebnisse durchläuft und jedes Element in HTML konvertiert.

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

Die Bing Image Search-API kann vier Arten von Suchvorschlägen zurückgeben, um die Suchfunktionen der Benutzer zu unterstützen, die jeweils in einem eigenen Objekt der obersten Ebene enthalten sind:

Vorschlag BESCHREIBUNG
pivotSuggestions Abfragen, die ein Pivotwort in der ursprünglichen Suche durch eine andere ersetzen. Wenn Sie beispielsweise nach "rote Blumen" suchen, kann ein Pivotwort "rot" sein, und ein Pivotvorschlag könnte "gelbe Blumen" sein.
queryExpansions Abfragen, die die ursprüngliche Suche einschränken, indem weitere Ausdrücke hinzugefügt werden. Wenn Sie beispielsweise nach "Microsoft Surface" suchen, kann eine Abfrageerweiterung "Microsoft Surface Pro" sein.
relatedSearches Abfragen, die auch von anderen Benutzern eingegeben wurden, die die ursprüngliche Suche eingegeben haben. Wenn Sie z. B. nach "Mount Rainier" suchen, könnte eine verwandte Suche "Mt. Rainier" sein. St. Helens."
similarTerms Abfragen, die der ursprünglichen Suche ähnlich sind. Wenn Sie beispielsweise nach "Kätzchen" suchen, könnte ein ähnlicher Begriff "süß" sein.

Diese Anwendung rendert nur die relatedItems Vorschläge und platziert die resultierenden Links in der Randleiste der Seite.

Rendern von Suchergebnissen

In dieser Anwendung enthält das searchItemRenderers-Objekt Rendererfunktionen, die HTML für jede Art von Suchergebnis generieren.

searchItemRenderers = {
    images: function(item, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Diese Rendererfunktionen akzeptieren die folgenden Parameter:

Parameter BESCHREIBUNG
item Das JavaScript-Objekt, das die Eigenschaften des Elements enthält, z. B. seine URL und seine Beschreibung.
index Der Index des Ergebniselements innerhalb seiner Sammlung.
count Die Anzahl der Elemente in der Auflistung des Suchergebniselements.

Die Parameter index und count werden verwendet, um Ergebnisse zu nummerieren, HTML für Sammlungen zu generieren und den Inhalt zu organisieren. Insbesondere weist es darauf hin:

  • Berechnet die Miniaturansichtsgröße des Bilds (breite variiert mit mindestens 120 Pixeln, während die Höhe bei 90 Pixeln festgelegt ist).
  • Erstellt das HTML-<img>-Tag, um die Miniaturansicht des Bilds anzuzeigen.
  • Erstellt die HTML-<a>-Tags, die mit dem Bild und der Seite verknüpft sind, die sie enthält.
  • Erstellt die Beschreibung, die Informationen über das Bild und die Website anzeigt, auf der es sich befindet.
    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

Die height und width des Miniaturbilds werden sowohl im <img>-Tag als auch in den Feldern h und w in der URL der Miniaturansicht verwendet. Dadurch kann Bing eine Miniaturansicht genau dieser Größe zurückgeben.

Beibehalten der Client-ID

Antworten von den Bing-Such-APIs können einen X-MSEdge-ClientID Header enthalten, der mit aufeinander folgenden Anforderungen an die API zurückgesendet werden soll. Wenn mehrere Bing Search-APIs verwendet werden, sollte nach Möglichkeit dieselbe Client-ID mit allen verwendet werden.

Wenn Sie den X-MSEdge-ClientID-Header bereitstellen, können die Bing-APIs alle Suchvorgänge eines Benutzers zuordnen, was nützlich ist.

Erstens ermöglicht es der Bing-Suchmaschine, den früheren Kontext auf Suchvorgänge anzuwenden, um Ergebnisse zu finden, die den Benutzer besser erfüllen. Wenn ein Benutzer zuvor nach Begriffen im Zusammenhang mit Segeln gesucht hat, z. B. könnte eine spätere Suche nach "Knoten" bevorzugt Informationen zu Knoten zurückgeben, die beim Segeln verwendet werden.

Zweitens kann Bing zufällig benutzer auswählen, um neue Features zu erleben, bevor sie allgemein verfügbar gemacht werden. Wenn Sie die gleiche Client-ID für jede Anforderung bereitstellen, wird sichergestellt, dass Benutzer, die ausgewählt wurden, ein Feature immer sehen. Ohne die Client-ID wird dem Benutzer möglicherweise ein Feature angezeigt und verschwindet scheinbar zufällig in den Suchergebnissen.

Browsersicherheitsrichtlinien (CORS) verhindern möglicherweise, dass der X-MSEdge-ClientID-Header für JavaScript verfügbar ist. Diese Einschränkung tritt auf, wenn die Suchantwort einen anderen Ursprung von der Seite hat, die sie angefordert hat. In einer Produktionsumgebung sollten Sie diese Richtlinie adressieren, indem Sie ein serverseitiges Skript hosten, das den API-Aufruf in derselben Domäne wie die Webseite ausführt. Da das Skript denselben Ursprung wie die Webseite hat, ist der X-MSEdge-ClientID Header dann für JavaScript verfügbar.

Hinweis

In einer Produktionswebanwendung sollten Sie die serverseitige Anforderung trotzdem ausführen. Andernfalls muss Ihr Bing Search-API-Schlüssel auf der Webseite enthalten sein, wo er für alle Personen verfügbar ist, die die Quelle anzeigen. Sie werden für alle Nutzungen unter Ihrem API-Abonnementschlüssel in Rechnung gestellt, auch Anfragen von nicht autorisierten Parteien, daher ist es wichtig, Ihren Schlüssel nicht verfügbar zu machen.

Für Entwicklungszwecke können Sie die Bing Web Search-API-Anforderung über einen CORS-Proxy stellen. Die Antwort eines solchen Proxys verfügt über einen Access-Control-Expose-Headers Header, der Antwortheader zulässt und sie javaScript zur Verfügung stellt.

Es ist einfach, einen CORS-Proxy zu installieren, damit unsere Lernprogramm-App auf den Client-ID-Header zugreifen kann. Zuerst, wenn du ihn noch nicht hast, Node.jsinstalliere. Geben Sie dann den folgenden Befehl in einem Befehlsfenster aus:

npm install -g cors-proxy-server

Ändern Sie als Nächstes den Bing Web Search-Endpunkt in der HTML-Datei in:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Starten Sie schließlich den CORS-Proxy mit dem folgenden Befehl:

cors-proxy-server

Lassen Sie das Befehlsfenster geöffnet, während Sie die Lernprogramm-App verwenden. Durch schließen des Fensters wird der Proxy beendet. Im Abschnitt "erweiterbare HTTP-Header" unter den Suchergebnissen können Sie nun den X-MSEdge-ClientID-Header (unter anderem) sehen und überprüfen, ob es für jede Anforderung identisch ist.

Nächste Schritte

Siehe auch