[javascript] インスタグラムのログインをAjaxで突破する方法

2018年7月31日

Javascript テクノロジー プログラミング

最近、開発以来の多いスクレイピング案件でインスタグラムクローリングをやっていた時に、Storiesのスクレイピングをやっている人が世の中にほとんどいないという事実に気がつきました。 インスタのbotシステムはpythonライブラリがgithubで公開されているので、簡単にbotを作りたい人は、下記を参考にしていただくといいでしょう。 https://github.com/instagrambot/instabot しかし、他のシステムとの連動や、自分プロイグラムとの連携を行いたい場合には、少しばかり扱いづらいライブラリのようなので、ちょこっと参考にさせていただき、独自にjavascriptで構築してみました。

インスタのAPIは使えない

そもそもインスタグラムのデータを取得する時に、すでにあるAPIを使えばいいじゃないかと考えがちですが、2018年4月あたりから機能別に順次廃止されていっているという情報が出回っており、ベンダーや利用者には、インスタから直接メールが送信されているとの事。 こうした処理にAPIを使うのは、確実にデータ取得ができるメリットがあるのですが、
Facebookも含め、APIはとにかく使いにくい わかりにくい 不具合の保証をしてくれない
という3つの原因があるので、スクレイピングした方が確実という意見もエンジニア内では意外と少なく無い意見のようです。 とはいえ、インスタはAPIが廃止されるので、スクレイピングできなければ色々と不便極まりないので、今回思い立ってみました。

インスタ機能のスクレイピング仕様

インスタグラムでスクレイピングしたい仕様としては、下記のような内容です。
フォロワー数 フォロー数 投稿数 投稿内容 ストーリーズ
これらを最低でも1日に1回定時に取得できると、マーケティングデータとしての利用価値があるという事なのですが、 インスタグラムは、上記の内容は「ストーリーズ」以外はログイン(認証)をしなくても対象のアカウント名さえわかれば取得できてしまいます。 問題はこの「ストーリーズ」データのスクレイピングという事なんですね。 僕も最初は「nodejsなどを使って、ログインページでIDとパスワードをvalueセットして、submitすればいいや」程度に思っていたら、どんでも無い安易な思い込みで、さすがにこれだけでかいサービスであれば、こうしたbotログイン対策もできているようでした。 まず、ログインページでIDとパスワードのinputタグに対して、value値の書き換えを行なっても、サイト側が「クッキーが正常に動作していません」というエラーページが表示されるだけで、うんともすんとも行かないわけです。 そして、前期したpythonライブラリのログイン箇所を見てみて、submitのリクエストヘッダを工夫すればうまくログインができてセッションキープ状態を取得できることが判明したので、今回はその作ったソースコードを載せておきます。

ソースコード

;(function(){ var config = { username : "*username", password : "*password" }; var $$ = function(){ // ajax-auth $$AJAX({ url : "https://www.instagram.com/accounts/login/ajax/", query : { username : config.username, password : config.password }, headers : { 'Accept' : '*/*', 'Accept-Language' : 'en-US,en;q=0.5', 'Accept-Encoding' : 'gzip, deflate, br', 'Connection' : 'keep-alive', 'Content-Length' : '0', 'Host' : 'www.instagram.com', 'Origin' : 'https://www.instagram.com', 'Referer' : 'https://www.instagram.com/', 'User-Agent' : navigator.userAgent, 'X-Instagram-AJAX': '1', 'Content-Type' : 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken' : $$COOKIE.val("csrftoken") }, onSuccess : function(res){ console.log("response : " + res); } }); }; var $$AJAX = (function(){ var $$ajax = function(options){ if(!options){return} var ajax = new $$ajax; var httpoj = $$ajax.prototype.createHttpRequest(); if(!httpoj){return;} // open メソッド; var option = ajax.setOption(options); // 実行 httpoj.open( option.method , option.url , option.async ); // type if(option.headers){ httpoj = $$ajax.prototype.setHeaders(httpoj , option.headers); } else{ httpoj.setRequestHeader('Content-Type', option.type); } // onload-check httpoj.onreadystatechange = function(){ //readyState値は4で受信完了; if (this.readyState==4){ //コールバック option.onSuccess(this.responseText); } }; // responseType if(typeof option.responseType !== "undefined" && option.responseType){ httpoj.responseType = option.responseType; } //query整形 var data = ajax.setQuery(option); //send メソッド if(data.length){ httpoj.send(data.join("&")); } else{ httpoj.send(); } }; $$ajax.prototype.setHeaders = function(httpoj , headers){ for(var key in headers){ httpoj.setRequestHeader(key , headers[key]); } return httpoj; }; $$ajax.prototype.dataOption = { url:"", query:{}, // same-key Nothing querys:[], // same-key OK data:{}, // ETC-data event受渡用 async:"true", // [trye:非同期 false:同期] method:"POST", // [POST / GET] responseType:"", headers : {}, type:"application/x-www-form-urlencoded", // [text/javascript]... onSuccess:function(res){}, onError:function(res){} }; $$ajax.prototype.option = {}; $$ajax.prototype.createHttpRequest = function(){ //Win ie用 if(window.ActiveXObject){ //MSXML2以降用; try{return new ActiveXObject("Msxml2.XMLHTTP")} catch(e){ //旧MSXML用; try{return new ActiveXObject("Microsoft.XMLHTTP")} catch(e2){return null} } } //Win ie以外のXMLHttpRequestオブジェクト実装ブラウザ用; else if(window.XMLHttpRequest){return new XMLHttpRequest()} else{return null} }; $$ajax.prototype.setOption = function(options){ var option = {}; for(var i in this.dataOption){ if(typeof options[i] != "undefined"){ option[i] = options[i]; } else{ option[i] = this.dataOption[i]; } } return option; }; $$ajax.prototype.setQuery = function(option){ var data = []; if(typeof option.query != "undefined"){ for(var i in option.query){ data.push(i+"="+encodeURIComponent(option.query[i])); } } if(typeof option.querys != "undefined"){ for(var i=0;i<option.querys.length;i++){ if(typeof option.querys[i] == "Array"){ data.push(option.querys[i][0]+"="+encodeURIComponent(option.querys[i][1])); } else{ var sp = option.querys[i].split("="); data.push(sp[0]+"="+encodeURIComponent(sp[1])); } } } return data; }; return $$ajax; })(); var $$COOKIE = (function(){ var $$ = { //init-data options:{ name : 'temporary_cookie', day : 0, hour : 0, min : 1, sec : 0 }, //expires date : function() { var exp = new Date(); exp.setTime(exp.getTime() + (this.options.day * 1000 * 60 * 60 * 24) + (this.options.hour * 1000 * 60 * 60) + (this.options.min * 1000 * 60) + (this.options.sec * 1000)); return exp.toGMTString(); }, //secure-check checkSecure : function() { if (location.href.match(/^https/)) {return true} else {return false} }, set : function(name, val) { if(!name){name = this.options.name} val = this.encode(val); if (this.checkSecure()) { document.cookie = name + "=" + val + ";expires=" + this.date() + ";secure"; } else { document.cookie = name + "=" + val + ";expires=" + this.date(); } }, get : function(name){ return this.val(name); }, val : function(name) { var ck0 = document.cookie.split(" ").join(""); var ck1 = ck0.split(";"); for ( var i = 0; i < ck1.length; i++) { var ck2 = ck1[i].split("="); if (ck2[0] == name) { ck2[1] = this.encode(ck2[1]); return ck2[1]; } } return ''; }, encode:function(val){ if (!val) {return ""} val = val.split("¥r") .join(""); val = val.split("¥n") .join(""); val = val.split("<") .join("-"); val = val.split("%3c").join("-"); val = val.split("%3C").join("-"); val = val.split(">") .join("-"); val = val.split("%3e").join("-"); val = val.split("%3E").join("-"); return val; } }; return $$; })(); return new $$; })(); 上記ソースコードの上部にある"*username"と"*password"の箇所を自分のインスタアカウントの物をセットすれば用意完了です。

使い方

基本的にGoogleChromeブラウザを使って操作してください。 まず、ブラウザでインスタグラムのログインページを開きます。 https://www.instagram.com/accounts/login/ ブラウザのjavascriptコンソールを開いて、上記ソースコードを貼り付けてください。(IDとパスワードは書き換えてください) すると、ログイン後のページが開くと思いますが、それで成功です。 このコードを応用した利用例も書いておくので、便利に使ってみてください。

応用例

1. seleniumでjavascriptを使って自動ログイン おそらくログインページの項目にIDとパスワードを入力する手順で失敗すると思うので、思い切って、このJSを流し込むだけでうまくいきます。 2. ブラウザ機能拡張の対応  機能拡張を使って自動ログインをさせたりするロボティクス操作(自動操作)が可能になります。 3. Nodejsを使ったbotコントロール ログインできたセッション状態をキープできていれば、Nodejsなどでページ移動しながら、そのページの情報をスクレイピングする事ができるので、自動フォローや、自動投稿なども可能になります。 botが作れちゃうわけですね。

ちょこっと解説

今回のポイントは、ajaxを使ってヘッダ情報を書き換えて送信するという点です。 ヘッダ情報は、 headers : { 'Accept' : '*/*', 'Accept-Language' : 'en-US,en;q=0.5', 'Accept-Encoding' : 'gzip, deflate, br', 'Connection' : 'keep-alive', 'Content-Length' : '0', 'Host' : 'www.instagram.com', 'Origin' : 'https://www.instagram.com', 'Referer' : 'https://www.instagram.com/', 'User-Agent' : navigator.userAgent, 'X-Instagram-AJAX': '1', 'Content-Type' : 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken' : $$COOKIE.val("csrftoken") }, この箇所なのですが、"X-CSRFToken"でサイトアクセス時に書き込まれたcookieをつけてあげるのがポイントですね。 ajaxのヘッダ書き換えを覚えたら、他のサイトでも便利に使えるので、意外といい技術を覚えちゃいましたね。 そして、ここまで記事を読んでくれた人に一つ忠告! インスタグラムは、サイト規約により、「スクレイピングは禁止!」とのことです。 くれぐれも悪用しないようにしてくださいね。 このソースコードによって生じる問題などは、本サイトならびに僕の方では責任は追えませんので、ご自身の責任という事でご使用ください。 ちなみに、このソースコードMITライセンスにしてますんで。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ