Sdílet prostřednictvím


Kurz: Jednostránková aplikace s vyhledáváním videí

Upozornění

30. října 2020 se rozhraní API Vyhledávání Bingu přesunula ze služeb Azure AI na Vyhledávání Bingu Services. Tato dokumentace je k dispozici pouze pro referenci. Aktualizovanou dokumentaci najdete v dokumentaci k rozhraní API Bingu pro vyhledávání. Pokyny k vytváření nových prostředků Azure pro vyhledávání Bingu najdete v tématu Vytvoření prostředku Vyhledávání Bingu prostřednictvím Azure Marketplace.

Rozhraní API Bingu pro vyhledávání videí umožňuje hledat na webu a získávat výsledky videí relevantní pro vyhledávací dotaz. V tomto kurzu sestavíme jednostránkovou webovou aplikaci, která používá rozhraní API pro vyhledávání Bingu k zobrazení výsledků hledání na stránce. Aplikace zahrnuje komponenty HTML, CSS a JavaScriptu.

Poznámka

Hlavičky JSON a HTTP v dolní části stránky při kliknutí zobrazí informace odpovědi JSON a požadavku HTTP. Tyto podrobnosti můžou být užitečné při prozkoumávání služby.

Nezpracované výsledky JSON, HTTP

Tato ukázková aplikace předvádí, jak:

  • Provést volání rozhraní API Bingu pro vyhledávání videí v JavaScriptu
  • Předat možnosti hledání do rozhraní API pro vyhledávání Bingu
  • Zobrazit výsledky hledání videí nebo volitelně zahrnout webové stránky, zprávy nebo obrázky
  • Vyhledávat v časových intervalech 24 hodin, minulý týden, měsíc nebo všech dostupných časech
  • Procházet stránky výsledků hledání
  • Používat ID klienta Bingu a klíč předplatného rozhraní API
  • Zpracovat chyby, které můžou nastat

Stránka kurzu je zcela nezávislá. Nepoužívá žádná externí rozhraní, šablony stylů ani soubory obrázků. Používá jenom běžně podporované funkce jazyka JavaScript a funguje s aktuálními verzemi všech hlavních webových prohlížečů.

V tomto kurzu probereme vybrané části zdrojového kódu. K dispozici je kompletní zdrojový kód. Pokud chcete spustit příklad, zkopírujte a vložte zdrojový kód do textového editoru a uložte ho jako bing.html.

Komponenty aplikace

Stejně jako každá jednostránková webová aplikace i tato aplikace zahrnuje tři části:

  • HTML – definuje strukturu a obsah stránky
  • Šablony stylů CSS – definují vzhled stránky
  • JavaScript – definuje chování stránky

Většina částí HTML a šablon stylů CSS je konvenční, proto se jimi tento kurz nezabývá. Kód HTML obsahuje vyhledávací formulář, do kterého uživatel zadá dotaz a vybere možnosti hledání. Formulář je spojený s JavaScriptem, který vyhledávání provádí s využitím atributu onsubmit značky <form>:

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

Obslužná rutina onsubmit vrátí false. Díky tomu se formulář neodesílá na server. Kód JavaScriptu shromažďuje nezbytné informace z formuláře a provádí hledání.

Kód HTML také obsahuje úseky (značky HTML <div>), kde se zobrazují výsledky hledání.

Správa klíče předplatného

Aby se nemusel klíč předplatného rozhraní API pro vyhledávání Bingu zahrnout do kódu, používáme k uložení klíče trvalé úložiště prohlížeče. Před uložením klíče vyzveme k zadání uživatelova klíče. Pokud rozhraní API klíč později odmítne, uložený klíč zneplatníme, takže uživateli se znovu zobrazí výzva.

Definujeme funkce storeValue a retrieveValue, které používají buď objekt localStorage (který nepodporují všechny prohlížeče) nebo soubor cookie. Funkce getSubscriptionKey() tyto funkce používá k ukládání a načítání uživatelova klíče.

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

Značka HTML <form>onsubmit volá funkci bingWebSearch k vrácení výsledků hledání. bingWebSearch používá getSubscriptionKey() k ověření každého dotazu. Jak je vidět v předchozí definici, getSubscriptionKey vyzve uživatele k zadání klíče, pokud klíč nebyl zadán. Klíč se pak uloží pro další používání ze strany aplikace.

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

Výběr možností hledání

Následující obrázek znázorňuje textové pole dotazu a možnosti, které definují vyhledávání.

Možnosti vyhledávání zpráv Bingu

Formulář HTML obsahuje prvky s těmito názvy:

Element Popis
where Rozevírací nabídka pro výběr trhu (polohy a jazyka) pro vyhledávání.
query Textového pole k zadání hledaných termínů.
modules Zaškrtávací políčka pro podporu konkrétních modulů výsledků, všech výsledků nebo souvisejících videí.
when Rozevírací nabídka pro volitelné omezení vyhledávání na poslední den, týden nebo měsíc.
safe Zaškrtávací políčko označující, jestli se má používat funkce Bingu Bezpečné hledání k filtrování výsledků „pro dospělé“.
count Skryté pole. Počet výsledků hledání, který se má vrátit u jednotlivých žádostí. Můžete změnit, aby se na stránce zobrazovalo méně nebo více výsledků.
offset Skryté pole. Posun prvního výsledku hledání v požadavku, sloužící ke stránkování. Při novém požadavku se resetuje na 0.

Poznámka

Vyhledávání na webu Bingu nabízí další parametry dotazu. Používáme jenom několik z nich.

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

Například SafeSearch parametr ve skutečném volání rozhraní API může být strict, nebo moderate, přičemž moderate výchozí hodnota je .

Provedení požadavku

Na základě dotazu, řetězce možností a klíče rozhraní API funkce BingWebSearch použije objekt XMLHttpRequest k provedení požadavku na koncový bod vyhledávání Bingu. Můžete použít globální koncový bod níže nebo vlastní koncový bod subdomény zobrazený v Azure Portal pro váš prostředek.

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

Při úspěšném dokončení požadavku HTTP volá JavaScript obslužnou rutinu události load, handleOnLoad(), ke zpracování úspěšného požadavku HTTP GET na rozhraní API.

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

Důležité

Pokud v operaci vyhledávání dojde k chybě, rozhraní API Bingu pro vyhledávání zpráv vrátí stavový kód HTTP jiný než 200 zahrnující informace o chybě v odpovědi JSON. Kromě toho, pokud byl požadavek omezený rychlostí, vrátí rozhraní API prázdnou odpověď. Úspěšný požadavek HTTP nemusí nutně znamenat, že bylo úspěšné samotné vyhledávání.

Velká část kódu v obou předchozích funkcích je vyhrazená zpracování chyb. V následujících fázích můžou nastat chyby:

Fáze Potenciální chyby Čím se zpracují
Vytváření javascriptového objektu požadavku Neplatná adresa URL Blok try/catch
Provedení žádosti Chyby sítě, přerušená připojení Obslužné rutiny událostí error a abort
Provedení vyhledávání Neplatný požadavek, neplatný JSON, omezení rychlosti Testy v obslužné rutině události load

Chyby se zpracovávají voláním renderErrorMessage() se všemi známými podrobnostmi o chybě. Pokud odpověď úspěšné projde kompletní řadou testů chyb, voláme renderSearchResults() k zobrazení výsledků hledání na stránce.

Zobrazení výsledků hledání

Hlavní funkcí pro zobrazení výsledků hledání je renderSearchResults(). Tato funkce vezme JSON vrácené službou vyhledávání zpráv Bingu a vykreslí výsledky zpráv a souvisejících hledání, pokud existují.

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

Výsledky hledání se v odpovědi JSON vrátí jako objekt value nejvyšší úrovně. Předáme je do naší funkce renderResultsItems(), která skrz ně iteruje a volá funkci k vykreslení každé položky do kódu HTML. Výsledný kód HTML se vrátí do renderSearchResults(), kde se vloží do úseku results na stránce.

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

Rozhraní API Bingu pro vyhledávání zpráv vrátí až čtyři různé druhy souvisejících výsledků, každý ve vlastním objektu nejvyšší úrovně. Jsou to tyto:

Relace Description
pivotSuggestions Dotazy, které nahradí pivotové slovo v původním vyhledávání jiným. Pokud třeba vyhledáváte „červené květiny“, pivotové slovo může být „červené“ a pivotový návrh může být „žluté květiny“.
queryExpansions Dotazy, které původní hledání zúží přidáním dalších výrazů. Pokud třeba vyhledáváte „Microsoft Surface“, rozšíření dotazu může být „Microsoft Surface Pro“.
relatedSearches Dotazy, které také zadali ostatní uživatelé, kteří zadali původní vyhledávání. Pokud třeba vyhledáváte „Mount Rainier“, související hledání může být „Mt. Saint Helens“.
similarTerms Dotazy, které mají podobný význam jako původní vyhledávání. Pokud třeba vyhledáváte „školy“, podobný výraz může být „vzdělávání“.

Jak jste už viděli v renderSearchResults(), vykreslujeme jenom návrhy relatedItems a výsledné odkazy umisťujeme na boční panel stránky.

Vykreslování položek výsledků

V kódu JavaScriptu může objekt, searchItemRenderers, obsahovat funkce renderers:, které generují kód HTML pro každý druh výsledku hledání. Stránka vyhledávání videí využívá jenom videos. Různé typy rendererů najdete v dalších kurzech.

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

Funkce rendereru může přijímat tyto parametry:

Parametr Popis
item Objekt JavaScriptu obsahující vlastnosti položky, jako je její adresa URL a popis.
index Index položky výsledků v rámci jeho kolekce.
count Počet položek v kolekci položek výsledků hledání.

Parametry index a count se můžou použít k číslování výsledků, generování zvláštního kódu HTML pro začátek nebo konec kolekce, vložení konců řádků za určitý počet položek a tak dále. Pokud renderer tuto funkci nepotřebuje, nepotřebuje tyto dva parametry přijímat.

Vykreslovací video modul je znázorněn v následujícím výňatku JavaScriptu. Při použití koncového bodu Videos jsou všechny výsledky typu Videos. searchItemRenderers jsou uvedené v následujícím segmentu kódu:

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

Funkce rendereru:

  • Vytvoří značku odstavce, přiřadí ji ke třídě images a předá ji do pole html.
  • Vypočítá velikost miniatury obrázku (šířka je pevně nastavená na 60 pixelů, výška se vypočítá poměrně).
  • Vytvoří značku HTML <img> k zobrazení miniatury obrázku.
  • Vytvoří značky HTML <a>, které odkazují na obrázek a na stránku, která ho obsahuje.
  • Vytvoří popis, který zobrazuje informace o obrázku a webu, na kterém se nachází.

Velikost miniatury se používá ve značce <img> i v polích h a w v adrese URL miniatury. Bing vrátí miniaturu přesně této velikosti.

Zachování ID klienta

Odpovědi z rozhraní API pro vyhledávání Bingu můžou zahrnovat hlavičku X-MSEdge-ClientID, která by se měla odesílat zpět do rozhraní API v následných požadavcích. Pokud se používá více rozhraní API pro vyhledávání Bingu, mělo by se pro všechny používat stejné ID klienta, pokud je to možné.

Poskytnutí hlavičky X-MSEdge-ClientID umožňuje rozhraním API Bingu spojit si všechna uživatelova vyhledávání. To má dvě důležité výhody.

Zaprvé to umožňuje, aby vyhledávací web Bing na vyhledávání použil minulý kontext a našel výsledky, které uživatele více uspokojí. Pokud uživatel v minulosti vyhledával třeba výrazy týkající se lodí, může pozdější vyhledání „uzlů“ přednostně vrátit informace o uzlech používaných při plavbě lodí.

Za druhé může Bing náhodně vybírat uživatele k vyzkoušení nových funkcí, než budou všeobecně dostupné. Poskytnutí stejného ID klienta s každým požadavkem zajistí, že uživatelé, kteří tuto funkci vidí, ji vidí vždy. Bez ID klienta může uživatel funkci ve svých výsledcích hledání zdánlivě náhodně někdy vidět a jindy ne.

Zásady zabezpečení prohlížeče (CORS) můžou bránit tomu, aby byla hlavička X-MSEdge-ClientID pro JavaScript dostupná. K tomuto omezení dochází, když odpověď na vyhledávání má jiný zdroj než stránka, která o ni požádala. V produkčním prostředí je potřeba tyto zásady vyřešit hostováním skriptu na straně serveru, který provádí volání rozhraní API ve stejné doméně jako webová stránka. Protože tento skript má stejný původ jako webová stránka, hlavička X-MSEdge-ClientID je pak pro JavaScript dostupná.

Poznámka

V produkční webové aplikaci byste měli požadavek provádět na straně serveru. Jinak musí být klíč rozhraní API pro vyhledávání Bingu součástí webové stránky, kde je k dispozici každému, kdo si zobrazí zdroj. Účtuje se vám veškeré využívání vašeho klíče předplatného rozhraní API, dokonce i požadavky provedené neoprávněnými stranami, proto je důležité klíč nezveřejňovat.

Pro účely vývoje můžete požadavek na rozhraní API Bingu pro vyhledávání na webu provést prostřednictvím proxy serveru CORS. Odpověď z takového proxy serveru má hlavičku Access-Control-Expose-Headers , která umožňuje hlavičky odpovědí a zpřístupňuje je JavaScriptu.

Nainstalovat proxy server CORS a povolit naší ukázkové aplikaci přístup k hlavičce ID klienta je snadné. Nejdřív nainstalujte Node.js, pokud jste to ještě neudělali. Pak zadejte v příkazovém okně tento příkaz:

npm install -g cors-proxy-server

V dalším kroku změňte koncový bod vyhledávání na webu Bingu v souboru HTML na:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Nakonec spusťte proxy server CORS pomocí tohoto příkazu:

cors-proxy-server

Při používání ukázkové aplikace nechte příkazové okno otevřené. Zavřením okna se zastaví proxy server. V rozbalitelné sekci hlaviček HTTP pod výsledky hledání teď uvidíte hlavičku X-MSEdge-ClientID (mimo jiné) a můžete zkontrolovat, jestli je stejná pro každý požadavek.

Další kroky