ajax – 如何使SPA SEO可爬行?
我一直在努力如何使一个SPA可以爬行的谷歌google基于谷歌的
instructions.即使有很多一般的解释,我找不到任何地方更详细的逐步教程与实际的例子。完成后,我想分享我的解决方案,以便其他人也可以使用它,并可能进一步改善。
我使用MVC与Webapi控制器, Phantomjs在服务器端,和 Durandal在客户端启用推状态;我也使用 Breezejs进行客户端 – 服务器数据交互,所有这些都强烈推荐,但我会尽量给出一个足够全面的解释,也将帮助人们使用其他平台。
在开始之前,请确保您了解什么google
requires,特别是使用漂亮和丑陋的URL。现在让我们看看实现:
客户端 在客户端,你只有一个html页面,通过AJAX调用动态地与服务器交互。这就是SPA是什么。客户端中的所有a标签都是在我的应用程序中动态创建的,稍后我们将看到如何使这些链接对服务器中的Googlebot可见。每个这样的代码都需要能够在href标记中有一个漂亮的网址,这样google的bot才能抓取它。您不希望在客户端点击它时使用href部分(即使您确实希望服务器能够解析它,我们稍后会看到),因为我们可能不希望加载新页面,只有使AJAX调用获取一些数据显示在页面的一部分,并通过javascript更改URL(例如使用HTML5 pushstate或与Durandaljs)。因此,我们有google的href属性以及onclick,当用户点击链接时执行该作业。现在,由于我使用push状态,我不想在URL上的任何#,所以一个典型的标签可能看起来像这样: 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 }); } }; }); 这里有几个重要的事情需要注意: >第一个路由(路由:”)用于没有额外数据的URL,即http://www.xyz.com。在此页面中,使用AJAX加载常规数据。在此页面中实际上可能没有任何标签。您将需要添加以下标记,以便google的bot将知道该怎么做: 这就是我们在客户端所需要的。它也可以实现与哈希的URL(在Durandal你简单删除pushState:true)。更复杂的部分(至少对我来说)是服务器部分: 服务器端 我在服务器端使用MVC 4.5与WebAPI控制器。服务器实际上需要处理3种类型的网址:google生成的网址 – 漂亮和丑陋的网址,以及与客户端浏览器中显示的网址格式相同的“简单”网址。让我们看看如何做: 漂亮的URL和“简单”的URL首先被服务器解释为好像试图引用一个不存在的控制器。服务器看到类似于http://www.xyz.com/category/subCategory/product111的东西,并查找名为“category”的控制器。所以在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加载数据,所以这里的诀窍是调用默认的“索引”控制器,如果没有引用任何控制器;我通过在所有’category’和’subCategory’参数之前向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; } } } 但是,丑陋的URL呢?这些是由google的bot创建的,应该返回包含用户在浏览器中看到的所有数据的纯HTML。对于这个我使用phantomjs. Phantom是一个无头浏览器做浏览器在客户端做的 – 但在服务器端。换句话说,phantom知道(除其他事项外)如何通过URL获取网页,解析它,包括运行其中的所有javascript代码(以及通过AJAX调用获取数据),并返回HTML反映DOM。如果你使用MS Visual Studio Express,你很多人想通过这个link安装phantom。 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; } } } 这也从’app_start’中的’filterConfig.cs’调用: 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,"SEOcreateSnapshot.js"),url),FileName = Path.Combine(appRoot,"binphantomjs.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(); } } } 相关的视图很简单,只是一行代码: 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的页面,我得到的基本代码从:-)。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |