私はグーグルの instructions に基づいてグーグルがSPAをクロール可能にする方法に取り組んできました。かなり多くの一般的な説明がありますが、実際の例を含むより詳細なステップバイステップのチュートリアルはどこにも見つかりませんでした。これを終えた後、他の人もそれを利用し、場合によってはさらに改善できるように、ソリューションを共有したいと思います。MVC
コントローラーでWebapi
を使用しています。サーバー側で Phantomjs を使用し、Push-state
を有効にしてクライアント側で Durandal を使用しています。 Breezejs をクライアントとサーバー間のデータのやり取りにも使用しますが、これらすべてを強くお勧めしますが、他のプラットフォームを使用する人々にも役立つ一般的な十分な説明をしようと思います。
始める前に、Google requires の内容、特に 可愛い そして 醜い URL。実装を見てみましょう:
クライアント側には、AJAX呼び出しを介してサーバーと動的にやり取りする単一のhtmlページのみがあります。それがSPAの目的です。クライアント側のすべてのa
タグはアプリケーションで動的に作成されます。これらのリンクをサーバーのgoogleのボットに表示する方法については後で説明します。そのような各a
タグは、Googleのボットがクロールできるように、href
タグにpretty URL
を含めることができる必要があります。クライアントがクリックしたときにhref
部分が使用されないようにします(サーバーで解析できるようにしたい場合でも、後で確認します)。ロードする新しいページ。ページの一部に表示されるデータを取得するAJAX呼び出しを行い、JavaScriptを使用してURLを変更します(HTML5を使用するpushstate
またはDurandaljs
)。したがって、Googleのhref
属性と、ユーザーがリンクをクリックしたときにジョブを実行するonclick
属性の両方があります。ここで、Push-state
を使用しているため、URLに#
が必要ないため、一般的なa
タグは次のようになります。<a href="http://www.xyz.com/#!/category/subCategory/product111" onClick="loadProduct('category','subCategory','product111')>see product111...</a>
「category」と「subCategory」は、電化製品店の「通信」と「電話」または「コンピューター」と「ラップトップ」など、おそらく他のフレーズでしょう。明らかに、多くの異なるカテゴリとサブカテゴリがあります。ご覧のとおり、リンクはhttp://www.xyz.com/store/category/subCategory/product111
などの特定の「ストア」ページへの追加パラメーターとしてではなく、カテゴリー、サブカテゴリー、および製品への直接のリンクです。これは、短くてシンプルなリンクを好むためです。これは、「ページ」の1つと同じ名前、つまり「約」のカテゴリが存在しないことを意味します。
AJAX(onclick
部分)を介してデータをロードする方法については説明しません。Googleで検索してください。多くの良い説明があります。ここで言及したい唯一の重要なことは、ユーザーがこのリンクをクリックすると、ブラウザーのURLが次のようになることです。http://www.xyz.com/category/subCategory/product111
。そして、これはURLがサーバーに送信されないことです!これは、クライアントとサーバー間のすべての対話がAJAXを介して行われるSPAであり、リンクはまったくないことを思い出してください!すべての「ページ」はクライアント側に実装され、異なるURLはサーバーを呼び出しません(サーバーは、これらのURLが別のサイトからサイトへの外部リンクとして使用される場合にこれらのURLを処理する方法を知る必要があります。後でサーバー側で確認します)。現在、これはDurandalによって素晴らしく処理されています。強くお勧めしますが、他の技術を好む場合はこの部分をスキップすることもできます。それを選択して、私のようなMS Visual Studio Express 2012 for Webも使用している場合は、 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 });
}
};
});
ここで注意すべき重要な点がいくつかあります。
route:''
を使用)は、余分なデータを持たないURL、つまりhttp://www.xyz.com
です。このページでは、AJAXを使用して一般データをロードします。このページにはa
タグが実際にはまったくない場合があります。次のタグを追加して、Googleのボットがそれをどう処理するかを認識できるようにします。<meta name="fragment" content="!">
。このタグにより、GoogleのボットはURLをwww.xyz.com?_escaped_fragment_=
に変換します。これについては後で説明します。mapUnknownRoutes
の出番です。これらの不明なルートを「ストア」ルートにマップし、「!」も削除します。 Googleの検索エンジンによって生成されたpretty URL
の場合はURLから。 'store'ルートは 'fragment'プロパティの情報を取得し、AJAX呼び出しを行ってデータを取得し、表示し、URLをローカルで変更します。私のアプリケーションでは、そのような呼び出しごとに異なるページをロードしません。このデータが関連するページの部分のみを変更し、URLもローカルで変更します。pushState:true
に注目してください。クライアント側で必要なのはこれだけです。ハッシュ化されたURLでも実装できます(Durandalでは、そのためにpushState:true
を削除するだけです)。より複雑な部分(少なくとも私にとっては...)はサーバー部分でした:
サーバー側でWebAPI
コントローラーを使用してMVC 4.5
を使用しています。サーバーは実際に3種類のURLを処理する必要があります。Googleによって生成されたもの-pretty
とugly
の両方、およびクライアントのブラウザーに表示されるものと同じ形式の「単純な」URLこれを行う方法を見てみましょう。
プリティ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
のようなものに変換されます。 AJAX経由でデータをロードするクライアントにURLを送信したいので、ここでのトリックは、コントローラーを参照していないかのようにデフォルトの「インデックス」コントローラーを呼び出すことです。私はそれをする 追加 すべての '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;
}
}
}
しかし、どうですか UいURL?これらはgoogleのボットによって作成され、ユーザーがブラウザーに表示するすべてのデータを含むプレーンHTMLを返す必要があります。これには phantomjs を使用します。 Phantomは、ブラウザーがクライアント側で行っていることを実行するヘッドレスブラウザーです。ただし、サーバー側で実行されます。言い換えれば、ファントムは(とりわけ)URL経由でWebページを取得し、その中のすべてのjavascriptコードを実行することを含めて解析し(同様にAJAX呼び出し経由でデータを取得する)、 DOMを反映するHTMLをバックアップします。 MS Visual Studio Expressを使用している場合、多くの場合、この 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;
}
}
}
これは、「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, "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
は非常に単純で、1行のコードのみです。@Html.Raw( ViewBag.result )
コントローラーで確認できるように、ファントムは、seo
という名前で作成したフォルダーの下にcreateSnapshot.js
という名前のjavascriptファイルをロードします。このjavascriptファイルは次のとおりです。
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()
関数がtrueを返すまで、ファントムはページの再読み込みを続けます。何故ですか?これは、特定のSPAが複数のAJAX呼び出しを行ってすべてのデータを取得し、それをページ上のDOMに配置し、ファントムがHTMLのリフレクションを返す前にすべての呼び出しが完了したことを知ることができないためですDOM。ここでやったことは、最後のAJAX呼び出しの後に<span id='compositionComplete'></span>
を追加することです。このタグが存在する場合、DOMが完了したことがわかります。これはDurandalのcompositionComplete
イベントに応じて行います。詳細については here を参照してください。これが10秒以内に起こらない場合、私はあきらめます(1秒しかかからないはずです)。返されるHTMLには、ユーザーがブラウザーで見るすべてのリンクが含まれます。 HTMLスナップショットに存在する<script>
タグは正しいURLを参照しないため、スクリプトは正しく機能しません。これは、javascriptファントムファイルでも変更できますが、HTMLスナップショートはa
リンクを取得し、javascriptを実行しないためにGoogleでのみ使用されるため、これが必要だとは思いません。これらのリンク 行う きれいなURLを参照します。実際、ブラウザでHTMLスナップショットを表示しようとすると、javascriptエラーが発生しますが、すべてのリンクが適切に機能し、今回はきれいなURLを使用してもう一度サーバーに移動します作業ページ。
これです。これで、サーバーは、きれいなURLとbothいURLの両方を処理する方法を知っており、サーバーとクライアントの両方でプッシュ状態が有効になっています。すべてのugいURLはファントムを使用して同じように扱われるため、呼び出しのタイプごとに個別のコントローラーを作成する必要はありません。
変更したいことの1つは、一般的な「category/subCategory/product」呼び出しを行うのではなく、リンクがhttp://www.xyz.com/store/category/subCategory/product111
のようになるように「store」を追加することです。これにより、すべての無効なURLが実際に「インデックス」コントローラーへの呼び出しであるかのように扱われるというソリューションでの問題を回避できます。また、これらは、web.config
上記。
GoogleはSPAページをレンダリングできるようになりました。 AJAXクロールスキームの廃止
8月14日にロンドンでホストしたEmber.jsトレーニングクラスのスクリーンキャスト録画へのリンクを次に示します。クライアント側アプリケーションとサーバー側アプリケーションの両方の戦略の概要を説明し、これらの機能を実装することでJavaScriptがオフになっているユーザーでもJavaScriptシングルページアプリがどのようにグレースフルデグラデーションを提供するかを実演します。 。
PhantomJSを使用して、Webサイトのクロールを支援します。
要するに、必要な手順は次のとおりです。
このステップが完了すると、バックエンドがHTMLの静的バージョンをそのページのnoscript-tagの一部として提供します。これにより、アプリが元々単一ページのアプリであったとしても、Googleや他の検索エンジンがウェブサイトのすべてのページをクロールできるようになります。
完全な詳細を含むスクリーンキャストへのリンク:
http://sparender.com/ を使用すると、シングルページアプリケーションを正しくクロールできます。
Prerenderと呼ばれるサービスを使用して、SPAをprerenderするための独自のサービスを使用または作成できます。彼のWebサイト prerender.io と彼の githubプロジェクト (PhantomJSを使用し、あなたのWebサイトをレンダリングします)で確認できます。
始めるのはとても簡単です。クローラーのリクエストをサービスにリダイレクトするだけで、レンダリングされたhtmlを受け取ります。