twitterで特定ユーザの画像を全部ダウンロードする
twitterユーザの画像を全部ダウンロードするスクリプトを書きました。
特定のユーザのtwitterの画像を全部ダウンロードしたかったので、
プログラムを書きました。
APIキー取得
twitterのAPIを利用するには、APIキーの取得が必要です。
Twitter developersからログインして、「Create an application」から
アプリケーション登録すると取得できます。
Twitter Developers
twitter REST API
twitterの情報を取得するにはREST APIを使用します。
かなりたくさんのAPIが揃っていて、ほとんどなんでも操作できます。
REST APIs | Twitter Developers
ユーザの写真を取得するには「GET statuses/user_timeline」を使用します。
GET statuses/user_timeline | Twitter Developers
このAPIでは取得したいユーザ名を指定することで、一度に最大200のツイートをjson形式で取得できます。
200以上取得したい場合は、複数回GETする必要があります。
そして対象ユーザの過去3,200ツイートまで取得できます。
また、各APIには叩ける回数が決まっています。
下記に各APIの15分で叩ける回数が記載されています。
Twitter API 1.1で「Application-only authentication」が追加されました。
これはアプリケーションのみがAPIを利用する場合に利用できます。
Rate Limits: Chart | Twitter Developers
これをみると「GET statuses/user_timeline」は15分で300回叩けます。
1度のリクエストで200ツイート取得できるので、3,200 ÷ 200 = 16回取得すれば全部取得できます。
15分で300回たたけるので16回は全然余裕です。
max_id
ツイートには64bitの一意なidが設定されているので、
max_idパラメータにidを指定することで、そのidより前のツイートを取得できます。
注意点はmax_idを含むツイートが取得されるので、含ませたくない場合、
下図のように(max_id - 1)を指定する必要があります。
画像URL取得
取得したjsonの中から画像URLを取得します。
1枚の画像の場合、entities.mediaに画像情報が入っているのですが、
複数枚画像がある場合はextended_entitiesに画像情報が配列で入っています。
jsonレスポンス
extended_entities: { media: [ { id: 704325644088311800, id_str: '704325644088311808', indices: [ 139, 140, [length]: 2 ], media_url: 'http://pbs.twimg.com/media/CcZEcQ4VAAAj5pa.jpg', media_url_https: 'https://pbs.twimg.com/media/CcZEcQ4VAAAj5pa.jpg', url: 'https://t.co/FJ0ptKC1S1', display_url: 'pic.twitter.com/FJ0ptKC1S1', expanded_url: 'http://twitter.com/RLE000/status/704325645526904832/photo/1', type: 'photo', sizes: { large: { w: 700, h: 700, resize: 'fit' }, medium: { w: 600, h: 600, resize: 'fit' }, thumb: { w: 150, h: 150, resize: 'crop' }, small: { w: 340, h: 340, resize: 'fit' } }, source_status_id: 704325645526904800, source_status_id_str: '704325645526904832', source_user_id: 265123955, source_user_id_str: '265123955' }, { id: 704325637356408800, id_str: '704325637356408834', indices: [ 139, 140, [length]: 2 ], media_url: 'http://pbs.twimg.com/media/CcZEb3zUUAIQc5R.jpg', media_url_https: 'https://pbs.twimg.com/media/CcZEb3zUUAIQc5R.jpg', url: 'https://t.co/FJ0ptKC1S1', display_url: 'pic.twitter.com/FJ0ptKC1S1', expanded_url: 'http://twitter.com/RLE000/status/704325645526904832/photo/1', type: 'photo', sizes: { small: { w: 340, h: 299, resize: 'fit' }, medium: { w: 600, h: 528, resize: 'fit' }, thumb: { w: 150, h: 150, resize: 'crop' }, large: { w: 700, h: 616, resize: 'fit' } }, source_status_id: 704325645526904800, source_status_id_str: '704325645526904832', source_user_id: 265123955, source_user_id_str: '265123955' }, [length]: 2 ] },
URLが下記の様に色々ありますが、media_urlを使用します。
- media_url:画像URL
- media_url_https:画像URL(SSL)
- url:画像を含むtweet⇒expanded_urlにリダイレクトされる
- display_url:http://が含まれない短縮URLの形式⇒expanded_urlにリダイレクトされる
- expanded_url:media_urlを含むツイートの完全なURL
画像はthumb, small, medium, largeという4つのサイズがあり、デフォルトはmediumになってます。
画像URLにコロンをつけてサイズを指定することができます。
せっかくなので一番大きいlargeを指定します。
http://pbs.twimg.com/media/CcZEcQ4VAAAj5pa.jpg:large
twitモジュール
twitter REST APIを普通に呼んでもいいのですが、簡単に操作ができる
twitというライブラリを使用しました。
github.com
twitではREST APIとStreaming APIを使用できます。
npmでインストールします。
# npm install twit
使い方は簡単で、twitter developersで取得した各種キーを設定するだけです。
var Twit = require('twit') var T = new Twit({ consumer_key: '...', consumer_secret: '...', access_token: '...', access_token_secret: '...' })
getもとても簡単でREST URLを指定して、送信したいパラメータをつけてやるだけです。
- screen_name : @で始まるtwitterのユーザ名
- count : 一度で取得するツイート数
- max_id : 指定したtweet IDより昔のツイートを取得
- exclude_replies : 返信を含めるか
- trim_user : ユーザ情報を省略するか
- include_rts : リツイートを含めるか
var params = { screen_name : '@flomorrissey', count : 200, max_id : maxId, exclude_replies : false, trim_user : true, include_rts : true }; T.get('statuses/user_timeline', params, function(err, data, response) { : })
javascriptで64bit整数演算
twitterではすべてのツイートに64bitの一意なidが設定されているのですが、
javascriptでは以上の数値は精度が保障されず、演算に誤差が発生します。
twitter REST APIではこの問題に対応して、idをstringで表現したid_strという項目があります。
なのでidではなくid_strを取得して使用します。
- id:tweetのユニークなID
- id_str:idのstringフォーマット。idを''でくくったもの
ただ困ったのが、max_idを指定するときに(id - 1)の演算をする必要があります。
stringでidを受け取っても演算ができません。
なので演算できるライブラリを使用します。
npmで探すと下記のように色々あるみたいです。
一番ユーザ数が多そうなlong.jsを使ってみることにします。
https://www.npmjs.com/package/long
https://www.npmjs.com/package/bigint
https://www.npmjs.com/package/bignum
https://www.npmjs.com/package/int64-native
long.jsライブラリ
long.jsは64bitの数値を扱えるライブラリです。
www.npmjs.com
npmでインストールします。
# npm install long
long@3.0.3 node_modules/long
longオブジェクトを生成する方法は色々ありますが、今回はstringから生成するので
formString()を使います。
var Long = require("long"); var longId = Long.fromString(str_id);
演算用のメソッドも色々用意されてますが、今回は1減算するので
subtract()を使用します。
var longIdSub = longId.subtract(1);
結果をstringで取得します。
var longStr = longIdSub.toString();
画像ダウンロード
取得した画像URLを使用して画像をダウンロードします。
ダウンロードにはrequestモジュールを使用します。
requestモジュール
requestモジュールは多機能なHTTPクライアントです。
www.npmjs.com
npmでインストールします。
# npm install request
request@2.69.0 node_modules/request
requestモジュールでは簡単にストリーミングができます。
画像URLを使用して画像をダウンロードするのも、pipe()で簡単に書けます。
request('http://xxxxxxxx/image.png').pipe(fs.createWriteStream('image.png'))
twitter画像ダウンロード
最終的には下記の様になりました。
dlTwitterUserImages.js
var twit = require('twit'); var request = require('request'); var fs = require('fs'); var Long = require("long"); var URL = require('url'); var T = new twit({ consumer_key: 'YOUR CONSUMER KEY', consumer_secret: 'YOUR CONSUMER SECRET', access_token: 'YOUR ACCESS TOKEN', access_token_secret: 'YOUR ACCESS SECRET', app_only_auth: true }); var MAX_LOOP = 16; var maxId; var params = { screen_name : '@SCREEN_NAME', count : 200, max_id : maxId, exclude_replies : false, trim_user : true, include_rts : true }; var urls = []; var cnt = 1; getImage(0, download); function getImage(loop, callback) { if (loop >= MAX_LOOP) { callback(urls); return; } T.get('statuses/user_timeline', params, function(err, data, response) { if (data.length === 0) { callback(urls); return; } var minId; for(var i = 0; i < data.length; i++) { var d = data[i]; if (d.extended_entities) { for(var j = 0; j < d.extended_entities.media.length; j++) { console.log(cnt + " : " + d.extended_entities.media[j].media_url); urls.push(d.extended_entities.media[j].media_url); } } else { console.log(cnt + " : no image"); } minId = d.id_str; cnt++; } var longId = Long.fromString(minId); var longIdSub = longId.subtract(1); params.max_id = longIdSub.toString(); getImage(loop + 1, callback); }); } function download(urls) { for (var i = 0; i < urls.length; i++) { var url = urls[i]; var pathname = URL.parse(url).pathname; var filename = pathname.replace(/[^a-zA-Z0-9\.]+/g, '_'); request .get(url + ":large") .on('end', done(url)) .on('error', function(err) { console.log(err); }) .pipe(fs.createWriteStream(filename)); } } function done(url) { return function() { console.log("downloaded : " + url); }; }
実行・確認
Flo Morrisseyという人の画像を取得してみます。
Flo Morrissey (@flomorrissey) | Twitter
実行。
全ツイートの画像URLを取得します。画像がないツイートの場合、no imageと表示されます。
そのあと、全画像をダウンロードします。
# node dlTwitterUserImages.js 1 : no image 2 : no image 3 : no image 4 : no image 5 : no image 6 : no image 7 : no image 8 : no image 9 : no image 10 : no image 11 : http://pbs.twimg.com/tweet_video_thumb/CdC-TGRW0AAl99w.jpg 12 : http://pbs.twimg.com/media/CdCUYngWEAAVq0N.jpg 13 : http://pbs.twimg.com/media/Cc9V8VBXEAAfrPv.jpg 14 : no image 15 : http://pbs.twimg.com/media/Cc1owfBVIAEnIvs.jpg 16 : http://pbs.twimg.com/ext_tw_video_thumb/705888574957338624/pu/img/fw7LE_Rv_q5Wy1Tm.jpg 17 : no image 18 : no image 19 : no image : : downloaded : http://pbs.twimg.com/media/CW3PvBHWcAEMwmE.jpg downloaded : http://pbs.twimg.com/media/CTm2g4CXAAAjeza.jpg downloaded : http://pbs.twimg.com/media/CUMyUx9XAAACadi.jpg downloaded : http://pbs.twimg.com/media/CTDOTahWUAEmfGN.jpg downloaded : http://pbs.twimg.com/media/CRNG29eXAAAI0fv.jpg downloaded : http://pbs.twimg.com/media/COS4tlcUcAA91IL.jpg downloaded : http://pbs.twimg.com/media/CPURHA8VAAAkUUj.jpg downloaded : http://pbs.twimg.com/media/COYaJkfU8AEOpf7.jpg downloaded : http://pbs.twimg.com/media/CPMT4QyW8AAVNkN.jpg downloaded : http://pbs.twimg.com/media/CK_kYE2WcAAPFIJ.jpg : :
確認
# ll flomorrissey/ 合計 36772 -rw-r--r-- 1 root root 14866 3月 10 11:27 _ext_tw_video_thumb_598296176211824640_pu_img_HnGfu7NvC_F7_VT.jpg -rw-r--r-- 1 root root 135178 3月 10 11:27 _media_B_1Sc2_UYAA71FQ.jpg -rw-r--r-- 1 root root 133596 3月 10 11:27 _media_CAUXkfGWYAAxbeG.jpg -rw-r--r-- 1 root root 156079 3月 10 11:27 _media_CAeVOPnUsAAAKwr.jpg -rw-r--r-- 1 root root 122161 3月 10 11:27 _media_CAzCkZ0UQAAUXos.jpg -rw-r--r-- 1 root root 68867 3月 10 11:27 _media_CB4Rl9CUsAEOM2z.jpg -rw-r--r-- 1 root root 76887 3月 10 11:27 _media_CB4fZmHUMAAKnsS.jpg -rw-r--r-- 1 root root 58688 3月 10 11:27 _media_CB8GT16WIAA3enR.jpg -rw-r--r-- 1 root root 65630 3月 10 11:27 _media_CBBO_buWsAAI4PB.jpg -rw-r--r-- 1 root root 37661 3月 10 11:27 _media_CBBOdj0WYAADFmf.jpg -rw-r--r-- 1 root root 37661 3月 10 11:27 _media_CBCj6t6XEAAWsPb.jpg -rw-r--r-- 1 root root 19385 3月 10 11:27 _media_CBJJeBFUwAERzTV.jpg -rw-r--r-- 1 root root 61221 3月 10 11:27 _media_CBJUCsqXIAE0xT8.jpg -rw-r--r-- 1 root root 83285 3月 10 11:27 _media_CBpVndlUEAA3UMp.jpg -rw-r--r-- 1 root root 93858 3月 10 11:27 _media_CBpVnduUgAIwZHk.jpg -rw-r--r-- 1 root root 21046 3月 10 11:27 _media_CC3yHq_WAAAmpD1.jpg -rw-r--r-- 1 root root 75453 3月 10 11:27 _media_CCXImJQWEAAK6IR.jpg -rw-r--r-- 1 root root 60723 3月 10 11:27 _media_CCqDoT2XIAEHxTz.jpg -rw-r--r-- 1 root root 48671 3月 10 11:27 _media_CCqK4fHWEAA04Au.jpg -rw-r--r-- 1 root root 91041 3月 10 11:27 _media_CCuDqlwXIAE0jay.jpg -rw-r--r-- 1 root root 169754 3月 10 11:27 _media_CCunxGfUUAARI9A.jpg -rw-r--r-- 1 root root 88775 3月 10 11:27 _media_CCzmVVgWIAI0X_h.jpg : : :
うまく取れているようです。
画像をWindowsで見てみると、ちゃんと取れてます(^-^)
終わり。
ソースは一応置いておきました。
github.com