最近は様々な Web サイトで Ajax が当たり前のように使われています。
ページ内のコンテンツをほぼ全てAjaxで別途ロードするようなサイトの場合、検索エンジンのロボットにはすっからかんのページがクロールされてしまいます。
そのために、サーバ側で予めブラウザで画面をロードした後のHTMLを生成して返すのが良いみたいです。
Googleのサイトにも AJAX クロール: ウェブマスターおよびデベロッパー向けガイド - ウェブマスター ツール ヘルプ のようなページがあり、HTML スナップショットをクローラーに提供するように提案されています。
また下記の英語サイトでは具体的な HTML snapshot の実装方法例が出ていて、その中で Java の HtmlUnit というものが紹介されています。
How do I create an HTML snapshot? - Webmasters — Google Developers
これを使って実際に今、Nginx とアプリケーションサーバで動かしている Web サイトに、
クローラーのアクセス時だけ Nginx → HtmlUnit の Java Web アプリサーバ → アプリケーションサーバと経由して HTML スナップショットを返すようにしてみました。
HtmlUnit のサイト: http://htmlunit.sourceforge.net/
サーブレットの実装
HtmlStaticServlet クラスは、GET で受け取ったパスをそのまま本来のアプリケーションサーバにリクエストして返すものです。
getAjaxPage メソッド内で FireFox 3.6 で JavaScript オン、CSS オフでアクセスしてロード後2秒間処理させて結果のXHTMLを返しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.gargoylesoftware.htmlunit.BrowserVersion; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.HtmlPage; public class HtmlStaticServlet extends HttpServlet { private static final String ROOT_URI = "http://localhost:8081"; private static final long serialVersionUID = 172403081095751054L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String url = ROOT_URI + request.getRequestURI(); System.out.println(url); String htmlContent = getAjaxPage(url); response.setContentType("text/html; charset=UTF-8"); PrintWriter writer = response.getWriter(); if (htmlContent != null) { response.setStatus(HttpServletResponse.SC_OK); writer.print(htmlContent); } else { response.setStatus(HttpServletResponse.SC\_NO\_CONTENT); } } private String getAjaxPage(String url) { WebClient webClient = new WebClient(BrowserVersion.FIREFOX\_3\_6); webClient.setJavaScriptEnabled(true); webClient.setCssEnabled(false); webClient.setScriptPreProcessor(new IgonoredGAScriptPreProcessor()); try { HtmlPage page = webClient.getPage(url); webClient.waitForBackgroundJavaScriptStartingBefore(2000); return page.getDocumentElement().asXml(); } catch (Exception e) { return null; } finally { webClient.closeAllWindows(); } } }
|
このままだとスナップショット生成時にGoogle Anlyticsへのアクセスが発生してしまいます。
こちらを無視するように JavaScritpのファイルロード時の処理を前もって処理する ScriptPreProcessor クラスのサブクラスを作っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import com.gargoylesoftware.htmlunit.ScriptPreProcessor; import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlPage; public class IgonoredGAScriptPreProcessor implements ScriptPreProcessor { @Override public String preProcess(HtmlPage htmlPage, String sourceCode, String sourceName, int lineNumber, HtmlElement htmlElement) { if (sourceName.startsWith("script in")) { if (sourceCode.indexOf(".google-analytics.com/ga.js") \> -1) return ""; } return sourceCode; } }
|
サーブレットの実行
ここでは htmlsnapshot.war として上記のサーブレットをアーカイブしました。
さらに軽量 Java Web アプリケーションサーバの winstone を使って起動します。
Winstone のサイト: http://winstone.sourceforge.net/
java -jar winstone-lite-0.9.10.jar –warfile=htmlsnapshot.war –httpPort=8082
Nginx のサイト設定
最後に Nginx のサイト設定を変更します。
まず、それぞれのサービスポートをおさらいしておきます。
Nginx
80
アプリケーションサーバ
8081
Winstone
8082
Googlebot と bingbot に対して snapshot を返すように proxy_pass を切り替えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| upstream example_com { server 127.0.0.1:8081; } upstream example\_com\_bot { server 127.0.0.1:8082; } server { listen 80; server_name example.com; location / { if ($http\_user\_agent ~* (googlebot|bingbot)) { proxy\_pass http://example\_com_bot; break; } proxy\_pass http://example\_com; } }
|
tilfin
freelance software engineer