質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Twitter

Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

2回答

1980閲覧

Node.jsでツイートをできるだけ(200件以上)検索するには

elpha

総合スコア18

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

Twitter

Twitterは、140文字以内の「ツイート」と呼ばれる短文を投稿できるサービスです。Twitter上のほぼ全ての機能に対応するAPIが存在し、その関連サービスが多く公開されています。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2018/11/27 23:57

編集2018/11/28 04:05

JavaScript、Node.jsともに初心者になります。
(Windows7のコマンドプロンプトから実行しています)

やりたいこと

「特定の30分間につぶやかれた、特定のハッシュタグを含んだツイート」をすべて一気に検索
したいと思っています。
(手動で検索した限りではおよそ800~2000ツイートほどになるので、分割やスクロールなどの手間を省きたく…)

Stream APIは今年8月に廃止されたそうなので、REST APIを使うしかないようですが… こちらは一度に200件までしか検索できないようですね。

試したこと

Node.jsでTwitter検索から100件を超えるツイートを取得したい
https://qiita.com/ryo-a/items/53fe9eadcf719b817c9a

こちらを参考にしてしてみたところ、countの値をいくらにしても100件以内しか取得できませんでした。

Twitter API Timeline解説
http://nonbiri-tereka.hatenablog.com/entry/2014/03/06/220015

とりあえず検索するたびに最後のツイートのIDを格納し、次はそのIDをmax_idにして検索すればいいということはわかったのですが…

Node.js

1const Twit = require('twit'); 2 3const T = new Twit({ 4 各種キー 5 app_only_auth: true 6}); 7let lastId = ''; 8 9let params = { 10 q: '#ハッシュタグ since:2018-11-28_00:00:00_JST until:2018-11-28_00:30:00_JST', 11 count: 3, 12 max_id: lastId, 13 result_type: 'recent', 14 include_entities: false 15} 16 17for(let i=0; i<3; i++){ 18 console.log('検索開始'); 19 params.max_id = lastId; 20 T.get('search/tweets', params, (err, data, response) => { 21 data.statuses.forEach(function(val, index, ar){ 22 console.log(index); 23 console.log('@' + val.user.screen_name); 24 console.log(val.text); 25 lastId = val.id.str; 26 }); 27 }); 28 console.log('最後のIDは' + lastId); 29}

と3ツイート×3回の検索をしようとしてみると、コンソールはまず

検索開始 最後のIDは 検索開始 最後のIDは 検索開始 最後のIDは

と、まだ代入されていない状態のものが最初に3回ぶん表示されてしまい、そのあとで検索結果(3回とも同じ結果)が出てきます。

これはNode.jsゆえの、非同期だからこそ起こることでしょうか?
それともJavaScriptの何か初歩的な間違い(スコープなど)を犯しているのでしょうか?

ここさえ解決できれば、800ツイートでも2000ツイートでも(規制のかからないかぎり)検索できるとは思うのですが…
ご教授よろしくお願いいたします。

追記

やはり非同期通信が原因とのことでしたので、初めてながらasync/awaitというやつで書き換えてみましたが
何かが足りないのか、やはり検索結果は変わりません…

Node.js

1(略) 2async function main() { 3 console.log('ループ開始'); 4 for(let i=0; i<3; i++){ 5 await search(); 6 } 7} 8 9function search() { 10 return new Promise((resolve, reject) => { 11 console.log('検索開始'); 12 params.max_id = lastId; 13 T.get('search/tweets', params, (err, data, response) => { 14 data.statuses.forEach(function(val, index, ar){ 15 console.log(index); 16 console.log('@' + val.user.screen_name); 17 console.log(val.text); 18 lastId = val.id.str; 19 }); 20 }); 21 console.log('最後のIDは' + lastId); 22 resolve(); 23 }); 24} 25 26main();

動作した版

いただいた回答を元にさらに修正したところ、無事動作しました

Node.js

1const Twit = require('twit'); 2 3const T = new Twit({ 4 consumer_key: "略", 5 consumer_secret: "略", 6 access_token_key: "略", 7 access_token_secret: "略", 8 app_only_auth: true 9}); 10 11let num = 0; 12let lastId = ''; 13 14let params = { 15 q: '#ハッシュタグ since:2018-11-28_00:00:00_JST until:2018-11-28_00:30:00_JST', 16 count: 3, 17 max_id: lastId, 18 result_type: 'recent', 19 include_entities: false 20} 21 22!(async () => { 23 for(let i=1; i<=3; i++){ 24 console.log('\n\n' + i + '回目の検索開始 ID' + lastId + '以前のツイートを検索'); 25 params.max_id = lastId; 26 const result = await T.get('search/tweets', params); 27 result.data.statuses.forEach(function(val, index, ar){ 28 num++; 29 console.log(`\n${parseInt(index) + 1}個目のツイート (累計${num}個目)`); 30 console.log('@' + val.user.screen_name); 31 console.log(val.text.replace(/\r?\n/g, '')); 32 lastId = val.id_str; 33 console.log('ツイートのIDは' + lastId); 34 }) 35 console.log('\n最後のツイートのIDは' + lastId); 36 } 37})();

このままだと再検索のたびに最初と最後のツイート内容が重複してしまうので、ほんとは再検索の際にIDを-1したいところですが
なまじIDの桁数が多いため計算に誤差が生じて面倒なので、ひとまず重複をよしとしています

なぜJavaScriptで「76287755398823936」が正しく表示できないか、あるいはなぜRubyでも表せないか。
https://7io.org/2011/07/02/21:11:55/

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/11/28 00:04

つ 非同期通信
elpha

2018/11/28 00:47

ありがとうございます。やはりそのへんですか…async/awaitを使って書いてみたものを追記いたしました。
guest

回答2

0

ベストアンサー

まずJavaScriptの非同期処理とイベントループについて調べて理解されることをおすすめします。

非同期に呼び出される関数は、少なくとも現在のコールスタックが空になるまで呼び出されることはありません。今の場合、T.get()のコールバック((err, data, response) => {...}の部分)は3回設定されるわけですが、これらは少なくともfor文が終わったあと、任意の順番で実行されます。検索結果が3回とも同じになるのも、for文内でlastId''のままなのでparamsの更新ができていないことが原因です。

追記のコードでは、resolveする場所が間違っています。コールバックが呼び出された時点で完了したとみなしたいので、コールバックの中でresolveしないと意味がありません。

ですが、twitのUsageを見る限り Promise をサポートしているので自分でPromiseを作る必要がそもそもありません。

テストしてないですが、以下のコードのようにできると思います。

js

1!(async () => { 2 for (let i = 0; i < 3; i++) { 3 params.max_id = lastId 4 const result = await T.get('search/tweets', params) 5 result.data.statuses.forEach(function (val, index, ar) { 6 console.log(index); 7 console.log('@' + val.user.screen_name); 8 console.log(val.text); 9 lastId = val.id.str 10 }) 11 } 12})()

イベントループの分かりやすい動画(英語)
Jake Archibald: In The Loop - JSConf.Asia 2018 (YouTube)

投稿2018/11/28 01:16

編集2018/11/28 03:15
karamarimo

総合スコア2551

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

elpha

2018/11/28 03:00

for文(追記で言うところのsearch())の中で params.max_id = lastId; と再代入していますが、これではいけませんでしたか? あっ! 確かにresolve()の位置が変でしたね…! Promiseなしでの記述はちょっとまだ理解が及ばなさそうですが(thenとかですか…?) ひとまずresolve()の位置を修正し、さらに lastId = val.id.str; が誤表記だったので lastId = val.id_str; に書き換えてみると無事 イロハ ハニホ ホヘト(仮) という計7ツイートを取得することができました
karamarimo

2018/11/28 03:05

> for文(追記で言うところのsearch())の中で > params.max_id = lastId; > と再代入していますが、これではいけませんでしたか? すみません、見逃していました。回答を修正しておきます。 > Promiseなしでの記述はちょっとまだ理解が及ばなさそうですが(thenとかですか…?) これも回答に追記しておきます。
elpha

2018/11/28 04:08

!(async () => {}); および const result = await func(); という表記は見かけたことがありましたが、このように使うのですね! おかげさまで理解が深まりました。ありがとうございます。
karamarimo

2018/11/28 04:57

!(async () => {})(); ですね正確には。先頭の ! は、セミコロンなしで書くスタイルだと、parserに直前の文と合わせて一文とみなされてしまうことがあるので、それを防ぐためのものです。
elpha

2018/11/28 05:25

即時関数というやつでしょうか。 セミコロンがないのはあえてなのかなと気になっていましたが、やはりそうだったのですね。 聞きかじりですが、CoffeScriptなどでもないかぎり基本的にセミコロンは必須(省略すると予期せぬ事態が起こる)という認識でしたので…
karamarimo

2018/11/28 05:29

はい、まさに即時関数(IIFE)です。トップレベルではawaitを使用できないためよく使われます。
guest

0

追記のPromiseのアドバイス。
全体的に横着しすぎだね、もう少しPromiseを勉強してモノにすれば解決するから頑張って。
以下ちょっとしたアドバイス。

Promiseをnewする時に渡す関数内で、
resolve(value)という書き方で引数として設定したvalueを外に持ち出す事が出来るのね。
そのPromiseを.then(value => { console.log(value); })という風に次のthenに指定した関数の第一引数でvalueを取り出してリレーできるんだよ。

要するにTwitterにHTTPリクエストを投げて、
受け取った結果からlastIdを取り出してリレーさせたいんだよね。
だったら結果が帰ってきた所で、なんとかしてlastIdを取り出して、resolve(lastId)ってやらないとダメだよ。

以下は面倒だからざっくり書いちゃおう

JavaScript

1// 関数宣言は巻き上げやらなんやらで面倒だから、この形式が一般的 2// 値を即返すなら{}不要 3// lastIdは関数実行時に外から持ち込むようにすること、むしろparamsを持ち込んだ方が良さげだね 4const search = (params = {}) => new Promise((resolve, reject) => { 5 console.log('検索開始'); 6 T.get('search/tweets', params, (err, data, response) => { 7 // お目当てはlastIdじゃなくて、皆のツイート結果一覧も含まれてるんだからdataを持ち帰る仕組みにしたほうがよくね? 8 if (err) { 9 reject(err); 10 return; 11 } 12 resolve(data); 13 }); 14}) 15 16const main = async () => { 17 let lastId = ''; 18 19 console.log('ループ開始'); 20 for(let i=0; i<3; i++){ 21 // このようにdataを持ち帰るのだ 22 // paramsはこの中で毎回作った方が綺麗じゃね? 23 const data = await search({ 24 q: '#ハッシュタグ since:2018-11-28_00:00:00_JST until:2018-11-28_00:30:00_JST', 25 count: 3, 26 max_id: lastId, 27 result_type: 'recent', 28 include_entities: false, 29 }); 30 31 // ここまでくればあとは単なる同期処理みたいなもんだから自分で出来るでしょ? 32 // TODO: ここでlastIdを抜き出して、次のループで使えるよう仕込む 33 // TODO: 更にツイート一覧を取り出してくっつける 34 } 35} 36 37main();

投稿2018/11/28 03:30

編集2018/11/28 03:32
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問