it-swarm.asia

كيفية جعل SEO الزحف؟

لقد كنت أعمل على كيفية جعل SPA قابلاً للزحف بواسطة google استنادًا إلى google's تعليمات . على الرغم من وجود عدد قليل من التفسيرات العامة ، لم أتمكن من العثور في أي مكان على برنامج تعليمي أكثر دقة خطوة بخطوة مع أمثلة فعلية. بعد الانتهاء من هذا ، أود أن أشارك حلي حتى يتمكن الآخرون من الاستفادة منه وربما تحسينه.
أنا أستخدم MVC مع وحدات تحكم Webapi ، و Phantomjs على جانب الخادم ، و Durandal على جانب العميل مع تمكين Push-state ؛ يمكنني أيضًا استخدام Breezejs للتفاعل مع بيانات خادم العميل ، وكل ذلك أوصي به بشدة ، لكنني سأحاول تقديم توضيح عام بما فيه الكفاية سيساعد أيضًا الأشخاص الذين يستخدمون منصات أخرى.

143
beamish

قبل البدء ، يرجى التأكد من فهم ما جوجل يتطلب ، وخاصة استخدام جميلة و البشعة عناوين المواقع. الآن لنرى التنفيذ:

جانب العميل

من جانب العميل ، لديك فقط صفحة html واحدة تتفاعل مع الخادم ديناميكيًا عبر مكالمات AJAX. هذا هو ما SPA حول. يتم إنشاء جميع علامات a الموجودة في جانب العميل بشكل حيوي في طلبي ، وسنرى لاحقًا كيفية جعل هذه الروابط مرئية لروبوت google في الخادم. يجب أن تكون كل علامة a قادرة على امتلاك pretty URL في العلامة href بحيث يقوم برنامج الروبوت google بالزحف إليها. لا تريد استخدام الجزء href عندما ينقر العميل عليه (على الرغم من أنك تريد أن يتمكن الخادم من تحليله ، فسنرى ذلك لاحقًا) ، لأننا قد لا نريد تحميل صفحة جديدة ، فقط لإجراء مكالمة AJAX للحصول على بعض البيانات ليتم عرضها في جزء من الصفحة وتغيير عنوان URL عبر javascript (على سبيل المثال باستخدام HTML5 pushstate أو باستخدام Durandaljs). لذلك ، لدينا سمة href لكل من google وكذلك على onclick والتي تؤدي المهمة عندما ينقر المستخدم على الرابط. الآن ، بما أنني أستخدم Push-state ، فأنا لا أريد أي # على عنوان URL ، لذلك قد تبدو علامة a النموذجية كما يلي:
<a href="http://www.xyz.com/#!/category/subCategory/product111" onClick="loadProduct('category','subCategory','product111')>see product111...</a>

قد تكون "الفئة" و "الفئة الفرعية" عبارات أخرى ، مثل "الاتصالات" و "الهواتف" أو "أجهزة الكمبيوتر" و "أجهزة الكمبيوتر المحمولة" لمتجر للأجهزة الكهربائية. من الواضح أنه سيكون هناك العديد من الفئات والفئات الفرعية المختلفة. كما ترى ، يرتبط الرابط مباشرةً بالفئة والفئة الفرعية والمنتج ، وليس كمعلمات إضافية لصفحة "متجر" معينة مثل http://www.xyz.com/store/category/subCategory/product111. هذا لأنني أفضل الروابط الأقصر والأبسط. وهذا يعني أنني لن أكون هناك فئة تحمل نفس الاسم كواحدة من "صفحاتي" ، أي "حول".
لن أخوض في كيفية تحميل البيانات عبر AJAX (الجزء onclick) ، ابحث فيه على google ، وهناك العديد من التفسيرات الجيدة. الشيء المهم الوحيد الذي أود ذكره هنا هو أنه عندما ينقر المستخدم على هذا الرابط ، أريد أن يظهر عنوان URL في المتصفح بهذا الشكل:
http://www.xyz.com/category/subCategory/product111. وهذا هو عنوان URL لا يتم إرساله إلى الخادم! تذكر ، إنه منتجع صحي حيث يتم كل التفاعل بين العميل والخادم عبر أجاكس ، لا توجد روابط على الإطلاق! يتم تنفيذ جميع "الصفحات" على جانب العميل ، ولا يقوم عنوان URL المختلف بإجراء مكالمة إلى الخادم (يحتاج الخادم إلى معرفة كيفية التعامل مع عناوين URL هذه في حالة استخدامها كروابط خارجية من موقع آخر إلى موقعك ، سنرى ذلك لاحقًا في جزء جانب الخادم). الآن ، يتم التعامل مع هذا رائعة بواسطة Durandal. أوصي به بشدة ، ولكن يمكنك أيضًا تخطي هذا الجزء إذا كنت تفضل التقنيات الأخرى. إذا اخترت ذلك ، وكنت تستخدم أيضًا MS Visual Studio Express 2012 للويب مثلي ، فيمكنك تثبيت Durandal Starter Kit ، وهناك ، في Shell.js ، استخدم شيئًا مثل هذا:

define(['plugins/router', 'durandal/app'], function (router, app) {
    return {
        router: router,
        activate: function () {
            router.map([
                { route: '', title: 'Store', moduleId: 'viewmodels/store', nav: true },
                { route: 'about', moduleId: 'viewmodels/about', nav: true }
            ])
                .buildNavigationModel()
                .mapUnknownRoutes(function (instruction) {
                    instruction.config.moduleId = 'viewmodels/store';
                    instruction.fragment = instruction.fragment.replace("!/", ""); // for pretty-URLs, '#' already removed because of Push-state, only ! remains
                    return instruction;
                });
            return router.activate({ pushState: true });
        }
    };
});

هناك بعض الأشياء المهمة التي يجب ملاحظتها هنا:

  1. المسار الأول (مع route:'') مخصص لعنوان URL الذي لا يحتوي على بيانات إضافية ، أي http://www.xyz.com. في هذه الصفحة ، يمكنك تحميل البيانات العامة باستخدام أجاكس. في الواقع قد لا توجد علامات a على الإطلاق في هذه الصفحة. ستحتاج إلى إضافة العلامة التالية حتى يعرف الروبوت في google ما يجب القيام به به:
    <meta name="fragment" content="!">. هذه العلامة ستجعل الروبوت google يحول عنوان URL إلى www.xyz.com?_escaped_fragment_= الذي سنراه لاحقًا.
  2. يعد المسار "about" مجرد مثال على رابط إلى "صفحات" أخرى قد ترغب في تطبيقها على تطبيق الويب الخاص بك.
  3. الآن ، الجزء الصعب هو أنه لا يوجد طريق "فئة" ، وقد يكون هناك العديد من الفئات المختلفة - ليس لأي منها مسار محدد مسبقًا. هذا هو المكان الذي يأتي فيه mapUnknownRoutes. وهو يرسم هذه المسارات غير المعروفة إلى مسار "المتجر" ويزيل أيضًا أي "!" من عنوان URL في حال كان pretty URL تم إنشاؤه بواسطة محرك بحث Google. يأخذ مسار 'store' المعلومات في خاصية 'fragment' ويقوم بإجراء AJAX للحصول على البيانات وعرضها وتغيير عنوان URL محليًا. في طلبي ، لا أحمل صفحة مختلفة لكل مكالمة ؛ يمكنني فقط تغيير جزء الصفحة الذي تكون فيه هذه البيانات وثيقة الصلة وأيضًا تغيير عنوان URL محليًا.
  4. لاحظ pushState:true الذي يرشد دوراندال لاستخدام عناوين URL لحالة الدفع.

هذا هو كل ما نحتاجه في جانب العميل. يمكن تنفيذه أيضًا باستخدام عناوين URL المجزأة (في Durandal يمكنك ببساطة إزالة pushState:true لذلك). الجزء الأكثر تعقيدًا (على الأقل بالنسبة لي ...) كان جزء الخادم:

جانب الخادم

أنا أستخدم MVC 4.5 على جانب الخادم مع وحدات تحكم WebAPI. يحتاج الخادم فعليًا إلى التعامل مع 3 أنواع من عناوين URL: تلك التي تنشئها google - كل من pretty و ugly وأيضًا عنوان URL "بسيط" بنفس التنسيق الذي يظهر به متصفح العميل. لنلقي نظرة على كيفية القيام بذلك:

يتم أولاً تفسير عناوين URL الجميلة والعناوين "البسيطة" من قبل الخادم كما لو كانت تحاول الرجوع إلى وحدة تحكم غير موجودة. يرى الخادم شيئًا مثل http://www.xyz.com/category/subCategory/product111 ويبحث عن وحدة تحكم باسم "الفئة". لذلك في web.config أضف السطر التالي لإعادة توجيهه إلى وحدة تحكم معينة لمعالجة الأخطاء:

<customErrors mode="On" defaultRedirect="Error">
    <error statusCode="404" redirect="Error" />
</customErrors><br/>

الآن ، يحول هذا عنوان URL إلى شيء مثل: http://www.xyz.com/Error?aspxerrorpath=/category/subCategory/product111. أريد إرسال عنوان URL إلى العميل الذي سيقوم بتحميل البيانات عبر AJAX ، لذلك فإن الحيلة هنا هي استدعاء وحدة التحكم الافتراضية "الفهرس" كما لو لم تكن تشير إلى أي وحدة تحكم ؛ أفعل ذلك بواسطة مضيفا علامة تجزئة لعنوان URL قبل جميع معلمات "الفئة" و "الفئة الفرعية" ؛ لا يتطلب عنوان URL المجزأة أي وحدة تحكم خاصة باستثناء وحدة التحكم الافتراضية "الفهرس" ويتم إرسال البيانات إلى العميل الذي يقوم بعد ذلك بإزالة التجزئة ويستخدم المعلومات بعد التجزئة لتحميل البيانات عبر AJAX. فيما يلي رمز وحدة التحكم في معالج الأخطاء:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

using System.Web.Routing;

namespace eShop.Controllers
{
    public class ErrorController : ApiController
    {
        [HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH"), AllowAnonymous]
        public HttpResponseMessage Handle404()
        {
            string [] parts = Request.RequestUri.OriginalString.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries);
            string parameters = parts[ 1 ].Replace("aspxerrorpath=","");
            var response = Request.CreateResponse(HttpStatusCode.Redirect);
            response.Headers.Location = new Uri(parts[0].Replace("Error","") + string.Format("#{0}", parameters));
            return response;
        }
    }
}


ولكن ماذا عن عناوين قبيحة؟ يتم إنشاء هذه بواسطة برنامج google ويجب أن تُرجع HTML عادي يحتوي على جميع البيانات التي يراها المستخدم في المتصفح. لهذا أنا استخدم فانتومجس . Phantom هو متصفح مقطوع الرأس يقوم بما يفعله المتصفح من جانب العميل - ولكن من جانب الخادم. بمعنى آخر ، يعرف الوهمية (من بين أشياء أخرى) كيفية الحصول على صفحة ويب عبر عنوان URL ، وتحليلها بما في ذلك تشغيل جميع شفرة جافا سكريبت فيها (وكذلك الحصول على البيانات عبر AJAX المكالمات) ، وإعطاء يمكنك نسخ HTML الذي يعكس DOM. إذا كنت تستخدم MS Visual Studio Express ، فأنت تريد أن تقوم بتثبيت phantom عبر هذا link .
ولكن أولاً ، عندما يتم إرسال عنوان URL القبيح إلى الخادم ، يجب أن نلتقطه ؛ لهذا ، أضفت إلى المجلد "App_start" الملف التالي:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace eShop.App_Start
{
    public class AjaxCrawlableAttribute : ActionFilterAttribute
    {
        private const string Fragment = "_escaped_fragment_";

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var request = filterContext.RequestContext.HttpContext.Request;

            if (request.QueryString[Fragment] != null)
            {

                var url = request.Url.ToString().Replace("?_escaped_fragment_=", "#");

                filterContext.Result = new RedirectToRouteResult(
                    new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } });
            }
            return;
        }
    }
}

هذا يسمى من 'filterConfig.cs' أيضًا في 'App_start':

using System.Web.Mvc;
using eShop.App_Start;

namespace eShop
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
            filters.Add(new AjaxCrawlableAttribute());
        }
    }
}

كما ترون ، فإن 'AjaxCrawlableAttribute' يقوم بتوجيه عناوين URL القبيحة إلى وحدة تحكم باسم "HtmlSnapshot" ، وهنا وحدة التحكم هذه:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace eShop.Controllers
{
    public class HtmlSnapshotController : Controller
    {
        public ActionResult returnHTML(string url)
        {
            string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);

            var startInfo = new ProcessStartInfo
            {
                Arguments = String.Format("{0} {1}", Path.Combine(appRoot, "seo\\createSnapshot.js"), url),
                FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                StandardOutputEncoding = System.Text.Encoding.UTF8
            };
            var p = new Process();
            p.StartInfo = startInfo;
            p.Start();
            string output = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            ViewData["result"] = output;
            return View();
        }

    }
}

view المرتبط بسيط جدًا ، فقط سطر واحد من التعليمات البرمجية:
@Html.Raw( ViewBag.result )
كما ترون في وحدة التحكم ، يقوم الشبح بتحميل ملف javascript باسم createSnapshot.js ضمن مجلد أنشأته يسمى seo. هنا ملف جافا سكريبت هذا:

var page = require('webpage').create();
var system = require('system');

var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();

page.onResourceReceived = function (response) {
    if (requestIds.indexOf(response.id) !== -1) {
        lastReceived = new Date().getTime();
        responseCount++;
        requestIds[requestIds.indexOf(response.id)] = null;
    }
};
page.onResourceRequested = function (request) {
    if (requestIds.indexOf(request.id) === -1) {
        requestIds.Push(request.id);
        requestCount++;
    }
};

function checkLoaded() {
    return page.evaluate(function () {
        return document.all["compositionComplete"];
    }) != null;
}
// Open the page
page.open(system.args[1], function () { });

var checkComplete = function () {
    // We don't allow it to take longer than 5 seconds but
    // don't return until all requests are finished
    if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
        clearInterval(checkCompleteInterval);
        var result = page.content;
        //result = result.substring(0, 10000);
        console.log(result);
        //console.log(results);
        phantom.exit();
    }
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);

أريد أولاً أن أشكر Thomas Davis على الصفحة التي حصلت على الكود الأساسي من :-).
ستلاحظ شيئًا غريبًا هنا: تستمر الشبح في إعادة تحميل الصفحة حتى تعود دالة checkLoaded() إلى حقيقة. لماذا هذا؟ هذا لأن SPA الخاص بي يقوم بإجراء عدة AJAX مكالمة للحصول على جميع البيانات ووضعها في DOM على صفحتي ، ولا يمكن للفانتوم معرفة متى أكملت جميع المكالمات قبل أن تعيدني إلى الوراء انعكاس HTML لل DOM. ما فعلته هنا هو بعد المكالمة الأخيرة AJAX وأضيف <span id='compositionComplete'></span> ، حتى إذا وجدت هذه العلامة ، فأنا أعرف أن DOM قد اكتمل. أفعل ذلك ردًا على حدث Durandal compositionComplete ، راجع هنا للمزيد. إذا لم يحدث ذلك خلال 10 ثوانٍ ، فسأستسلم (يجب أن يستغرق الأمر أكثر من ثانية واحدة). يحتوي HTML الذي تم إرجاعه على جميع الروابط التي يراها المستخدم في المتصفح. لن يعمل البرنامج النصي بشكل صحيح لأن علامات <script> الموجودة في لقطة HTML لا تشير إلى عنوان URL الصحيح. يمكن تغيير هذا أيضًا في ملف javascript phantom ، لكنني لا أعتقد أن هذا أمر ضروري لأن HTML snapshort يستخدم فقط بواسطة google للحصول على روابط a وليس لتشغيل javascript؛ هذه الروابط فعل قم بالإشارة إلى عنوان URL جميل ، وإذا كنت في الواقع ، إذا حاولت رؤية لقطة HTML في المستعرض ، فسوف تحصل على أخطاء جافا سكريبت ، لكن جميع الروابط ستعمل بشكل صحيح وتوجهك إلى الخادم مرة أخرى باستخدام عنوان URL جميل هذه المرة تحصل على صفحة العمل.
هذه هي. الآن يعرف الخادم كيفية التعامل مع كل من عناوين URL الجميلة والقبيحة ، مع تمكين حالة الضغط على كل من الخادم والعميل. يتم التعامل مع جميع عناوين URL القبيحة بنفس الطريقة باستخدام الوهمية ، لذلك ليست هناك حاجة لإنشاء وحدة تحكم منفصلة لكل نوع من أنواع المكالمات.
هناك شيء واحد قد تفضل تغييره ألا يتمثل في إجراء مكالمة عامة من الفئة/الفئة/الفئة/المنتج ، ولكن لإضافة "متجر" حتى يظهر الرابط بشكل مثل: http://www.xyz.com/store/category/subCategory/product111. سيؤدي هذا إلى تجنب المشكلة في حل بلدي المتمثلة في معاملة جميع عناوين URL غير الصالحة كما لو كانت بالفعل مكالمات إلى وحدة تحكم "الفهرس" ، وأفترض أنه يمكن معالجتها ثم داخل وحدة تحكم "store" دون إضافة web.config التي أظهرتها في الاعلى.

122
beamish

أصبح بإمكان Google الآن تقديم صفحات SPA: إلغاء مخطط الزحف لدينا AJAX

32
Edward Olamisan

فيما يلي رابط لتسجيل تسجيل من خلال صف التدريب الخاص بـ Ember.js الذي استضفته في لندن في 14 أغسطس. إنها تحدد إستراتيجية لكلٍ من تطبيق جانب العميل ولك من أجل تطبيق جانب الخادم ، كما تقدم عرضًا حيًا لكيفية تطبيق هذه الميزات ستزود تطبيق JavaScript أحادي الصفحة بتدهور كبير حتى بالنسبة للمستخدمين الذين تم إيقاف تشغيل JavaScript .

يستخدم PhantomJS للمساعدة في الزحف إلى موقع الويب الخاص بك.

باختصار ، الخطوات المطلوبة هي:

  • لديك إصدار مستضاف من تطبيق الويب الذي تريد الزحف إليه ، يجب أن يحتوي هذا الموقع على جميع البيانات التي لديك قيد الإنتاج
  • اكتب تطبيق JavaScript (PhantomJS Script) لتحميل موقع الويب الخاص بك
  • أضف index.html (أو "/") إلى قائمة عناوين URL للزحف إليها
    • أدخل عنوان URL الأول المضاف إلى قائمة الزحف
    • تحميل الصفحة وتقديم DOM
    • ابحث عن أي روابط في الصفحة المحملة التي ترتبط بموقعك (تصفية عناوين URL)
    • أضف هذا الرابط إلى قائمة عناوين URL "القابلة للزحف" ، إذا لم يتم الزحف إليها بالفعل
    • تخزين DOM المقدمة في ملف على نظام الملفات ، ولكن تجريد جميع علامات البرنامج النصي أولاً
    • في النهاية ، قم بإنشاء ملف Sitemap.xml باستخدام عناوين URL التي تم الزحف إليها

بمجرد الانتهاء من هذه الخطوة ، يعود الأمر إلى الواجهة الخلفية لخدمة الإصدار الثابت من HTML الخاص بك كجزء من علامة noscript في تلك الصفحة. سيسمح هذا لـ Google ومحركات البحث الأخرى بالزحف إلى كل صفحة على موقع الويب الخاص بك ، على الرغم من أن تطبيقك في الأصل عبارة عن تطبيق من صفحة واحدة.

ربط screencast مع التفاصيل الكاملة:

http://www.devcasts.io/p/spas-phantomjs-and-seo/#

4
Joachim H. Skeie

يمكنك استخدام http://sparender.com/ الذي يمكّن تتبع ارتباطات تطبيقات الصفحة الواحدة بشكل صحيح.

0
ddtxra

يمكنك استخدام أو إنشاء الخدمة الخاصة بك لتقديم prerender الخاص بك مع خدمة تسمى prerender. يمكنك التحقق من ذلك على موقع الويب الخاص به prerender.io وعلى مشروعه github (يستخدم PhantomJS ويجعل موقع الويب الخاص بك لك).

من السهل جدا أن تبدأ. ما عليك سوى إعادة توجيه طلبات الزواحف إلى الخدمة وسيتلقى أتش تي أم أل المقدمة.

0
gabrielperales