Ajax Web ぺージを検索ボットがクロールできるコンテンツを返すようにする

最近は様々な 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を返しています。 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 Analyticsの無視 このままだとスナップショット生成時にGoogle Anlyticsへのアクセスが発生してしまいます。 こちらを無視するように JavaScritpのファイルロード時の処理を前もって処理する ScriptPreProcessor クラスのサブクラスを作っておきます。 ...

2012年3月17日 · Toshimitsu Takahashi

Java で整数値を基数 62 の文字列に変換するには

Integer.toString だと基数 36 (0-9a-z) までしか対応していなかったので自前で作った。 自然数を 62進数表記(0-9a-zA-Z)に変換する。 public static String toBase62String(final long value) { long val = value; StringBuilder sb = new StringBuilder(7); while (val > 0) { int mod = (int)(val % 62); if (mod < 10) { // 数字 sb.append(mod); } else if (mod < 36) { // 英小文字 a = 97 // mod = mod - 10 + 97 mod += 87; sb.append((char)mod); } else { // 英大文字 A = 65 // mod = mod - 36 + 65 mod += 29; sb.append((char)mod); } val = val / 62; } return new String(sb.reverse()); }

2010年6月22日 · Toshimitsu Takahashi

XMLGregorianCalendar のデータサンプル値

メモ。 データ型 “date” 2009-05-25+09:00 データ型 “dateTime” 2009-05-25T10:15:28.261+09:00

2009年5月25日 · Toshimitsu Takahashi

S2JDBC で INSERT 後に Oracle のシーケンス値 CURRVAL を取得するには

メモ。 エンティティ @Entity @Generated(value = {“S2JDBC-Gen 2.4.35”, “org.seasar.extension.jdbc.gen.internal.model.EntityModelFactoryImpl”}, date = “2009/05/11 10:51:08”) public class ExampleEntity extends AbstractEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator=“EXAMPLE_ID_SEQUENCE_GEN”) @SequenceGenerator(name=“EXAMPLE_ID_SEQUENCE_GEN”, sequenceName=“EXAMPLE_ID_SEQUENCE”, allocationSize = 1, initialValue = 1) @Column(length = 3, nullable = false, unique = true) public long id; … 処理 newId = … のところで直前の新IDの値を取得している。 public class ExampleService extends AbstractService { public void insertEntity(ExampleEntity exampleEntity) { jdbcManager.insert(exampleEntity).execute(); long newId = jdbcManager.selectBySql(Long.class, “select EXAMPLE_ID_SEQUENCE.currval from dual”).getSingleResult(); … ...

2009年5月13日 · Toshimitsu Takahashi

Eclipse 3.4 Ganymede リリース

Macbook Air に Eclipse 入れようと http://www.eclipse.org/ に行ってみたら、新バージョンがリリースされていた。 全然知らなかったと思って調べたところ、 http://journal.mycom.co.jp/news/2008/06/27/002/index.html とタイムリーな話題だったらしい。 ちなみにコードネーム Ganymede (「ガニメデ」と読む)は、木星の衛星に由来するみたいです。

2008年6月27日 · Toshimitsu Takahashi

Google API translate java を試す

Google 翻訳サービスを Java から簡単に使える Java ライブラリが Google Code にある。 http://code.google.com/p/google-api-translate-java/ 非常に簡単。翻訳対象文字列と元言語、変換言語を指定するだけ。 import com.google.api.translate.Language; import com.google.api.translate.Translate; String ctn = Translate.translate(textEnglish, Language.ENGLISH, Language.JAPANESE); ctn = ctn.replace(" ", “\r\n”); 内部で Web サービスを呼んでいるためか改行は、 となって返ってくる。 ちなみに プロキシサーバが必要なときは下記のようにする。 class HttpAuthenticator extends Authenticator { private String user; private char[] pass; public HttpAuthenticator(String username, String password) { user = username; pass = password.toCharArray(); } protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(user, pass); } } System.setProperty(“http.proxyHost”, “proxy.host.com”); System.setProperty(“http.proxyPort”, “8080”); Authenticator.setDefault(new HttpAuthenticator(“username”, “password”));

2007年12月14日 · Toshimitsu Takahashi

Jakarta Commons HttpClient

仕事で Java Web アプリケーションサーバから Web サービスを叩いてみるということで、HTTP クライアントをどうしようかと考えた。 プロキシ認証とかリトライとかタイムアウトとか色々ありそうなので Jakarta Commons HttpClient を試してみた。せっかくなので、Amazon Web Service を触ってみる。 http://jakarta.apache.org/commons/httpclient/ package sample; import java.io.IOException; import java.io.InputStreamReader; import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.params.HttpMethodParams; /** * @author tosshi / public class Main { /* * @param args */ public static void main(String[] args) { HttpClient client = new HttpClient(); // ソケットタイムアウトは 1秒 client.getParams().setParameter(“http.socket.timeout”, new Integer(1000)); /* * プロキシの認証が必要なとき * * Credentials cred = new UsernamePasswordCredentials(“PROXYAUTHID”, “PROXYAUTHPASS”); * HttpState state = new HttpState(); * state.setProxyCredentials(AuthScope.ANY, cred); * client.setState(state); */ // URIを指定して GET を生成 GetMethod method = new GetMethod(“http://webservices.amazon.co.jp/onca/xml"); // リトライは3回 method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false)); // クエリをセット NameValuePair[] pairs = { new NameValuePair(“Service”, “AWSECommerceService”), new NameValuePair(“AWSAccessKeyId”, ☆取得したAWSのアクセスキーID☆), new NameValuePair(“Operation”, “ItemSearch”), new NameValuePair(“SearchIndex”, “Music”), new NameValuePair(“ResponseGroup”, “Small”), new NameValuePair(“Artist”, “BENNIE K”), }; method.setQueryString(pairs); try { // 実行 int statusCode = client.executeMethod(method); if (statusCode != HttpStatus.SC_OK) { System.err.println(“Method failed: " + method.getStatusLine()); } // レスポンスをアウト InputStreamReader isr = new InputStreamReader( method.getResponseBodyAsStream(), method.getResponseCharSet()); final int BUFFER_SIZE = 1024; char[] buffer = new char[BUFFER_SIZE]; int readSize; while ((readSize = isr.read(buffer, 0, BUFFER_SIZE)) > 0) { System.out.print(new String(buffer, 0, readSize)); } isr.close(); } catch (HttpException e) { System.err.println(“Fatal protocol violation: " + e.getMessage()); e.printStackTrace(); } catch (IOException e) { System.err.println(“Fatal transport error: " + e.getMessage()); e.printStackTrace(); } finally { // 接続を解放 method.releaseConnection(); } } }

2007年5月23日 · Toshimitsu Takahashi

Maven2 Eclipseのプロジェクト生成

> cd workspace > mvn archetype:create -DgroupId=group -DartifactId=newproject > cd newproject > mvn eclipse:eclipse

2007年5月21日 · Toshimitsu Takahashi

log4jの再設定

log4j.xml の設定を動的に切り替えるため、リロードメソッドを実装してみる。 1 2 3 4 5 6 7 8 import org.apache.log4j.xml.DOMConfigurator; public class LogManager { … public static reload() { URL url = getLogConfigURL() DOMConfigurator.configure(url); } }

2007年5月8日 · Toshimitsu Takahashi

Java で文字列をMD5ダイジェストの16進数形式文字列に変換するには

1 2 3 4 5 6 7 8 9 10 11 12 13 14 public String createDigest(String source) { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] data = source.getBytes(); md.update(data); byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < digest.length; i++) { int b = (0xFF & digest\[i\]); if (b < 16) sb.append("0"); sb.append(Integer.toHexString(b)); } return sb.toString(); } ※ご指摘のゼロ補完処理を追加(2012/2/25)

2007年2月13日 · Toshimitsu Takahashi