Tutorial: Single-Page-Webanwendung für die Videosuche

Warnung

Am 30. Oktober 2020 wurden die Bing-Suche-APIs aus den Azure KI Services in die Bing-Suchdienste verschoben. Diese Dokumentation wird nur zu Referenzzwecken 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-Videosuche-API können Sie das Web durchsuchen und für eine Suchabfrage relevante Videoergebnisse abrufen. In diesem Tutorial wird eine Single-Page-Webanwendung erstellt, die unter Verwendung der Bing-Suche-API Suchergebnisse auf der Seite anzeigt. Die Anwendung enthält HTML-, CSS- und JavaScript-Komponenten.

Hinweis

Wenn Sie auf die JSON- und HTTP-Überschriften im unteren Bereich der Seite klicken, werden die JSON-Antwort und HTTP-Anforderungsinformationen angezeigt. Diese Details können hilfreich sein, wenn Sie sich genauer mit dem Dienst befassen.

JSON- und HTTP-Rohergebnisse

Mit dieser Tutorial-App wird folgendes veranschaulicht:

  • Aufrufen der Bing-Videosuche-API in JavaScript
  • Übergeben von Suchoptionen an die Bing-Suche-API
  • Anzeigen von Ergebnissen der Videosuche oder optionales Einschließen von Webseiten, News oder Bildern
  • Suchzeitrahmen von 24 Stunden, der letzten Woche, dem letzten Monat oder aller verfügbaren Zeit
  • Seitenweises Anzeigen von Suchergebnissen
  • Verarbeiten der Bing-Client-ID und des API-Abonnementschlüssels
  • Behandeln von möglicherweise auftretenden Fehlern

Die Tutorialseite ist unabhängig von anderen Komponenten und verwendet keine externen Frameworks, Stylesheets oder Bilddateien. Die Seite greift nur auf die am häufigsten unterstützten Features für JavaScript zurück und kann in aktuellen Versionen aller gängigen Webbrowser ausgeführt werden.

In diesem Tutorial werden ausgewählte Teile des Quellcodes erläutert. Der vollständige Quellcode steht zur Verfügung. Zum Ausführen des Beispiels kopieren Sie den Quellcode, fügen Sie ihn in einen Text-Editor ein, und speichern Sie die Datei als bing.html.

App-Komponenten

Diese Tutorial-App setzt sich ebenso wie alle anderen Single-Page-Web-Apps aus drei Teilen zusammen:

  • HTML: Definiert die Struktur und der Inhalt der Seite
  • CSS: Definiert das Layout der Seite
  • JavaScript: Definiert das Verhalten der Seite

Bei HTML und CSS handelt es sich zum Großteil um konventionellen Code, der in diesem Tutorial nicht erläutert wird. Der HTML-Teil enthält das Suchformular, in dem der Benutzer eine Abfrage eingibt und Suchoptionen auswählt. Das Formular wird vom JavaScript-Teil ausgewertet, der die Suche mithilfe des onsubmit-Attributs des <form>-Tags ausführt:

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

Der onsubmit-Handler gibt false zurück, wodurch verhindert wird, dass das Formular an den Server gesendet wird. Der JavaScript-Code ist dafür verantwortlich, die erforderlichen Informationen aus dem Formular abzurufen und die Suche durchzuführen.

Der HTML-Teil enthält auch die Bereiche (<div>-HTML-Tags), in denen die Suchergebnisse angezeigt werden.

Verwalten des Abonnementschlüssels

Zum Speichern des Schlüssels wird der persistente Browserspeicher verwendet. Auf diese Weise müssen Sie nicht den Abonnementschlüssel der Bing-Suche-API im Code hinterlegen. Vor dem Speichern des Schlüssels wird der Benutzer zur Eingabe des Schlüssels aufgefordert. Wird der Schlüssel zu einem späteren Zeitpunkt von der API zurückgewiesen, wird der gespeicherte Schlüssel für ungültig erklärt, und der Benutzer wird zur erneuten Eingabe aufgefordert.

In den Funktionen storeValue und retrieveValue wird entweder das localStorage-Objekt (nicht von allen Browsern unterstützt) oder ein Cookie verwendet. Diese Funktionen werden in der getSubscriptionKey()-Funktion zum Speichern und Abrufen des Benutzerschlüssels verwendet.

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

Das onsubmit-Attribut des HTML-Tags <form> ruft die bingWebSearch-Funktion auf, um die Suchergebnisse zurückzugeben. bingWebSearch verwendet getSubscriptionKey() zur Authentifizierung aller Abfragen. Wie in der obigen Definition gezeigt, fordert getSubscriptionKey den Benutzer zur Eingabe des Schlüssels auf, falls dieser noch nicht eingegeben wurde. Der Schlüssel wird anschließend gespeichert und kann von der Anwendung auch später verwendet werden.

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

Auswählen der Suchoptionen

Die folgende Abbildung zeigt das Abfragetextfeld und Optionen, die eine Suche definieren.

Optionen der Bing-News-Suche

Das HTML-Formular enthält folgende Elemente:

Element BESCHREIBUNG
where Ein Dropdownmenü zur Auswahl des Markts (Ort und Sprache), der für die Suche verwendet wird.
query Das Textfeld, in das Suchbegriffe eingegeben werden.
modules Kontrollkästchen, mit denen bestimmte Module der Ergebnisse, alle Ergebnisse oder zugehörige Videos heraufgestuft werden können.
when Dropdownmenü, mit dem die Suche optional auf den letzten Tag, die letzte Woche oder den letzten Monat eingeschränkt werden kann.
safe Ein Kontrollkästchen, mit dem festgelegt wird, ob das SafeSearch-Feature von Bing verwendet werden soll. Mit diesem werden Inhalte herausgefiltert, die für Minderjährige nicht geeignet sind.
count Verborgenes Feld. Die Anzahl der Suchergebnisse, die bei jeder Anforderung zurückgegeben werden. Damit lässt sich die Anzahl der angezeigten Ergebnisse pro Seite festlegen.
offset Verborgenes Feld. Der Offset für das erste Suchergebnis der Anforderung. Dieser wird zur Unterteilung der Suchergebnisse in mehrere Seiten verwendet. Der Offset wird bei einer neuen Anforderung auf 0 zurückgesetzt.

Hinweis

Die Bing-Websuche bietet noch andere Abfrageparameter. Hier werden nur einige davon verwendet.

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

Der Parameter SafeSearch kann bei einem tatsächlichen API-Aufruf beispielsweise den Wert strict oder moderate haben, wobei moderate der Standardwert ist.

Ausführen der Anforderung

Die BingWebSearch-Funktion verwendet auf der Grundlage der Abfrage, der Optionszeichenfolge und des API-Schlüssels ein XMLHttpRequest-Objekt, um die Anforderung an den Bing-Suche-API-Endpunkt zu senden. Sie können den unten angegebenen globalen Endpunkt oder den Endpunkt der benutzerdefinierten Unterdomäne verwenden, der im Azure-Portal für Ihre Ressource angezeigt wird.

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

Nach erfolgreichem Abschluss der HTTP-Anforderung ruft JavaScript den load-Ereignishandler handleOnLoad() auf, um eine erfolgreiche HTTP-GET-Anforderung an die API zu verarbeiten.

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

Wichtig

Wenn bei einem Suchvorgang ein Fehler auftritt, gibt die Bing-News-Suche-API einen HTTP-Statuscode zurück, der vom Code 200 abweicht und Fehlerinformationen in der JSON-Antwort enthält. Wenn die Anzahl der Anforderungen begrenzt war, gibt die API zusätzlich eine leere Antwort zurück. Eine erfolgreiche HTTP-Anforderung ist nicht zwangsläufig mit einer erfolgreichen Suche gleichzusetzen.

Ein Großteil des Codes in den beiden vorangehenden Funktionen ist für die Fehlerbehandlung zuständig. In folgenden Phasen können Fehler auftreten:

Phase Mögliche Fehler Verarbeitet durch
Erstellen des JavaScript-Anforderungsobjekts Ungültige URL try/catch-Block
Senden der Anforderung Netzwerkfehler, abgebrochene Verbindungen Ereignishandler error und abort
Durchführen der Suche Ungültige Anforderung, ungültiger JSON-Code, Beschränkung der Anforderungsanzahl Tests im load-Ereignishandler

Fehler werden behandelt, indem renderErrorMessage() zusammen mit allen bekannten Fehlerinformationen aufgerufen wird. Wenn die Antwort alle Fehlertests besteht, wird renderSearchResults() zur Anzeige der Suchergebnisse auf der Seite aufgerufen.

Anzeigen der Suchergebnisse

Die Hauptfunktion zum Anzeigen der Suchergebnisse ist renderSearchResults(). Diese Funktion verwendet den vom Bing-News-Suche-Dienst zurückgegebenen JSON-Code und rendert die News-Ergebnisse sowie verwandte Suchvorgänge (falls vorhanden).

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

Die Suchergebnisse werden als value-Objekt der obersten Ebene in der JSON-Antwort zurückgegeben. Diese werden dann an die renderResultsItems()-Funktion übergeben, die diese durchläuft und eine Funktion zum Rendern der einzelnen Elemente in HTML aufruft. Der resultierende HTML-Code wird an renderSearchResults() zurückgegeben, wo er in den Bereich results auf der Seite eingefügt wird.

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

Die Bing-News-Suche-API gibt bis zu vier verschiedene Arten verwandter Ergebnisse zurück, und zwar jeweils in einem eigenen Objekt der obersten Ebene. Sie lauten wie folgt:

Beziehung BESCHREIBUNG
pivotSuggestions Abfragen, bei denen ein Pivot-Wort in der ursprünglichen Suche durch ein anderes ersetzt wird. Wenn Sie beispielsweise nach „rote Blumen“ suchen, kann ein Pivot-Wort „rote“ sein, und ein Pivot-Vorschlag kann „gelbe Blumen“ sein.
queryExpansions Abfragen, bei denen die ursprüngliche Suche durch Hinzufügen weiterer Begriffe eingegrenzt wird. Wenn Sie beispielsweise nach „Microsoft Surface“ suchen, kann eine Abfrageerweiterung „Microsoft Surface Pro“ sein.
relatedSearches Abfragen, die ebenfalls von anderen Benutzern eingegeben wurden, die auch die ursprüngliche Suche eingegeben haben. Wenn Sie z.B. nach „Mount Rainier“ suchen, kann eine verwandte Suche „Mount Saint Helens“ lauten.
similarTerms Abfragen, die von der Bedeutung her der ursprünglichen Suche ähneln. Wenn Sie beispielsweise nach „Schulen“ suchen, kann ein ähnlicher Begriff „Ausbildung“ sein.

Wie bereits bei renderSearchResults() gezeigt, werden nur die relatedItems-Vorschläge gerendert und die resultierenden Links auf der Randleiste der Seite eingefügt.

Rendern von Ergebniselementen

Im JavaScript-Code kann das searchItemRenderers-Objekt Renderer enthalten, also Funktionen, die für jeden Suchergebnistyp HTML-Code generieren. Die Seite für die Videosuche verwendet nur videos. Informationen zu verschiedenen Typen von Renderern finden Sie in anderen Tutorials.

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

Für eine Funktion zum Rendern können die folgenden Parameter angegeben werden:

Parameter BESCHREIBUNG
item Das JavaScript-Objekt mit Eigenschaften des Elements, z.B. seine URL und Beschreibung.
index Der Index des Ergebniselements innerhalb der Auflistung.
count Die Anzahl der Elemente in der Auflistung der Suchergebniselemente.

Die Parameter index und count können beispielsweise zum Nummerieren von Ergebnissen, zum Generieren von speziellem HTML-Code für den Anfang oder das Ende einer Collection oder zum Einfügen von Zeilenumbrüchen nach einer bestimmten Anzahl von Elementen verwendet werden. Wenn ein Renderer diese Funktionalität nicht benötigt, muss er diese beiden Parameter nicht akzeptieren.

Der video-Renderer wird im folgenden JavaScript-Auszug gezeigt. Bei Verwendung des Videos-Endpunkts sind alle Ergebnisse vom Typ Videos. Die searchItemRenderers werden im folgenden Codesegment gezeigt.

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

Die Funktion zum Rendern führt folgende Vorgänge aus:

  • Erstellt ein Absatztag, weist es der images-Klasse zu und übergibt es mit einem Pushvorgang an den HTML-Array.
  • Berechnet die Größe von Miniaturbildern (die Breite ist auf 60 Pixel festgelegt, die Höhe wird proportional berechnet).
  • Erstellt das HTML-Tag <img>, mit dem das Miniaturbild angezeigt wird.
  • Erstellt die <a>-HTML-Tags, die eine Verknüpfung mit dem Bild und der Seite, die das Bild enthält, darstellen.
  • Erstellt die Beschreibung, die Informationen über das Bild und die Website, auf der sich das Bild befindet, angibt.

Die Größe des Miniaturbilds wird sowohl im <img>-Tag als auch in den Feldern h und w in der URL des Miniaturbilds verwendet. Bing gibt ein Miniaturbild genau dieser Größe zurück.

Persistentes Speichern der Client-ID

Antworten, die von Bing-Suche-APIs gesendet werden, können einen X-MSEdge-ClientID-Header enthalten, der mit den nachfolgenden Anforderungen an die API zurückgesendet werden sollte. Wenn mehrere Bing-Suche-APIs genutzt werden, sollte nach Möglichkeit dieselbe Client-ID für alle APIs verwendet werden.

Durch die Bereitstellung des X-MSEdge-ClientID-Headers können die Bing-Suche-APIs alle Suchvorgänge eines Benutzers zuordnen, was zwei entscheidende Vorteile mit sich bringt.

Erstens kann die Bing-Suchmaschine auf diese Weise Kontextinformationen aus vorherigen Suchvorgängen nutzen, um den Benutzern relevantere Ergebnisse anzuzeigen. Wenn ein Benutzer in der Vergangenheit beispielsweise nach Segelbegriffen gesucht hat, können bei einer Suche nach „Knoten“ möglicherweise Informationen zu Segelknoten zurückgegeben werden.

Zweitens wählt Bing möglicherweise Benutzer zufällig aus, die die Möglichkeit haben, neue Features zu testen, bevor diese allen Benutzern zur Verfügung gestellt werden. Indem Sie bei jeder Anforderung dieselbe Client-ID bereitstellen, stellen Sie sicher, dass Benutzer, denen das Feature angezeigt wird, dieses Feature dauerhaft nutzen können. Ohne die Client-ID wird das Feature in den Suchergebnissen möglicherweise scheinbar willkürlich aus- oder eingeblendet.

Durch Browsersicherheitsrichtlinien (CORS) kann der X-MSEdge-ClientID-Header möglicherweise nicht von JavaScript verwendet werden. Diese Einschränkung tritt auf, wenn sich der Ursprung der Suchantwort von dem der Seite unterscheidet, die den Suchvorgang angefordert hat. In einer Produktionsumgebung sollten Sie zum Umgang mit dieser Richtlinie ein serverseitiges Skript hosten, das den API-Aufruf für die Domain durchführt, die auch für die Webseite genutzt wird. Da der Ursprung des Skripts mit dem Ursprung der Webseite übereinstimmt, kann der X-MSEdge-ClientID-Header von JavaScript verwendet werden.

Hinweis

In einer Web-App für eine Produktionsumgebung sollten Sie die Anforderung serverseitig ausführen. Andernfalls muss der Schlüssel der Bing-Suche-API auf der Webseite hinterlegt werden, wo er im Quelltext für alle Personen zugänglich ist. Dies müssen Sie vermeiden, da ansonsten unbefugte Dritte Anforderungen unter Verwendung Ihres API-Abonnementschlüssels Anforderungen senden können, die Ihnen in Rechnung gestellt werden.

In der Entwicklungsphase können Sie die Bing-Websuche-API-Anforderung über einen CORS-Proxy senden. In der Antwort eines solchen Proxys befindet sich ein Access-Control-Expose-Headers-Header, der Antwortheader zulässt und für JavaScript zur Verfügung stellt.

Die Installation eines CORS-Proxys, mit dem die Tutorial-App auf den Client-ID-Header zugreifen kann, ist schnell und unkompliziert. Installieren Sie Node.js, falls Sie dies noch nicht getan haben. Geben Sie anschließend folgenden Befehl in ein Befehlsfenster ein:

npm install -g cors-proxy-server

Passen Sie den Endpunkt der Bing-Websuche-API in der HTML-Datei wie folgt an:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Starten Sie abschließend den CORS-Proxy mit folgendem Befehl:

cors-proxy-server

Lassen Sie das Fenster während der Nutzung der Tutorial-App geöffnet. Wenn Sie das Fenster schließen, wird auch die Ausführung des Proxys beendet. Im Bereich mit den erweiterbaren HTTP-Headern unter den Suchergebnissen wird nun u.a. der X-MSEdge-ClientID-Header angezeigt. Hier können Sie überprüfen, ob dieser für alle Anforderungen identisch ist.

Nächste Schritte