البرنامج التعليمي: إنشاء تطبيق ويب أحادي الصفحة
تحذير
في 30 أكتوبر 2020، انتقلت واجهات برمجة التطبيقات بحث Bing من خدمات Azure الذكاء الاصطناعي إلى خدمات بحث Bing. تتوفر هذه الوثائق للرجوع إليها فحسب. للحصول على وثائق محدثة، راجع الوثائق الخاصة ببحث Bing API . للحصول على إرشادات بشأن إنشاء موارد Azure جديدة لـ Bing Search، راجع إنشاء مورد Bing Search من خلال Azure Marketplace.
تتيح لك واجهة برمجة تطبيقات بحث أخبار Bing البحث في الويب والحصول على نتائج من نوع الأخبار ذات الصلة باستعلام البحث. في هذا البرنامج التعليمي، نقوم بإنشاء تطبيق ويب من صفحة واحدة يستخدم Bing News Search API لعرض نتائج البحث على الصفحة. يتضمن التطبيق مكونات HTML وCSS وJavaScript. التعليمة البرمجية المصدر لهذا النموذج متاحة على GitHub.
ملاحظة
تظهر عناوين JSON وHTTP في أسفل الصفحة عند النقر عليها استجابة JSON ومعلومات طلب HTTP. يمكن أن تكون هذه التفاصيل مفيدة عند استكشاف الخدمة.
يوضح تطبيق البرنامج التعليمي كيفية:
- إجراء استدعاء Bing News Search API في JavaScript
- تمرير خيارات البحث إلى Bing News Search API
- عرض نتائج بحث الأخبار من أربع فئات: أي نوع، أو نشاط تجاري، أو صحة، أو نهج، من الأطر الزمنية 24 ساعة، أو الأسبوع الماضي، أو الشهر، أو كل الأوقات المتاحة
- نتائج البحث من خلال الصفحة
- معالجة معرف عميل Bing، ومفتاح الاشتراك في API
- معالجة الأخطاء التي قد تحدث
صفحة البرنامج التعليمي مكتفية ذاتيا تمامًا، لا تستخدم أي أطر عمل خارجية، أو أوراق أنماط، أو ملفات صور. لا تستخدم إلا ميزات لغة JavaScript المدعومة على نطاق واسع، وتعمل مع الإصدارات الحالية من جميع متصفحات الويب الرئيسية.
المتطلبات الأساسية
لمتابعة البرنامج التعليمي، تحتاج إلى مفتاح اشتراك Bing Search API. إذا لم تكن لديك هذه، فستحتاج إلى إنشائها:
- اشتراك Azure - إنشاء اشتراك مجانًا
- بمجرد حصولك على اشتراك Azure، أنشئ مورد Bing Search في مدخل Azure للحصول على المفتاح ونقطة النهاية. بعد انتشاره، انقر فوق Go to resource.
مكونات التطبيق
مثل أي تطبيق ويب من صفحة واحدة، يتضمن تطبيق البرنامج التعليمي هذا ثلاثة أجزاء:
- HTML - تحديد بنية الصفحة ومحتواها
- CSS - تحديد مظهر الصفحة
- JavaScript -- تحديد سلوك الصفحة
معظم HTML وCSS تقليدية؛ لذلك لا يناقش البرنامج التعليمي ذلك. يحتوي HTML على نموذج البحث الذي يقوم المستخدم بإدخال الاستعلام، وتحديد خيارات البحث فيه. يتم توصيل النموذج بـ JavaScript الذي يقوم بالبحث باستخدام سمة onsubmit
لعلامة <form>
:
<form name="bing" onsubmit="return newBingNewsSearch(this)">
يعود المعالج onsubmit
بالنتيجة false
، الذي يمنع إرسال النموذج إلى أحد الخوادم. تقوم التعليمات البرمجية JavaScript بعمل جمع المعلومات الضرورية من النموذج، وإجراء البحث.
يحتوي HTML أيضا على الأقسام (علامات <div>
لـ HTML) حيث تظهر نتائج البحث.
إدارة مفتاح الاشتراك
لتجنب الاضطرار إلى تضمين مفتاح اشتراك Bing Search API في التعليمات البرمجية، نستخدم المخزن الدائم للمتصفح لتخزين المفتاح. قبل تخزين المفتاح، نطلب مفتاح المستخدم. إذا تم رفض المفتاح لاحقًا بواسطة API، فإننا نبطل المفتاح المخزن بحيث تتم مطالبة المستخدم مرة أخرى.
نحن نحدد دالة storeValue
وretrieveValue
التي تستخدم إما الكائن localStorage
(لا تدعمها جميع المتصفحات) أو ملف تعريف الارتباط. تستخدم الدالة getSubscriptionKey()
هذه الدالات لتخزين مفتاح المستخدم واسترداده. يمكنك استخدام نقطة النهاية العامة أدناه، أو نقطة نهاية المجال الفرعي المخصص المعروضة في مدخل Microsoft Azure لموردك.
// Cookie names for data we store
API_KEY_COOKIE = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";
// Bing Search API endpoint
BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/news";
// ... omitted definitions of storeValue() and retrieveValue()
// 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;
}
تقوم العلامة <form>
HTMLonsubmit
باستدعاء الدالة bingWebSearch
للعودة بنتائج البحث. تقوم bingWebSearch
باستخدام getSubscriptionKey()
لمصادقة كل استعلام. كما هو موضح في التعريف السابق، يطالب getSubscriptionKey
المستخدم بالمفتاح إذا لم يتم إدخال المفتاح. ثم يتم تخزين المفتاح لاستمرار استخدامه بواسطة التطبيق.
<form name="bing" onsubmit="this.offset.value = 0; return bingWebSearch(this.query.value,
bingSearchOptions(this), getSubscriptionKey())">
تحديد خيارات البحث
يظهر الشكل التالي مربع نص الاستعلام والخيارات التي تحدد البحث عن أخبار حول تمويل المدرسة.
يتضمن نموذج HTML عناصر بالأسماء التالية:
العنصر | الوصف |
---|---|
where |
قائمة منسدلة لاختيار السوق (الموقع واللغة) المستخدمة للبحث. |
query |
حقل النص لإدخال عبارات البحث. |
category |
مربعات الاختيار لتعزيز أنواع معينة من النتائج. الترويج للصحة، على سبيل المثال، يزيد من ترتيب الأخبار الصحية. |
when |
القائمة المنسدلة لتقيد البحث اختياريًا على آخر يوم أو أسبوع أو شهر. |
safe |
مربع اختيار يشير إلى ما إذا كان سيتم استخدام ميزة Bing SafeSearch لتصفية نتائج "البالغين". |
count |
حقل مخفي. عدد نتائج البحث التي سيتم إرجاعها عند كل طلب. التغيير لعرض نتائج أقل أو أكثر في كل صفحة. |
offset |
حقل مخفي. إزاحة نتيجة البحث الأولى في الطلب، تستخدم لترحيل الصفحات. يتم إعادة تعيينها إلى 0 عند وجود طلب جديد. |
ملاحظة
يقدم Bing Web Search معلمات استعلام أخرى. نحن نستخدم القليل منهم فقط.
// 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);
for (var i = 0; i < form.category.length; i++) {
if (form.category[i].checked) {
category = form.category[i].value;
break;
}
}
if (category.valueOf() != "all".valueOf()) {
options.push("category=" + category);
}
options.push("count=" + form.count.value);
options.push("offset=" + form.offset.value);
return options.join("&");
}
على سبيل المثال، قد تكون المعلمة SafeSearch
في استدعاء API الفعلي strict
أو moderate
أو off
مع كون moderate
الإعداد الافتراضي. نموذجنا، ومع ذلك، يستخدم مربع الاختيار، الذي يحتوي على حالتين فقط. تقوم تعليمة JavaScript البرمجية بتحويل هذا الإعداد إلى إما strict
أو off
(moderate
غير مستخدم).
تنفيذ الطلب
ونظراً للاستعلام وسلسلة الخيارات ومفتاح API، تستخدم الدالة BingNewsSearch
كائن XMLHttpRequest
لتقديم الطلب إلى نقطة نهاية Bing News Search.
// perform a search given query, options string, and API key
function bingNewsSearch(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();
if (category.valueOf() != "all".valueOf()) {
var queryurl = BING_ENDPOINT + "/search?" + "?q=" + encodeURIComponent(query) + "&" + options;
}
else
{
if (query){
var queryurl = BING_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;
}
else {
var queryurl = BING_ENDPOINT + "?" + 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;
}
عند إكمال طلب HTTP بنجاح، يستدعي JavaScript معالج الأحداث load
، والدالة handleBingResponse()
لمعالجة طلب HTTP GET ناجح إلى API.
// 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 === "News") {
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"));
}
}
هام
طلب HTTP الناجح لا يعني بالضرورة نجاح البحث نفسه. إذا حدث خطأ في عملية البحث، تعود Bing News Search API بالتعليمات البرمجية لحالة HTTP غير 200، وتدرج معلومات الخطأ في استجابة JSON. بالإضافة إلى ذلك، إذا كان الطلب محدود السعر، فإن API تعود باستجابة فارغة.
تُخصص الكثير من التعليمات البرمجية في كل من الدالات السابقة لمعالجة الأخطاء. قد تحدث أخطاء في المراحل التالية:
المرحلة | الخطأ (الأخطاء) المحتمل (المحتملة) | معالجة بواسطة |
---|---|---|
إنشاء كائن طلب JavaScript | عنوان URL غير صالح | حظر try /catch |
تقديم الطلب | أخطاء الشبكة، الاتصالات التي توقفت قبل الإكمال | error وabort معالجات الأحداث |
تنفيذ البحث | طلب غير صالح، JSON غير صالح، حدود الأسعار | اختبارات في معالج الأحداث load |
تتم معالجة الأخطاء عن طريق استدعاء renderErrorMessage()
باستخدام أية تفاصيل معروفة حول الخطأ. إذا اجتازت الاستجابة تحدي اختبارات الخطأ بالكامل، فإننا نستدعي renderSearchResults()
لعرض نتائج البحث في الصفحة.
عرض نتائج البحث
الدالة الرئيسية لعرض نتائج البحث هي renderSearchResults()
. تأخذ هذه الدالة JSON التي تم إرجاعها بواسطة خدمة Bing News Search، وتقوم بتقديم نتائج الأخبار، وعمليات البحث ذات الصلة، إن وجدت.
// 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);
showDiv("results", renderResults(results.value));
if (results.relatedSearches)
showDiv("sidebar", renderRelatedItems(results.relatedSearches));
}
يتم إرجاع نتائج البحث ككائن المستوى الأعلى value
في استجابة JSON. ونقوم بتمريرها إلى دالة renderResults()
الخاصة بنا، والتي تتكرر من خلالهم وتستدعي إحدى الدوال لتقديم كل بند في HTML. يتم إرجاع HTML الناتج إلى renderSearchResults()
، حيث يتم إدراجه في القسم results
بالصفحة.
function renderResults(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.news(items[i], i, len));
}
return html.join("\n\n");
}
تعود Bing News Search API بما يصل إلى أربعة أنواع مختلفة من النتائج ذات الصلة، كل منها في كائن المستوى الأعلى الخاص به. وهي كالتالي:
علاقة | الوصف |
---|---|
pivotSuggestions |
الاستعلامات التي تستبدل كلمة محورية في البحث الأصلي بكلمة مختلفة. على سبيل المثال، إذا كنت تبحث عن "زهور حمراء"، فقد تكون الكلمة المحورية "حمراء"، وقد يكون الاقتراح المحوري هو "الزهور الصفراء." |
queryExpansions |
الاستعلامات التي تضيق نطاق البحث الأصلي بإضافة المزيد من المصطلحات. على سبيل المثال، إذا كنت تبحث عن "Microsoft Surface"، فقد يكون توسيع الاستعلام "Microsoft Surface Pro." |
relatedSearches |
الاستعلامات التي تم إدخالها أيضًا من قبل مستخدمين آخرين قاموا بإدخال البحث الأصلي. على سبيل المثال، إذا كنت تبحث عن "جبل رينييه"، فقد يكون البحث ذي الصلة "جبل سانت هيلين." |
similarTerms |
الاستعلامات التي تشبه البحث الأصلي في المعنى. على سبيل المثال، إذا كنت تبحث عن "مدارس"، فقد يكون المصطلح المماثل هو "التعليم." |
كما رأينا سابقاً في renderSearchResults()
، نحن لا نقدم إلا اقتراحات relatedItems
ونضع الارتباطات الناتجة في الشريط الجانبي للصفحة.
تقديم عناصر النتائج
في تعليمات JavaScript البرمجية، يحتوي الكائن searchItemRenderers
على دوال renderers: التي تقوم بإنشاء HTML لكل نوع من نتائج البحث.
searchItemRenderers = {
news: function(item) { ... },
webPages: function (item) { ... },
images: function(item, index, count) { ... },
relatedSearches: function(item) { ... }
}
دالة جهاز العرض قد تقبل المعلمات التالية:
المعلمة | الوصف |
---|---|
item |
كائن JavaScript الذي يحتوي على خصائص العنصر، مثل عنوان URL الخاص به ووصفه. |
index |
فهرس عنصر النتيجة ضمن مجموعته. |
count |
عدد العناصر الموجودة في مجموعة عناصر نتائج البحث. |
يمكن استخدام المعلمات index
وcount
لترقيم النتائج، لإنشاء HTML خاص لبداية أو نهاية مجموعة، لإدراج فواصل الأسطر بعد عدد معين من العناصر وهكذا. إذا كان جهاز العرض لا يحتاج هذه الدالة، فإنه لا يحتاج إلى قبول هذه المعلمتين.
يظهر جهاز العرض news
في المقتبس التالي من JavaScript.
// render news story
news: function (item) {
var html = [];
html.push("<p class='news'>");
if (item.image) {
width = 60;
height = Math.round(width * item.image.thumbnail.height / item.image.thumbnail.width);
html.push("<img src='" + item.image.thumbnail.contentUrl +
"&h=" + height + "&w=" + width + "' width=" + width + " height=" + height + ">");
}
html.push("<a href='" + item.url + "'>" + item.name + "</a>");
if (item.category) html.push(" - " + item.category);
if (item.contractualRules) { // MUST display source attributions
html.push(" (");
var rules = [];
for (var i = 0; i < item.contractualRules.length; i++)
rules.push(item.contractualRules[i].text);
html.push(rules.join(", "));
html.push(")");
}
html.push(" (" + getHost(item.url) + ")");
html.push("<br>" + item.description);
return html.join("");
},
وظيفة تقديم الأخبار:
- إنشاء علامة فقرة وتعيينها إلى الفئة
news
ودفعها إلى مصفوفة HTML. - حساب حجم الصورة المصغرة (يتم إصلاح العرض عند 60 بكسل، ويتم حساب الارتفاع بشكل متناسب).
- إنشاء علامة HTML
<img>
لعرض الصورة المصغرة. - تُنشئ علامات HTML
<a>
التي ترتبط بالصورة والصفحة التي تحتوي عليها. - إنشاء الوصف الذي يعرض معلومات حول الصورة والموقع الذي تظهر عليه.
يتم استخدام حجم الصورة المصغرة في كل من العلامة <img>
والحقول h
وw
في عنوان URL للصورة المصغرة. ثم تقدم خدمة Bing للصور المصغرة صورة مصغرة بهذا الحجم بالضبط.
معرف العميل الدائم
قد تتضمن الاستجابات من Bing search APIs رأس X-MSEdge-ClientID
يجب إرسالها مرة أخرى إلى API مع طلبات متتالية. إذا تم استخدام عدة Bing Search APIs، يجب استخدام معرف العميل نفسه مع كل منهم، إذا كان ذلك ممكنًا.
يسمح توفير الرأس X-MSEdge-ClientID
لـ Bing APIs بربط كافة عمليات البحث للمستخدم، الأمر الذي يعود بفائدتين مهمتين.
أولاً، يسمح لمحرك بحث Bing بتطبيق السياق الماضي على عمليات البحث للعثور على النتائج التي ترضي المستخدم بشكل أفضل. إذا كان المستخدم قد بحث من قبل عن مصطلحات تتعلق بالإبحار، على سبيل المثال، فإن البحث اللاحق عن "العقد" قد يعيد بشكل تفضيلي معلومات عن العقد المستخدمة في الإبحار.
ثانيًا، قد يحدد Bing المستخدمين عشوائيًا لتجربة ميزات جديدة قبل أن تتاح على نطاق واسع. يضمن توفير معرف العميل نفسه مع كل طلب أن المستخدمين الذين يرون الميزة سيتمكنون من رؤيتها على الدوام. بدون معرف العميل، قد يرى المستخدم ميزة تظهر وتختفي عشوائيًا على ما يبدو، في نتائج البحث الخاصة به.
قد تمنع نهج أمان المستعرض (CORS) توفر الرأس X-MSEdge-ClientID
لـJavaScript. يحدث هذا القيد عندما يكون لاستجابة البحث أصل مختلف عن الصفحة التي طلبت ذلك. في بيئة الإنتاج، تجب عنونة هذا النهج عن طريق استضافة برنامج نصي من جانب الخادم يقوم باستدعاء API على نفس المجال كصفحة ويب. وبما أن البرنامج النصي له نفس الأصل الخاص بصفحة الويب، فإن الرأس X-MSEdge-ClientID
يكون متاحًا بعد ذلك لـJavaScript.
ملاحظة
في تطبيق ويب الإنتاج، يجب تنفيذ الطلب من جانب الخادم. وإلا، يجب تضمين مفتاح Bing Search API في صفحة الويب، حيث يتوفر لأي شخص يقوم بعرض المصدر. تتم محاسبتك على جميع الاستخدامات تحت مفتاح اشتراك API الخاص بك، حتى الطلبات المقدمة من أطراف غير مصرح لهم؛ لذلك من المهم عدم كشف مفتاحك.
لأغراض تتعلق بالتطوير، يمكنك تقديم طلب API Bing Web Search من خلال وكيل CORS. الاستجابة من وكيل لديه رأس Access-Control-Expose-Headers
يسمح برؤوس الاستجابة ويجعلها متاحة لـJavaScript.
من السهل تثبيت وكيل CORS للسماح لتطبيق البرنامج التعليمي الخاص بنا بالوصول إلى رأس معرف العميل. أولاً، إذا لم يكن لديك بالفعل، فقم بتثبيت Node.js. ثم قم بإصدار الأمر التالي في نافذة الأمر:
npm install -g cors-proxy-server
بعد ذلك، قم بتغيير نقطة نهاية Bing Web Search في ملف HTML إلى:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search
وأخيرًا، ابدأ تشغيل وكيل CORS باستخدام الأمر التالي:
cors-proxy-server
اترك نافذة الأمر مفتوحة أثناء استخدام تطبيق البرنامج التعليمي؛ إغلاق النافذة يوقف الوكيل. في قسم رؤوس HTTP القابلة للتوسيع أسفل نتائج البحث، يمكنك الآن مشاهدة الرأس X-MSEdge-ClientID
(من بين رؤوس أخرى) والتحقق من مطابقتها لكل طلب.