htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

JavaScriptだけでUser-Agentを偽装してWebページを取得する

初めに、IE9以上でしか動きません。
しかもバグ持ちで、ページによっては画面が真っ白になることがあります。
同じスクリプトを2回実行すると直る可能性あり。

ちなみに Google News でテストしました。

ということで、スクリプトです。

(function(){
  if (document.createRange) { // Rangeオブジェクト多用してるのでこれが使えないとお話にならない
    var range = document.createRange();
    range.selectNodeContents(document.documentElement);
    var xhr = new window.ActiveXObject("MSXML2.XMLHTTP"); // XHRオブジェクト生成
    xhr.open("GET", location.href, true);
    
    // ここでUser-Agentを指定します。
    xhr.setRequestHeader("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9A405 Safari/7534.48.3");
    
    // ここでキャッシュを無効にして 304 が返らないようにします。
    xhr.setRequestHeader("Pragma", "no-cache");
    xhr.setRequestHeader("Cache-Control", "no-cache");
    xhr.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
    
    // 非同期通信なので必須
    xhr.onreadystatechange = function(){
      if (xhr.readyState===4 && xhr.status===200) {
        var root = document.documentElement,
        // 現在の head, body をコピーしておきます。
        oldHead = document.getElementsByTagName("head")[0].cloneNode(false),
        oldBody = document.body.cloneNode(false),
        // 新しいリクエストで取得する head, body の中身をそれぞれ格納します。
        newElem = [null, null],
        // DocumentFragment の中に入ると head や body が消えてしまうので、予め分割しておきます。
        texts = xhr.responseText.split(/<body[^>]*>/);
        
        // IE10からは Range#createContextualFragment が使えます。
        // if の分岐後、やってることは同じです。
        if (range.createContextualFragment) {
          newElem[0] = range.createContextualFragment(texts[0]);
          newElem[1] = range.createContextualFragment(texts[1]);
        }
        else {
          var div = document.createElement("div");
          for (var i in newElem) {
            div.innerHTML = texts[i];
            range.selectNodeContents(div);
            newElem[i] = range.extractContents();
          }
        }
        
        // head の中身を入れ替えます。
        // 一旦 head ごと削除し、コピーしておいた空の head を追加し、さらにその中に新しい中身を追加しています。
        range.selectNode(document.getElementsByTagName("head")[0]);
        range.deleteContents();
        root.appendChild(oldHead);
        document.getElementsByTagName("head")[0].appendChild(newElem[0]);
        
        // body の中身を入れ替えます。
        // 同上
        range.selectNode(document.body);
        range.deleteContents();
        root.appendChild(oldBody);
        document.body.appendChild(newElem[1]);
      }
    };
    xhr.send();
  }
})();

ここではiPhoneiOS 5.0.1に偽装していますが、もちろん他のUAでも可能です。

キモは、インスタンスの生成に MSXML2.XMLHTTP を使っていること。
これを使うと XMLHttpRequest では不可能なUser-Agent書き換えができるようです。