tag:crieit.net,2005:https://crieit.net/tags/TwitterAPI/feed 「TwitterAPI」の記事 - Crieit Crieitでタグ「TwitterAPI」に投稿された最近の記事 2019-12-29T11:22:37+09:00 https://crieit.net/tags/TwitterAPI/feed tag:crieit.net,2005:PublicArticle/15652 2019-12-29T11:22:37+09:00 2019-12-29T11:22:37+09:00 https://crieit.net/posts/Node-js-Twitter Node.jsで画像/動画つきツイートをTwitterに投稿すると大変だった... <p>JavaScriotでツイートしたいなと思って、いろいろ試していたら、<br /> 30秒以上動画つきツイートが結構めんどくさかったので、その時の備忘録。</p> <h3 id="Node.jsでTwitter APIを使う"><a href="#Node.js%E3%81%A7Twitter+API%E3%82%92%E4%BD%BF%E3%81%86">Node.jsでTwitter APIを使う</a></h3> <p>Node.jsでTwitter APIを使うときは、<a target="_blank" rel="nofollow noopener" href="https://github.com/desmondmorris/node-twitter">desmondmorris/node-twitter</a>を使うのが良さそう</p> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="shell">$ npm install twitter </code></pre> <h4 id="ツイートしてみる"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">ツイートしてみる</a></h4> <p>文字だけをツイートするのは、こんな感じ。</p> <pre><code class="typescript">import Twitter from "twitter"; // 初期化 const client = new Twitter({ consumer_key: TWITTER_CONSUMER_KEY, consumer_secret: TWITTER_CONSUMER_SECLET, access_token_key: ACCESS_TOKEN_KEY, access_token_secret: ACCESS_TOKEN_SECRET }); // 文字だけをツイート async function tweet(text: string) { const tweet = await client.post("statuses/update", { status: text }); } tweet("ツイート").then(); </code></pre> <p>Twitterクラスに<code>.post()</code>や、<code>.get()</code>が用意されているので、<br /> <a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs">Twitter APIのドキュメント</a>を見ながら、呼び出していく感じ。</p> <p>ツイートするのは<a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update">POST statuses/update</a>なのでドキュメントを参照。</p> <h3 id="画像つきでツイートしてみる"><a href="#%E7%94%BB%E5%83%8F%E3%81%A4%E3%81%8D%E3%81%A7%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">画像つきでツイートしてみる</a></h3> <p>画像とか動画とかメディアつきだとちょっとめんどくさく...</p> <p>ツイートと一緒に画像をアップロードできないので、</p> <ol> <li>最初に画像をアップロードしてからmediaIdを取得し、</li> <li>mediaIdと一緒に<code>statuses/update</code>でツイート</li> </ol> <p>という段階的な感じになる。</p> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithImage(text: string, filePath: string) { const data = require('fs').readFileSync(filePath); // 画像をアップロード const media = await client.post('media/upload', {media: data}); // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: media.media_id_string }; const tweet = await client.post("statuses/update", params); } tweetWithImage("ツイート", "./imange.jpg").then(); </code></pre> <p>複数の画像をつけたい場合は、それぞれアップロードして、<br /> <code>media_ids</code>にカンマ区切りでmediaIdを指定する。</p> <p>ただ、この<code>media/upload</code>を1度だけ呼び出すシンプルな方法には制限があり、<br /> GIFや動画はアップロードできない...</p> <h3 id="30秒以下の動画付きツイートをしてみる"><a href="#30%E7%A7%92%E4%BB%A5%E4%B8%8B%E3%81%AE%E5%8B%95%E7%94%BB%E4%BB%98%E3%81%8D%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">30秒以下の動画付きツイートをしてみる</a></h3> <p>動画やGIFをアップロードしたい場合は、<a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload">Chunked media upload</a>という形でアップロードする必要がある。</p> <p>この方法は、大きく3ステップに分かれている</p> <ol> <li>初期化: command=INIT</li> <li>アップロード: command=APPEND</li> <li>完了: commaind=FINALIZE</li> </ol> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithChunkedMedia(text: string, filePath: string) { const mediaType = 'video/mp4'; const mediaData = require('fs').readFileSync(filePath); const mediaSize = require('fs').statSync(filePath).size; // 動画をアップロード: INIT const media = await client.post('media/upload', { command : 'INIT', total_bytes: mediaSize, media_type : mediaType }); // INITでmediaIdが発行されるので、取得しておく const mediaId = media.media_id_string; // 動画をアップロード: UPLOAD await client.post('media/upload', { command : 'APPEND', media_id : mediaId, media : mediaData, segment_index: 0 }); // 動画をアップロード: FINALIZE await client.post('media/upload', { command : 'FINALIZE', media_id: mediaId }); // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: mediaId }; const tweet = await client.post("statuses/update", params); } tweetWithChunkedMedia("ツイート", "./video.mp4").then(); </code></pre> <p>動画やGIFのような大きいサイズのメディアは、分割してアップロードできるこの仕組みを使うっぽい。</p> <p>ただ、30秒以上の動画や1MB(チャンクサイズ上限)を超える場合は、<br /> INIT時に<code>media_category</code>を指定して、非同期アップロードをしないといけない。</p> <h3 id="30秒を超える動画付きツイートをしてみる"><a href="#30%E7%A7%92%E3%82%92%E8%B6%85%E3%81%88%E3%82%8B%E5%8B%95%E7%94%BB%E4%BB%98%E3%81%8D%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">30秒を超える動画付きツイートをしてみる</a></h3> <p>30秒を超える動画は、media_categoryをつけ、非同期アップロードで対応しないといけない。</p> <p>media_categoryは、<code>tweet_image</code>, <code>tweet_gif</code>, <code>tweet_video</code>を指定できるので、<br /> アップロードするメディアに合わせて指定する。</p> <p>また、チャンクサイズの上限が1MBなので、APPENDでデータをPOSTする際には注意。<br /> 1MB以上の場合は、1MB以下になるように分割し、segment_indexでindexを指定する。</p> <pre><code class="typescript">import Twitter from "twitter"; const client = // 略 async function tweetWithChunkedMedia(text: string, filePath: string) { const mediaType = 'video/mp4'; const mediaData = require('fs').readFileSync(filePath); const mediaSize = require('fs').statSync(filePath).size; // 動画をアップロード: INIT const media = await client.post('media/upload', { command : 'INIT', total_bytes: mediaSize, media_type : mediaType, media_category: "tweet_video" // media_categoryを指定 }); const mediaId = media.media_id_string; // 動画をアップロード: UPLOAD await client.post('media/upload', { command : 'APPEND', media_id : mediaId, media : mediaData, segment_index: 0 }); // 動画をアップロード: FINALIZE await client.post('media/upload', { command : 'FINALIZE', media_id: mediaId }); // 動画をアップロード: STATUS while(true) { // アップロードのステータスをポーリング const status = await client.get('media/upload', { command : 'STATUS', media_id: mediaId }); if (status.processing_info.state == "succeeded") { // 完了したら、ポーリングを終了 break; } else if (status.processing_info.state == "failed") { // エラーになったら、例外を投げる throw new Error(status.processing_info.error.message); } else { // 処理中(in_progress)の場合は、指定された秒数分待つ await sleep(status.processing_info.check_after_secs + 1); } } // mediaIdをパラメタに追加して、ツイート const params = { status: text, media_ids: mediaId }; const tweet = await client.post("statuses/update", params); } function sleep(time: number) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), time * 1000); }); } tweetWithChunkedMedia("ツイート", "./video.mp4").then(); </code></pre> <p>かなりハマったのが以下の2点。</p> <ol> <li>STATUSはFINALIZEしてからじゃないと、404が返ってくる</li> <li>FINALIZEのレスポンスにもprocessing_infoがあるが、<br /> STATUSをしないと永遠にpending状態。(STATUSを呼ぶと処理が始まる)</li> </ol> <p>このあたり、ドキュメントに詳しい説明がなくて、かなりハマった...</p> <h3 id="axiosを使って外部URLのメディアをツイートする"><a href="#axios%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%A4%96%E9%83%A8URL%E3%81%AE%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2%E3%82%92%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B">axiosを使って外部URLのメディアをツイートする</a></h3> <p>Cloud Storageにある画像/動画を含めてツイートしたかったので、<br /> axiosを使って外部URLを取得する処理を加えてみたのがこれ。</p> <p><code>new TwitterApi().postTweet("ツイート", ["https://..."]);</code><br /> みたいに呼び出すと、ダウンロード/アップロード/ツイートできる。(はず...)</p> <pre><code class="typescript">import Twitter from "twitter"; import axios from "axios"; /** * スリープ処理 * @param time スリープする秒数 */ function sleep(time: number) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), time * 1000); }); } export default class TwitterApi { private client: Twitter; constructor() { this.client = new Twitter({ consumer_key: // TWITTER_CONSUMER_KEY, consumer_secret: // TWITTER_CONSUMER_SECLET, access_token_key: // ACCESS_TOKEN_KEY, access_token_secret: // ACCESS_TOKEN_SECRET }); } /** * ツイートするメイン処理 * @param text ツイート文 * @param medias 添付する外部URLのリスト */ public async postTweet(text: string, medias: string[] = []) { let mediaIds: string[] = []; if (medias.length > 0) { // メディアファイルがあれば、アップロードしてmediaIdを取得 mediaIds = await Promise.all( medias.map(async v => await this.uploadMedia(v.url)) ); } const res = await this.tweet(text, mediaIds); } /** * メディアのアップロード処理 * @param url メディアのURL */ private async uploadMedia(url: string) { // axiosを使って、メディアのデータを取得 const res = await axios.create({ responseType: "arraybuffer" }).get(url); const mediaData: ArrayBuffer = res.data; const mediaSize = res.headers["content-length"]; const mediaType = res.headers["content-type"]; // INIT: mp4かgifなら、media_categoryを指定する const initParams = { command: "INIT", total_bytes: mediaSize, media_type: mediaType }; if (mediaType == "video/mp4") { initParams["media_category"] = "tweet_video"; } else if (mediaType == "image/gif") { initParams["media_category"] = "tweet_gif"; } const data = await this.client.post("media/upload", initParams); const mediaId = data.media_id_string; // APPEND: 500Bくらいにチャンクを分けてアップロードする const chunkSize = 500000; const chunkNum = Math.ceil(mediaSize / chunkSize); for (let index = 0; index < chunkNum; index++) { const chunk = mediaData.slice(chunkSize * index, chunkSize * (index + 1)); const resAppend = await this.client.post("media/upload", { command: "APPEND", media_id: mediaId, media: mediaData.slice(chunkSize * index, chunkSize * (index + 1)), segment_index: index }); } // FINALIZE const resFinalize = await this.client.post("media/upload", { command: "FINALIZE", media_id: mediaId }); if (!resFinalize.processing_info) { // media_categoryをしていないと、processing_infoがない return mediaId; } else if (resFinalize.processing_info.state == "succeeded") { return mediaId; } else if (resFinalize.processing_info.state == "failed") { throw new Error(resFinalize.processing_info.error.message); } // STATUS while (true) { const resStatus = await this.client.get("media/upload", { command: "STATUS", media_id: mediaId }); if (resStatus.processing_info.state == "succeeded") { return mediaId; } else if (resStatus.processing_info.state == "failed") { throw new Error(resStatus.processing_info.error.message); } else { await sleep(resStatus.processing_info.check_after_secs + 1); } } } /** * ツイート処理 * @param text ツイート文 * @param mediaIds メディアIDのリスト */ private async tweet(text: string, mediaIds: string[] = []) { const params = { status: text }; if (mediaIds.length > 0) params["media_ids"] = mediaIds.join(","); const tweet = await this.client.post("statuses/update", params); return tweet; } } </code></pre> <p>若干ハマったのが、以下の2点</p> <ol> <li>axiosで取得する場合は、<code>{ responseType: "arraybuffer" }</code>でcreateしないといけない</li> <li>cloudStrageでデータを取得できないので、downloadURLを取得しておかないといけない</li> </ol> <p>Twitter APIむずい...</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mpyw/items/7bedf8c23de286cef0f9">TwitterAPIのアップロード系エンドポイントまとめ (140秒動画対応) - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/tutorials/uploading-media">Uploading media — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/media-best-practices">Media best practices — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/uploading-media/chunked-media-upload">Chunked media upload — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/12740659/downloading-images-with-node-js">Downloading images with node.js - Stack Overflow</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/media/upload-media/api-reference/get-media-upload-status">GET media/upload (STATUS) — Twitter Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ksh-fthr/items/ba7c80252edad0e7c66c">[axios] 画像データのレスポンスを取得する際にハマった話 - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15649 2019-12-29T11:18:43+09:00 2019-12-29T11:18:43+09:00 https://crieit.net/posts/Twitter-URL Twitterでツイートできる文字数を正確に数える(絵文字もURLも) <p>Nuxt.jsでツイートするアプリを作りたいなと思い、<br /> 文字数ってどうやって計算するんだろ?って思ったら、<br /> 公式でライブラリ(<a target="_blank" rel="nofollow noopener" href="https://github.com/twitter/twitter-text">twitter-text</a>)が用意されているらしいので、使ってみたときの備忘録</p> <p>Java版/Ruby版/JavaScript版/Objective-C版などいろいろあるらしい。</p> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="shell">$ npm install twitter-text </code></pre> <h4 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h4> <pre><code class="javascript">const twitter = require('twitter-text'); // ツイートするテキスト const tweetText = "This is a test tweet"; // twitter-textで計算 const result = twitter.parseTweet(tweet); console.log(result) /* Returns: { weightedLength: 20, permillage: 71, valid: true, displayRangeEnd: 19, displayRangeStart: 0, validRangeEnd: 19, validRangeStart: 0 } */ // 日本語版の場合、文字数を2で割るとツイッターと同じになる。 const textLength = result.weightedLength / 2; </code></pre> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/PND/items/17e87b8839c9099d2e70#twitter-text-%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">ツイートの文字数を <strong>厳密に</strong> 数える方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/twitter/twitter-text/tree/master/js">twitter-text/js at master · twitter/twitter-text</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.twitter.com/en/docs/developer-utilities/twitter-text">twitter-text Parser — Twitter Developers</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15457 2019-10-07T09:13:01+09:00 2019-10-07T09:31:08+09:00 https://crieit.net/posts/GAS-5d9a830d875a5 GASでいいねしたツイートをいいねの数とリツイートの数と一緒に記録するやつ <p>特定のユーザーのいいねしたツイート一覧を、ツイートに付いたいいねの数とリツイートの数も一緒に記録したくなって作りました。トリガー機能を使うと、定期的にいいねが記録されるようになります。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/yanagiha/items/40c7f0cc140eace11bd8">GASで自分のツイートを取得してスプレッドシートに記録するやつ<br /> </a><br /> この記事も合わせて読むとわかりやすいかもしれません。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/d311791c-7655-7d87-1dba-cb3ee543444d.png" alt="スクリーンショット 2019-10-07 3.40.44.png" /></p> <p>このように記録されます。</p> <pre><code class="javascript"><br /><br />function getFav(){ var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/favorites/list.json?screen_name=いいねを取得したいアカウントのscreen name&count=100"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('いいねを記録したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("F2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("G2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var userId = array[int]["user"]["id_str"]; var json = service.fetch("https://api.twitter.com/1.1/users/show.json?user_id="+userId+"&include_entities=false"); var array2 = JSON.parse(json); var screenName = array2["screen_name"]; var text = array[int]["text"]; var favorite_count = array[int]["favorite_count"]; var retweet_count = array[int]["retweet_count"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(screenName); sheet.getRange(lastRow,3).setValue(text); sheet.getRange(lastRow,4).setValue(favorite_count); sheet.getRange(lastRow,5).setValue(retweet_count); sheet.getRange(lastRow,6).setValue(id); } lastRow = lastRow + 1; } </code></pre> <p>いいねを100件取得して、idが以前記録したツイートより大きければ記録する…といった感じです。<br /> 何かあったら気軽にコメントください。</p> yanagiha tag:crieit.net,2005:PublicArticle/15456 2019-10-07T09:10:43+09:00 2019-10-07T09:10:43+09:00 https://crieit.net/posts/GAS GASで自分のツイートを取得してスプレッドシートに記録するやつ <p>急にツイ消ししたくなった場合に備えて、自分のツイートをグーグルスプレッドシートに記録しておくことにしました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/golyat/items/6b430986dbfd8dd1c239">GASでTwitterの投稿とタイムライン取得</a><br /> こちらの記事を参考にして書いてみました。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406061/43b4b722-3b85-bb5e-be81-133be3f4d987.jpeg" alt="20190828150010.jpg" /><br /> 画像のように記録されます。</p> <pre><code class="javaScript"><br /> var twitter = TwitterWebService.getInstance( '**********', // 作成したアプリケーションのConsumer Key '**********' // 作成したアプリケーションのConsumer Secret ); // 認証を行う(必須) function authorize() { twitter.authorize(); } // 認証をリセット function reset() { twitter.reset(); } // 認証後のコールバック(必須) function authCallback(request) { return twitter.authCallback(request); } function getMyTweets() { var service = twitter.getService(); var json = service.fetch("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=ツイートを取得したいユーザーのid&count=30"); var array = JSON.parse(json); var ss = SpreadsheetApp.getActiveSpreadsheet(); var sheet = ss.getSheetByName('ツイートを保存したいシートの名前'); var lastRow = sheet.getLastRow() +1; var lastId = sheet.getRange("D2").getValue(); for(var i = 0; i <= array.length -1; i++) { var int = parseInt(i); if(i === 0){ var recId = array[int]["id"]; sheet.getRange("D2").setValue(recId); } var id = array[int]["id"]; if(id > lastId){ var time = array[int]["created_at"]; var text = array[int]["text"]; sheet.getRange(lastRow,1).setValue(time); sheet.getRange(lastRow,2).setValue(text); sheet.getRange(lastRow,3).setValue(id); } lastRow = lastRow + 1; } } </code></pre> <p>自分のツイートを最新のやつから30件まで取得して、idが以前取得したツイートより大きければスプレッドシートに書き込む……という感じです。</p> <p>ここ違うよ〜とかもっと良い書き方あるよ〜って場合は気軽にコメントください。</p> yanagiha tag:crieit.net,2005:PublicArticle/14675 2018-12-19T08:27:14+09:00 2018-12-20T01:27:20+09:00 https://crieit.net/posts/MySQL-BigQuery 話題のツイートを探せる「ツイレポ」に用いられている技術について(MySQL -> BigQueryに移行した) <p>この記事は<a href="https://crieit.net/advent-calendars/2018/technology">個人開発サービスに用いられている技術 Advent Calendar 2018</a> の19日目です。</p> <p>昨日は<a target="_blank" rel="nofollow noopener" href="https://twitter.com/isaito24085408">@isaitoi</a>さんの「<a href="https://crieit.net/posts/Microsoft">Microsoftの技術でもちゃんとしたサービスを作れるんだぞ!!</a>」でした。</p> <hr /> <p>こんにちは、2z(Twitter: <a target="_blank" rel="nofollow noopener" href="https://twitter.com/2zn01">@2zn01</a> )です。</p> <p>普段は会社員でWeb系の開発エンジニアとして働き、週末に趣味で個人開発をしています。</p> <p>僕自身、普段あまり時間がない中でどう情報収集したらよいか困っているということもあり、エンジニアらしく技術で何とか解決するべく、ツイッターを活用し、エンジニアの情報収集を助けるキュレーションサービスを作ってみました。</p> <h2 id="作ったもの"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">作ったもの</a></h2> <p>ツイッターで話題になっている情報をジャンルごとにまとめる「ツイレポ」というサービスを作りました。</p> <p>■ツイレポ<br /> <a target="_blank" rel="nofollow noopener" href="https://twirepo.com/">https://twirepo.com/</a></p> <p><a href="https://crieit.now.sh/upload_images/8b8de3f71f6faf506a78c98c3c3c8ca65c198192823cd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8b8de3f71f6faf506a78c98c3c3c8ca65c198192823cd.png?mw=700" alt="twirepo_slide.png" /></a></p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">【お知らせ】ツイッターを活用したエンジニアの情報収集を助けるキュレーションサービス <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E3%83%84%E3%82%A4%E3%83%AC%E3%83%9D?src=hash&ref_src=twsrc%5Etfw">#ツイレポ</a> をリリースしました!🎉<a target="_blank" rel="nofollow noopener" href="https://t.co/oFiPYwT34N">https://t.co/oFiPYwT34N</a>時間がない中でどう情報収集したらよいか困っていたので、話題の情報のみをサクッと確認できるように作りました。ぜひ使ってみてください~! <a target="_blank" rel="nofollow noopener" href="https://t.co/DHVGfBC0BQ">pic.twitter.com/DHVGfBC0BQ</a></p>— 2z / AIメーカー開発 (@2zn01) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/2zn01/status/1069286642765062144?ref_src=twsrc%5Etfw">2018年12月2日</a></blockquote> <p>画面イメージはこんな感じです。</p> <p><img width="400" alt="twirepo_screenshot1.png" src="https://qiita-image-store.s3.amazonaws.com/0/273248/657076e1-a42b-3f68-2ccf-6d7cdef4e9eb.png"></p> <p>「ツイレポ」では以下のジャンルごとに話題となっているツイートを見ることができます。</p> <ul> <li>トップ(ツイート)</li> <li>ニュース</li> <li>画像</li> <li>動画</li> <li>ライブ配信</li> <li>ネットメディア</li> <li>面白メディア</li> <li>エンタメ</li> <li>ライフスタイル</li> <li>女性向けメディア</li> <li>マーケティング</li> <li>スタートアップ</li> <li>ブログ</li> <li>イラスト</li> <li>いらすとや</li> <li>漫画</li> <li>本</li> <li>アニメ/ドラマ/映画</li> <li>音楽</li> <li>アプリ</li> <li>料理</li> <li>飲食店</li> <li>イベント</li> <li>まとめサイト</li> <li>プレスリリース</li> <li>はてブ</li> <li>NewsPicks</li> <li>note / cakes</li> <li>Voicy</li> <li>bouncy</li> <li>YouTube</li> <li>TikTok</li> <li>Amazon</li> <li>bosyu</li> <li>エンジニア求職</li> <li>GitHub</li> <li>Revue</li> <li>技術論文</li> <li>スライド</li> <li>Advent Calendar</li> <li>やってみた</li> <li>歌ってみた</li> <li>演奏してみた</li> <li>弾いてみた</li> <li>叩いてみた</li> <li>踊ってみた</li> <li>描いてみた</li> <li>作ってみた</li> <li>リリースした</li> </ul> <p>技術以外のジャンルもありますが、サービスを作り上げるエンジニアたる者、色んなジャンルにアンテナを張って様々な情報に触れる必要があると思うわけです。</p> <p>僕自身もアンテナを張りつつ、常に個人開発のサービスのアイディアに繋げられないかを考えていますので、このサービスによってその情報収集を楽にしたいと考えました。</p> <p>また、ジャンルごとに以下基準で並び替えを行い、ランキング形式で情報を確認することができます。</p> <ul> <li>リツイート数</li> <li>いいね数</li> <li>引用数</li> <li>リプライ数</li> <li>フォロワーのうち、リツイートしてくれた割合(リツイート数 / フォロワー数)</li> <li>フォロワーのうち、いいねしてくれた割合(いいね数 / フォロワー数)</li> <li>フォロワーのうち、引用してくれた割合(引用数 / フォロワー数)</li> <li>フォロワーのうち、リプライしてくれた割合(リプライ数 / フォロワー数)</li> </ul> <p><img width="485" alt="twirepo_screenshot_sort.png" src="https://qiita-image-store.s3.amazonaws.com/0/273248/f30d3ed4-99dd-fb8f-96e8-24f9112cad4a.png"></p> <h2 id="何で作ったの?"><a href="#%E4%BD%95%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%AE%EF%BC%9F">何で作ったの?</a></h2> <p>皆さんは情報収集って何でしていますか?</p> <p>GIGAZINE、ITmediaなどの様々なネットメディア、それからはてなブックマーク、技術記事であればQiita、はてなブログ、等々、あげると切りがありません。</p> <p>忙しいエンジニアにとっては、情報収集のためにすべてに目を通すのは難しいです。</p> <p>きちんと情報収集されている方だと、RSSリーダーなどを使って自分の欲しい情報を効率的に得ている人もいるかと思います。</p> <p>ただ、僕の場合、そこまでのことはやっておらず、はてなブックマークで話題になっている情報にさっと目を通して終わりというのがほとんどでした。</p> <p>少ない時間の中でいかに効率良く情報収集できるか、を解決するために、今回自分でサービスを作ってみることにしました。</p> <p>僕が目をつけたのは、ツイッターです。</p> <p>ツイッターは自分がフォローした人がつぶやいた情報を見ることができますが、タイムラインではすべてを追うのは難しいです。<br /> しかし、ツイートにはリツイート、いいね、リプライといった、他の人がつけた評価みたいなものがつけられています。</p> <p>リツイート数やいいね数が多いものほど、話題になっている情報ととらえることができます。<br /> この話題となっているツイートをキュレーションすることで、効率的に情報収集ができるのでは!? と考え、このサービスを作り始めました。</p> <h2 id="使用技術"><a href="#%E4%BD%BF%E7%94%A8%E6%8A%80%E8%A1%93">使用技術</a></h2> <h3 id="Linux"><a href="#Linux">Linux</a></h3> <p>クラウドのホスティングはGoogle Cloud Platformで、Google Compute Engine(GCE)でサーバを立てています。<br /> サーバのOSはLinuxでCentOSの7系を使っています。</p> <h3 id="Apache"><a href="#Apache">Apache</a></h3> <p>webサーバはApacheを使っています。<br /> Nginxもありますが、Apacheは普段から使っており、設定も把握していたため、いつも通りの安定の選択としました。</p> <h3 id="MySQL"><a href="#MySQL">MySQL</a></h3> <p>Amazon RDSを使ってMySQLを立てています。</p> <h3 id="BigQuery"><a href="#BigQuery">BigQuery</a></h3> <p>Google Cloud PlatformのBigQueryを使うようにしました。</p> <p>リリース後2週間ほどツイートを収集したら100万レコードを超えてしまい、応答速度的にも今後ヤバいな...ってなったので、BigQueryへデータを移行しました。</p> <p>BigQueryへの完全移行ではなく、MySQLとBigQueryのハイブリッド構成としました。</p> <p>移行については、この後ちょっと触れます。</p> <h3 id="PHP"><a href="#PHP">PHP</a></h3> <p>サーバ側のプログラムはPHPを使って実装しました。</p> <h3 id="jQuery"><a href="#jQuery">jQuery</a></h3> <p>クライアント側のJavaScriptは、jQueryを使って実装しました。</p> <h3 id="ツイッターAPI"><a href="#%E3%83%84%E3%82%A4%E3%83%83%E3%82%BF%E3%83%BCAPI">ツイッターAPI</a></h3> <p>ツイートの情報を取得するために使用しています。<br /> 使い方については、後述させて頂きます。</p> <h3 id="cron"><a href="#cron">cron</a></h3> <p>cronを使って定期的にバッチを走らせてツイートを収集しています。</p> <h2 id="MySQL -&gt; BigQueryへのデータ移行"><a href="#MySQL+-%26gt%3B+BigQuery%E3%81%B8%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E7%A7%BB%E8%A1%8C">MySQL -> BigQueryへのデータ移行</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/MemedDev/mysql-to-google-bigquery">mysql-to-google-bigquery</a> を使って、移行しました。</p> <p>要件としてはPHP 7以上である必要がありますが、使い方は超簡単です。</p> <p>①composerを使ってライブラリをダウンロード</p> <pre><code>$ composer require memeddev/mysql-to-google-bigquery </code></pre> <p>②プロジェクト直下に .env ファイルを用意し、以下の環境変数を設定する</p> <pre><code>BQ_PROJECT_ID=bigquery-project-id BQ_KEY_FILE=google-service-account-json-key-file.json BQ_DATASET=bigquery-dataset-name DB_DATABASE_NAME=mysql-database-name DB_USERNAME=mysql_username DB_PASSWORD=mysql_password DB_HOST=mysql-host </code></pre> <p>③MySQL -> BigQueryへのデータ同期を実行する</p> <pre><code>$ vendor/bin/console sync table-name --create-table </code></pre> <p>これだけでMySQLからBigQueryへデータを移行できちゃいます。</p> <h2 id="MySQLとBigQueryのハイブリッド構成"><a href="#MySQL%E3%81%A8BigQuery%E3%81%AE%E3%83%8F%E3%82%A4%E3%83%96%E3%83%AA%E3%83%83%E3%83%89%E6%A7%8B%E6%88%90">MySQLとBigQueryのハイブリッド構成</a></h2> <p>BigQueryにデータはすべて移行したんですが、BigQueryは保存しているデータ量およびクエリの検索対象となったデータ量をもとに課金され、検索対象のデータ量による課金が今後ヤバくなるのは目に見えているため、MySQLも使ったハイブリッド構成にしました。</p> <p>ツイートをジャンルごとにリツイート数などでソートして一覧を取得する部分は比較的DBに負荷がかかるため、BigQueryを使用しています。<br /> なお、ユニークIDからの単一のツイート取得はそれほどDBに負荷がかからないため、MySQLを使用しています。</p> <p>とはいえ、毎回一覧を取得するたびにBigQueryを呼び出すのも高くなっちゃうので、「ジャンルごと」 × 「ソートのオーダーごと」 × 「日ごと」でjsonファイルで分けて結果をキャッシュファイルとして保存するようにしました。</p> <p>jsonファイルがある場合はそのファイルからデータを取得し、jsonファイルがない場合はBigQueryを呼び出して結果を受け取り、jsonファイルへ保存するといった具合にしています。こうすることで、BigQueryによる検索量による課金地獄を回避しています(汗)、、クラウド破産こわい...><</p> <p>また、jsonファイルを保存する際には、単一のディレクトリに保存するのではなく、ファイル名をハッシュ化して先頭の2文字をとって、ディレクトリの階層を2つ作って保存しています。</p> <p>以下のような感じです。</p> <ul> <li>①news_retweet_20181219.json の場合、 「news_retweet_20181219」の文字をハッシュ化</li> <li>②ハッシュ化された先頭2文字がa, bだった場合、a/b/ の階層でディレクトリを作成</li> <li>③ a/b/news_retweet_20181209.json へjsonファイルを保存</li> </ul> <p>これは一つのディレクトリに1000個を超える大量のファイルを置くと、読み込み等のパフォーマンスに悪影響が出てしまうからです。</p> <h2 id="バズったツイートの可視化"><a href="#%E3%83%90%E3%82%BA%E3%81%A3%E3%81%9F%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%AE%E5%8F%AF%E8%A6%96%E5%8C%96">バズったツイートの可視化</a></h2> <p>ツイートがバズった要因のようなものが少しでも可視化できたらと思い、以下に挙げたグラフを確認できるようにしてみました。</p> <p>各ジャンルごとに下部メニューのグラフアイコンをクリック、または各ツイートのインフォメーションアイコンをクリックするとみることができます。</p> <p><a href="https://crieit.now.sh/upload_images/b8e016c2ff6a2fbd426e8e3ff1ff13665c1981c7c124c.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b8e016c2ff6a2fbd426e8e3ff1ff13665c1981c7c124c.png?mw=700" alt="twirepo_グラフ.png" /></a></p> <h3 id="リツイート数、いいね数の分布"><a href="#%E3%83%AA%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E6%95%B0%E3%80%81%E3%81%84%E3%81%84%E3%81%AD%E6%95%B0%E3%81%AE%E5%88%86%E5%B8%83">リツイート数、いいね数の分布</a></h3> <p>ランキング100位までのツイートのリツイート数、いいね数を分布図で可視化しました。<br /> バズったツイートのリツイート数、いいね数の傾向を見ることができます。</p> <p><a href="https://crieit.now.sh/upload_images/432fb70049c10af4e6992197c428b0a85c1981dd59b29.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/432fb70049c10af4e6992197c428b0a85c1981dd59b29.png?mw=700" alt="twirepo_retweet.png" /></a></p> <h3 id="ツイートの時間帯"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%AE%E6%99%82%E9%96%93%E5%B8%AF">ツイートの時間帯</a></h3> <p>ランキング100位までのツイートの時間帯を棒グラフで可視化しました。<br /> バズったツイートがどの時間帯でツイートされたかを見ることができます。</p> <p><a href="https://crieit.now.sh/upload_images/d2de452ea24d15b7dafc8c9a5cc079685c1981eabea58.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d2de452ea24d15b7dafc8c9a5cc079685c1981eabea58.png?mw=700" alt="twirepo_時間帯.png" /></a></p> <h3 id="ツイートの曜日・時間帯"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%AE%E6%9B%9C%E6%97%A5%E3%83%BB%E6%99%82%E9%96%93%E5%B8%AF">ツイートの曜日・時間帯</a></h3> <p>ランキング100位までのツイートの曜日、時間帯をバブルチャートで可視化しました。<br /> バズったツイートがどの曜日、時間帯でツイートされたかを見ることができます。</p> <p><a href="https://crieit.now.sh/upload_images/f30a1b83dfd159ee5a902ffc35a5fee95c1981ff3000d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f30a1b83dfd159ee5a902ffc35a5fee95c1981ff3000d.png?mw=700" alt="twirepo_時間帯曜日.png" /></a></p> <h3 id="使われているワード"><a href="#%E4%BD%BF%E3%82%8F%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%83%AF%E3%83%BC%E3%83%89">使われているワード</a></h3> <p>ランキング100位までのツイートで使われているワードをワードクラウドで可視化しました。<br /> バズったツイートでは、どんなワードが使われているかを見ることができます。</p> <p><a href="https://crieit.now.sh/upload_images/9c71893e3478e448141b187906a1eb9b5c1982111e87d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9c71893e3478e448141b187906a1eb9b5c1982111e87d.png?mw=700" alt="twirepo_ワード.png" /></a></p> <h3 id="エンゲージメント数"><a href="#%E3%82%A8%E3%83%B3%E3%82%B2%E3%83%BC%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E6%95%B0">エンゲージメント数</a></h3> <p>当該ツイートのエンゲージメント数を棒グラフで可視化しました。</p> <p><a href="https://crieit.now.sh/upload_images/a8a275fac4951a3ce1bcf59b33c71e945c198227e3a27.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/a8a275fac4951a3ce1bcf59b33c71e945c198227e3a27.png?mw=700" alt="twirepo_エンゲージメント数.png" /></a></p> <h3 id="エンゲージメント率"><a href="#%E3%82%A8%E3%83%B3%E3%82%B2%E3%83%BC%E3%82%B8%E3%83%A1%E3%83%B3%E3%83%88%E7%8E%87">エンゲージメント率</a></h3> <p>当該ツイートのエンゲージメント率をアクティビティゲージグラフで可視化しました。<br /> フォロワーのうちの何%が反応してくれているかを見ることができます。</p> <p><a href="https://crieit.now.sh/upload_images/18c901f8ca942ac061acb3c38858c6255c1982383bde4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/18c901f8ca942ac061acb3c38858c6255c1982383bde4.png?mw=700" alt="twirepo_エンゲージメント率.png" /></a></p> <h2 id="ツイッターAPI"><a href="#%E3%83%84%E3%82%A4%E3%83%83%E3%82%BF%E3%83%BCAPI">ツイッターAPI</a></h2> <p>ツイートはツイッターAPIを使って取得しています。</p> <h3 id="エンドポイント"><a href="#%E3%82%A8%E3%83%B3%E3%83%89%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88">エンドポイント</a></h3> <pre><code>GET [https://api.twitter.com/1.1/search/tweets.json](https://api.twitter.com/1.1/search/tweets.json) </code></pre> <h3 id="リクエストパラメータ"><a href="#%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF">リクエストパラメータ</a></h3> <div class="table-responsive"><table> <thead> <tr> <th>パラメータ</th> <th>説明</th> </tr> </thead> <tbody> <tr> <td>q</td> <td>ツイートを検索するワード。検索演算子も利用できます</td> </tr> <tr> <td>geocode</td> <td>緯度,経度,範囲(半径)を指定。</td> </tr> <tr> <td>lang</td> <td>検索対象とする言語を指定。</td> </tr> <tr> <td>locale</td> <td>検索に使用する言語を指定。</td> </tr> <tr> <td>result_type</td> <td>ツイート結果をpopular(話題のツイート)、recent(最新のツイート)、mixed(すべてのツイート)の中から指定。</td> </tr> <tr> <td>count</td> <td>ツイートを取得する数を指定。</td> </tr> <tr> <td>until</td> <td>yyyy-mm-ddの形式で検索期間のエンドを指定。</td> </tr> <tr> <td>since_id</td> <td>検索対象とするツイートIDのスタートを指定。</td> </tr> <tr> <td>max_id</td> <td>検索対象とするツイートIDのエンドを指定。</td> </tr> <tr> <td>include_entities</td> <td>レスポンス値にentitiesプロパティを含めるかを指定</td> </tr> </tbody> </table></div> <p>また、上記のqパラメータには以下の検索演算子を使うことができます。<br /> これはツイッターの検索でも同様に使用できますので、ぜひお試しください。</p> <div class="table-responsive"><table> <thead> <tr> <th>検索演算子</th> <th>説明</th> </tr> </thead> <tbody> <tr> <td>min_retweets:○○</td> <td>リツイート数が○○以上のツイートを取得できます。</td> </tr> <tr> <td>min_faves:○○</td> <td>いいね数が○○以上のツイートを取得できます。</td> </tr> <tr> <td>min_replies:○○</td> <td>リプライ数が○○以上のツイートを取得できます。</td> </tr> <tr> <td>@○○</td> <td>○○(ユーザーID)さんに関するツイートを(ユーザーがしたツイートも、ユーザーへのリプライ、メンションも含め)取得できます。</td> </tr> <tr> <td>from:○○</td> <td>○○(ユーザーID)さんがしたツイートを取得できます。</td> </tr> <tr> <td>to:○○</td> <td>○○(ユーザーID)さんへのツイート(リプライ、メンション)を取得できます。</td> </tr> <tr> <td>since:yyyy-mm-dd</td> <td>yyyy年mm月dd日以降のツイートを取得できます。</td> </tr> <tr> <td>until::yyyy-mm-dd</td> <td>yyyy年mm月dd日までのツイートを取得できます。</td> </tr> <tr> <td>filter:images</td> <td>画像が含まれるツイートを取得できます。</td> </tr> <tr> <td>filter:videos</td> <td>動画が含まれるツイートを取得できます。</td> </tr> <tr> <td>filter:links</td> <td>リンクが含まれるツイートを取得できます。</td> </tr> <tr> <td>filter:news</td> <td>ニュースに関するツイートを取得できます。</td> </tr> <tr> <td>filter:periscope</td> <td>ライブ配信に関するツイートを取得できます。</td> </tr> <tr> <td>filter:verified</td> <td>認証アカウントのツイートを取得できます。</td> </tr> <tr> <td>filter:safe</td> <td>不適切でないツイートを取得できます。</td> </tr> <tr> <td>source:○○</td> <td>ツイートの投稿元(クライアント)を指定して取得することができます。○○には「Instagram」などが使えるようです。</td> </tr> <tr> <td>near:○○ within:××km</td> <td>○○の場所、半径××km以内でツイートされたものを取得できます。</td> </tr> <tr> <td>geocode:○○,△△,××km</td> <td>緯度○○、経度△△、半径××km以内でツイートされたものを取得できます。</td> </tr> <tr> <td>lang:○○</td> <td>ツイートの言語を指定して検索することができます。日本語の場合は「ja」を指定します。</td> </tr> <tr> <td>"○○ ××"</td> <td>「○○ ××」に完全一致したツイートのみ取得できます。</td> </tr> <tr> <td>:)</td> <td>ポジティブなツイートを取得できるみたいです。</td> </tr> <tr> <td>:(</td> <td>ネガティブなツイートを取得できるみたいです。</td> </tr> <tr> <td>-</td> <td>条件の先頭に「-」をつけると否定の条件にすることができます。これを使うことで除外したツイートを取得できます。</td> </tr> <tr> <td>OR</td> <td>通常は半角スペースで複数のワードを入力するとAND検索となりますが、ORを使うことでOR検索をすることができます。</td> </tr> </tbody> </table></div> <h2 id="ツイート収集対象と条件"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E5%8F%8E%E9%9B%86%E5%AF%BE%E8%B1%A1%E3%81%A8%E6%9D%A1%E4%BB%B6">ツイート収集対象と条件</a></h2> <p>ツイートを取得する対象と条件について、以下にまとめてみます。<br /> なお、以下は各ジャンルごとの条件で、これに以下の流れで条件を追加して収集しています。</p> <ul> <li><ol> <li>リツイート数が 10000 または いいね数が 10000 のツイート収集(すべて収集したら次へ)</li> </ol></li> </ul> <p> ↓</p> <ul> <li><ol start="2"> <li>リツイート数が 1000 または いいね数が 1000 のツイート収集(すべて収集したら次へ)</li> </ol></li> </ul> <p> ↓</p> <ul> <li><ol start="3"> <li>リツイート数が 100 または いいね数が 100 のツイート収集(すべて収集したら次へ)</li> </ol></li> </ul> <p> ↓</p> <ul> <li><ol start="4"> <li>リツイート数が 10 または いいね数が 10 のツイート収集(すべて収集したら終わり)</li> </ol></li> </ul> <p>  ※リツイート数といいね数がともに10未満のツイートは収集していません</p> <h3 id="トップ"><a href="#%E3%83%88%E3%83%83%E3%83%97">トップ</a></h3> <pre><code>lang:ja -filter:verified </code></pre> <p>日本語のツイートをすべて取得します。<br /> ただし、フォロワー数が多く、リツイート数やいいね数が多くつく認証アカウントのツイートは除外しています。</p> <h3 id="ニュース"><a href="#%E3%83%8B%E3%83%A5%E3%83%BC%E3%82%B9">ニュース</a></h3> <pre><code>filter:news lang:ja </code></pre> <p>ニュースに関するツイートをすべて取得します。<br /> ニュースでは信頼性の高い認証アカウントのツイートも含めています。</p> <h3 id="画像"><a href="#%E7%94%BB%E5%83%8F">画像</a></h3> <pre><code>filter:images lang:ja -filter:verified </code></pre> <p>画像が含まれるツイートをすべて取得します。<br /> フォロワー数が多く、リツイート数やいいね数が多くつく認証アカウントのツイートは除外しています。</p> <h3 id="動画"><a href="#%E5%8B%95%E7%94%BB">動画</a></h3> <pre><code>filter:videos lang:ja -filter:verified </code></pre> <p>動画が含まれるツイートをすべて取得します。<br /> フォロワー数が多く、リツイート数やいいね数が多くつく認証アカウントのツイートは除外しています。</p> <h3 id="ライブ配信"><a href="#%E3%83%A9%E3%82%A4%E3%83%96%E9%85%8D%E4%BF%A1">ライブ配信</a></h3> <pre><code>filter:periscope lang:ja -filter:verified </code></pre> <p>ライブ配信に関するツイートをすべて取得します。<br /> フォロワー数が多く、リツイート数やいいね数が多くつく認証アカウントのツイートは除外しています。</p> <h3 id="ネットメディア"><a href="#%E3%83%8D%E3%83%83%E3%83%88%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2">ネットメディア</a></h3> <pre><code>(gigazine.net OR gizmodo.jp OR nlab.itmedia.co.jp OR rocketnews24.com OR getnews.jp OR wired.jp OR itmedia.co.jp OR ggsoku.com) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://gigazine.net/">GIGAZINE</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.gizmodo.jp/">GIZMODO</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://nlab.itmedia.co.jp/">ねとらぼ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://rocketnews24.com/">ロケットニュース24</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://getnews.jp/">ガジェット通信</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://wired.jp/">WIRED</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://www.itmedia.co.jp/">ITmedia</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://ggsoku.com/">ぐぐそく</a></li> </ul> <h3 id="面白メディア"><a href="#%E9%9D%A2%E7%99%BD%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2">面白メディア</a></h3> <pre><code>(omocoro.jp OR labaq.com OR dailyportalz.jp OR curazy.com) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://omocoro.jp/">オモコロ</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://labaq.com/">らばQ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dailyportalz.jp/">デイリーポータルZ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://curazy.com/">クレイジー</a></li> </ul> <h3 id="エンタメ"><a href="#%E3%82%A8%E3%83%B3%E3%82%BF%E3%83%A1">エンタメ</a></h3> <pre><code>(oricon.co.jp OR mantan+web.jp OR nikkan*spa.jp OR wpb.shueisha.co.jp OR narinari.com OR news.mynavi.jp OR cinra.net OR rockinon.com) filter:links lang:ja </code></pre> <p>※半角ハイフン「-」がURLに含まれると、うまく検索できないケースがあったため、「+」や「*」に置き換えているものがあります。</p> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.oricon.co.jp/">ORICON NEWS</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://mantan-web.jp/">MANTANWEB(まんたんウェブ)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://nikkan-spa.jp/">日刊SPA!</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://wpb.shueisha.co.jp/">週プレNEWS</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.narinari.com/">ナリナリドットコム</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://news.mynavi.jp/">マイナビニュース</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.cinra.net/">CINRA.NET</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://rockinon.com/">rockinon.com(ロッキング・オン ドットコム)</a></li> </ul> <h3 id="ライフスタイル"><a href="#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB">ライフスタイル</a></h3> <pre><code>(nanapi.jp OR enuchi.jp OR locari.jp OR sheage.jp) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://nanapi.jp/">nanapi</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://enuchi.jp/">えんウチ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://locari.jp/">LOCARI</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://sheage.jp/">Sheage</a></li> </ul> <h3 id="女性向けメディア"><a href="#%E5%A5%B3%E6%80%A7%E5%90%91%E3%81%91%E3%83%A1%E3%83%87%E3%82%A3%E3%82%A2">女性向けメディア</a></h3> <pre><code>(men-joy.jp OR mainichikirei.jp OR otajo.jp OR youpouch.com OR mdpr.jp OR japan.techinsight.jp) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.men-joy.jp/">Menjoy!</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://mainichikirei.jp/">毎日キレイ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://otajo.jp/">オタ女</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://youpouch.com/">Pouch</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://mdpr.jp/">モデルプレス</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://japan.techinsight.jp/">Techinsight</a></li> </ul> <h3 id="マーケティング"><a href="#%E3%83%9E%E3%83%BC%E3%82%B1%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">マーケティング</a></h3> <pre><code>(liginc.co.jp OR bazubu.com OR ferret-plus.com OR seojapan.com OR webtan.impress.co.jp OR liskul.com OR gaiax-socialmedialab.jp OR seolaboratory.jp OR smmlab.jp OR dmlab.jp OR promonista.com OR cont+hub.com) filter:links lang:ja </code></pre> <p>※半角ハイフン「-」がURLに含まれると、うまく検索できないケースがあったため、「+」や「*」に置き換えているものがあります。</p> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://liginc.co.jp/">LIG</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://bazubu.com/">バズ部</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://ferret-plus.com/">ferret</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://www.seojapan.com/blog/">SEO Japan</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://webtan.impress.co.jp/">Web担当者Forum</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://liskul.com/">LISKUL</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://gaiax-socialmedialab.jp/">ソーシャルメディアラボ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://seolaboratory.jp/">SEOラボ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://smmlab.jp/">SMMLab</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dmlab.jp/">デジタルマーケティングラボ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://promonista.com/">プロモニスタ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cont-hub.com/">コンテンツハブ</a></li> </ul> <h3 id="スタートアップ"><a href="#%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88%E3%82%A2%E3%83%83%E3%83%97">スタートアップ</a></h3> <pre><code>(japan.cnet.com OR jp.techcrunch.com OR thebridge.jp OR techwave.jp OR techable.jp OR thestartup.jp OR turnyourideasintoreality.com) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://japan.cnet.com/">CNET Japan</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://jp.techcrunch.com/">TechCrunch Japan</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://thebridge.jp/">THE BRIDGE</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://techwave.jp/">TechWave</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://techable.jp/">TECHABLE</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://thestartup.jp/">THE STARTUP</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://turnyourideasintoreality.com/">TURN YOUR IDEAS INTO REALITY.</a></li> </ul> <h3 id="ブログ"><a href="#%E3%83%96%E3%83%AD%E3%82%B0">ブログ</a></h3> <pre><code>(hatenablog.com OR hatenablog.jp OR hateblo.jp OR hatenadiary.com OR hatenadiary.jp OR ameblo.jp OR blog.livedoor.jp OR medium.com OR blogos.com OR blogger.com OR exblog.jp OR cocolog-nifty.com) filter:links lang:ja </code></pre> <p>以下ブログのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://hatenablog.com/">はてなブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://ameblo.jp/">アメーバブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://blog.livedoor.com/">ライブドアブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://medium.com/">Medium</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://blogos.com/">BLOGOS</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.blogger.com/">Blogger</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.exblog.jp/">エキサイトブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://www.cocolog-nifty.com/">ココログ</a></li> </ul> <h3 id="イラスト"><a href="#%E3%82%A4%E3%83%A9%E3%82%B9%E3%83%88">イラスト</a></h3> <pre><code>(#pixiv OR pixivision) filter:images lang:ja </code></pre> <p>以下ワードのリンクが含まれたツイートを取得します。</p> <ul> <li>#pixiv</li> <li>pixivision</li> </ul> <h3 id="いらすとや"><a href="#%E3%81%84%E3%82%89%E3%81%99%E3%81%A8%E3%82%84">いらすとや</a></h3> <pre><code>irasutoya.com filter:images lang:ja </code></pre> <p>以下サイトの画像が含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.irasutoya.com/">いらすとや</a></li> </ul> <h3 id="漫画"><a href="#%E6%BC%AB%E7%94%BB">漫画</a></h3> <pre><code>(mangaz.com OR sukima.me OR bookstore.yahoo.co.jp OR comic-walker.com OR manga-bang.com OR #pixivコミック OR manga-zero.com OR cmoa.jp OR sokuyomi.jp OR renta.papy.co.jp OR mangabox.me OR comico.jp OR dokuha.jp OR comic-meteor.jp OR mangag.com OR comic-polaris.jp) </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.mangaz.com/">マンガ図書館Z</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.sukima.me/">スキマ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://bookstore.yahoo.co.jp/">Yahoo!ブックストア</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://comic-walker.com/">コミックウォーカー</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://manga-bang.com/">マンガBANG!</a></li> <li>#pixivコミック</li> <li><a target="_blank" rel="nofollow noopener" href="https://manga-zero.com/">マンガZERO</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.cmoa.jp/">コミックシーモア</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://sokuyomi.jp/">ソク読み</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://renta.papy.co.jp/">Renta!</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.mangabox.me/">マンガボックス</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.comico.jp/">comico</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://dokuha.jp/">マンガ読破!EX</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://comic-meteor.jp/">COMICメテオ</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://mangag.com/">マンガごっちゃ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://comic-polaris.jp/">COMICポラリス</a></li> </ul> <h3 id="本"><a href="#%E6%9C%AC">本</a></h3> <pre><code>(bookmeter.com OR booklog.jp OR honz.jp OR sinkan.jp OR flierinc.com OR bijodoku.com OR bizpow.bizocean.jp OR bukupe.com OR bookvinegar.jp OR book-smart.jp OR booklog.kinokuniya.co.jp) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://bookmeter.com/">読書メーター</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://booklog.jp/">ブクログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://honz.jp/">HONZ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.sinkan.jp/">新刊JP</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.flierinc.com/">flier(フライヤー)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://bijodoku.com/">美女読書</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://bizpow.bizocean.jp/">Bizpow</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://bukupe.com/">ブクペ</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://bookvinegar.jp/">bookvinegar(ブックビネガー)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://book-smart.jp/">BOOK-SMART</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://booklog.kinokuniya.co.jp/">書評空間::紀伊國屋書店</a></li> </ul> <h3 id="アニメ/ドラマ/映画"><a href="#%E3%82%A2%E3%83%8B%E3%83%A1%2F%E3%83%89%E3%83%A9%E3%83%9E%2F%E6%98%A0%E7%94%BB">アニメ/ドラマ/映画</a></h3> <pre><code>(gyao.yahoo.co.jp OR anime.nicovideo.jp OR abema.tv OR fod.fujitv.co.jp OR cu.ntv.co.jp OR videomarket.jp OR tver.jp OR freshlive.tv) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://gyao.yahoo.co.jp/">GYAO!</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://anime.nicovideo.jp/">Nアニメ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://abema.tv/">AmebaTV</a></li> <li><a target="_blank" rel="nofollow noopener" href="http://fod.fujitv.co.jp/s/">FOD</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cu.ntv.co.jp/">日テレオンデマンド</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.videomarket.jp/">VideoMarket</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tver.jp/">TVer</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://freshlive.tv/">FRESH LIVE</a></li> </ul> <h3 id="音楽"><a href="#%E9%9F%B3%E6%A5%BD">音楽</a></h3> <pre><code>spotify.com filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.spotify.com/">Spotify</a></li> </ul> <h3 id="アプリ"><a href="#%E3%82%A2%E3%83%97%E3%83%AA">アプリ</a></h3> <pre><code>(itunes.apple.com OR play.google.com) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://itunes.apple.com/">iTunes</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://play.google.com/store">Google Play</a></li> </ul> <h3 id="料理"><a href="#%E6%96%99%E7%90%86">料理</a></h3> <pre><code>(cookpad.com OR kurashiru.com) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://cookpad.com/">クックパッド</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.kurashiru.com/">kurashiru(クラシル)</a></li> </ul> <h3 id="飲食店"><a href="#%E9%A3%B2%E9%A3%9F%E5%BA%97">飲食店</a></h3> <pre><code>(tabelog.com OR gnavi.co.jp OR retty.me OR hotpepper.jp OR hitosara.com) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://tabelog.com/">食べログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.gnavi.co.jp/">ぐるなび</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://retty.me/">Retty</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.hotpepper.jp/">ホットペッパーグルメ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://hitosara.com/">ヒトサラ</a></li> </ul> <h3 id="イベント"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88">イベント</a></h3> <pre><code>(doorkeeper.jp OR connpass.com OR peatix.com OR atnd.org) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.doorkeeper.jp/">Doorkeeper</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://connpass.com/">connpass</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://peatix.com/">Peatix</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://atnd.org/">ATND</a></li> </ul> <h3 id="まとめサイト"><a href="#%E3%81%BE%E3%81%A8%E3%82%81%E3%82%B5%E3%82%A4%E3%83%88">まとめサイト</a></h3> <pre><code>(togetter.com OR matome.naver.jp) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://togetter.com/">Togetter</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://matome.naver.jp/">NAVERまとめ</a></li> </ul> <h3 id="プレスリリース"><a href="#%E3%83%97%E3%83%AC%E3%82%B9%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9">プレスリリース</a></h3> <pre><code>prtimes.jp filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://prtimes.jp/">PR TIMES</a></li> </ul> <h3 id="はてブ"><a href="#%E3%81%AF%E3%81%A6%E3%83%96">はてブ</a></h3> <pre><code>b.hatena.ne.jp filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://b.hatena.ne.jp/">はてなブックマーク</a></li> </ul> <h3 id="NewsPicks"><a href="#NewsPicks">NewsPicks</a></h3> <pre><code>newspicks.com filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://newspicks.com/">NewsPicks</a></li> </ul> <h3 id="note / cakes"><a href="#note+%2F+cakes">note / cakes</a></h3> <pre><code>(note.mu OR cakes.mu) filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://note.mu/">note</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://cakes.mu/">cakes</a></li> </ul> <h3 id="Voicy"><a href="#Voicy">Voicy</a></h3> <pre><code>voicy.jp filter:links lang:ja </code></pre> <p>以下メディアのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://voicy.jp/">Voicy</a></li> </ul> <h3 id="bouncy"><a href="#bouncy">bouncy</a></h3> <pre><code>bouncy.news filter:videos lang:ja </code></pre> <p>以下メディアの動画が含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="http://bouncy.news/">bouncy</a></li> </ul> <h3 id="YouTube"><a href="#YouTube">YouTube</a></h3> <pre><code>(youtu.be OR youtube.com) filter:links lang:ja </code></pre> <p>以下メディアの動画が含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.youtube.com/">YouTube</a></li> </ul> <h3 id="TikTok"><a href="#TikTok">TikTok</a></h3> <pre><code>TikTok filter:videos lang:ja </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>TikTok</li> </ul> <h3 id="Amazon"><a href="#Amazon">Amazon</a></h3> <pre><code>(amazon.co.jp OR amzn.to) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.amazon.co.jp/">Amazon</a></li> </ul> <h3 id="bosyu"><a href="#bosyu">bosyu</a></h3> <pre><code>bosyu.me filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://bosyu.me/">bosyu</a></li> </ul> <h3 id="エンジニア求職"><a href="#%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E6%B1%82%E8%81%B7">エンジニア求職</a></h3> <pre><code>#hiyokonitsuduke OR #Twitter転職 lang:ja </code></pre> <p>以下ワードのツイートを取得します。</p> <ul> <li>#hiyokonitsuduke</li> <li>#Twitter転職</li> </ul> <h3 id="GitHub"><a href="#GitHub">GitHub</a></h3> <pre><code>(github.com OR c) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/">GitHub</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://pages.github.com/">GitHub Pages</a></li> </ul> <h3 id="Revue"><a href="#Revue">Revue</a></h3> <pre><code>getrevue.co filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.getrevue.co/">Revue</a></li> </ul> <h3 id="技術論文"><a href="#%E6%8A%80%E8%A1%93%E8%AB%96%E6%96%87">技術論文</a></h3> <pre><code>arxiv.org filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://arxiv.org/">arXiv.org</a></li> </ul> <h3 id="技術記事"><a href="#%E6%8A%80%E8%A1%93%E8%A8%98%E4%BA%8B">技術記事</a></h3> <pre><code>(qiita.com OR qrunch.io OR #Crieit) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/">Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qrunch.io/">Qrunch</a></li> <li>#Crieit</li> </ul> <h3 id="スライド"><a href="#%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%89">スライド</a></h3> <pre><code>(slideshare.net OR speakerdeck.com) filter:links lang:ja </code></pre> <p>以下サイトのリンクが含まれたツイートを取得します。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.slideshare.net/">SlideShare</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://speakerdeck.com/">Speaker Deck</a></li> </ul> <h3 id="Advent Calendar"><a href="#Advent+Calendar">Advent Calendar</a></h3> <pre><code>Advent Calendar filter:links lang:ja </code></pre> <p>以下ワードのリンクが含まれたツイートを取得します。</p> <ul> <li>Advent Calendar</li> </ul> <h3 id="やってみた"><a href="#%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F">やってみた</a></h3> <pre><code>(やってみた OR やってみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>やってみた</li> <li>やってみました</li> </ul> <h3 id="歌ってみた"><a href="#%E6%AD%8C%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F">歌ってみた</a></h3> <pre><code>(歌ってみた OR 歌ってみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>歌ってみた</li> <li>歌ってみました</li> </ul> <h3 id="演奏してみた"><a href="#%E6%BC%94%E5%A5%8F%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F">演奏してみた</a></h3> <pre><code>(演奏してみた OR 演奏してみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>演奏してみた</li> <li>演奏してみました</li> </ul> <h3 id="弾いてみた"><a href="#%E5%BC%BE%E3%81%84%E3%81%A6%E3%81%BF%E3%81%9F">弾いてみた</a></h3> <pre><code>(弾いてみた OR 弾いてみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>弾いてみた</li> <li>弾いてみました</li> </ul> <h3 id="叩いてみた"><a href="#%E5%8F%A9%E3%81%84%E3%81%A6%E3%81%BF%E3%81%9F">叩いてみた</a></h3> <pre><code>(叩いてみた OR 叩いてみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>叩いてみた</li> <li>叩いてみました</li> </ul> <h3 id="踊ってみた"><a href="#%E8%B8%8A%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F">踊ってみた</a></h3> <pre><code>(踊ってみた OR 踊ってみました) filter:videos lang:ja -filter:verified </code></pre> <p>以下ワードの動画が含まれたツイートを取得します。</p> <ul> <li>踊ってみた</li> <li>踊ってみました</li> </ul> <h3 id="描いてみた"><a href="#%E6%8F%8F%E3%81%84%E3%81%A6%E3%81%BF%E3%81%9F">描いてみた</a></h3> <pre><code>(描いてみた OR 描いてみました) filter:images lang:ja -filter:verified </code></pre> <p>以下ワードの画像が含まれたツイートを取得します。</p> <ul> <li>描いてみた</li> <li>描いてみました</li> </ul> <h3 id="作ってみた"><a href="#%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F">作ってみた</a></h3> <p>```(作ってみた OR 作ってみました OR 作りました OR つくってみた OR つくってみました OR つくりました) (filter:links OR filter:media) lang:ja -filter:verified</p> <pre><code><br />以下ワードのリンク、画像、動画が含まれたツイートを取得します。 * 作ってみた * 作ってみました * 作りました * つくってみた * つくってみました * つくりました ### リリースした </code></pre> <p>(リリースしました OR 開発しました OR Webサービス OR webサービス) (filter:links OR filter:media) lang:ja -filter:verified<br /> ```</p> <p>以下ワードのリンク、画像、動画が含まれたツイートを取得します。</p> <ul> <li>リリースしました</li> <li>開発しました</li> <li>Webサービス</li> <li>webサービス</li> </ul> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回は各ジャンルごとに話題のツイートを収集し、ランキングで表示するところまで実装しました。</p> <p><img width="400" alt="twirepo_screenshot1.png" src="https://qiita-image-store.s3.amazonaws.com/0/273248/657076e1-a42b-3f68-2ccf-6d7cdef4e9eb.png"></p> <p>作ってみて感じたのは、ジャンルによってはまだまだ収集したツイートの精度があまり良くなかったことです。<br /> ツイートにつけられたリツイート数やいいね数での評価だけでは、自分の望む精度の高い情報にはまだ及びませんでした。</p> <p>今後の課題としては、情報の精度をリツイート数やいいね数といったツイートの評価の他に「ツイレポ」のサービス内で改善できそうな点を考えていきたいと思います。<br /> また、今回の実装でリツイート数やいいね数は収集してるので、これを使ってツイートがバズる要因や傾向なども分析していければと考えています。</p> <p>なお、今回、ジャンル分けやその収集対象は自分の独断と偏見で決めてしまっていますので、これを入れたらいいんじゃないか、これを入れて欲しいなどのご要望がありましたら、「ツイレポ」のご意見・ご要望よりご連絡を頂ければ幸いです。</p> <p><img width="406" alt="twirepo_screenshot_contact.png" src="https://qiita-image-store.s3.amazonaws.com/0/273248/0f9382db-b2ce-04ed-9923-339315a55781.png"></p> <p>本サービスに少しでも興味をもって頂けましたら、ぜひ 本記事のいいね や Twitterのフォロー、いいねやリツイートをして頂けると嬉しいです!</p> <ul> <li>Twitter: <a target="_blank" rel="nofollow noopener" href="https://twitter.com/2zn01">@2zn01</a></li> </ul> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">【お知らせ】ツイッターを活用したエンジニアの情報収集を助けるキュレーションサービス <a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/%E3%83%84%E3%82%A4%E3%83%AC%E3%83%9D?src=hash&ref_src=twsrc%5Etfw">#ツイレポ</a> をリリースしました!🎉<a target="_blank" rel="nofollow noopener" href="https://t.co/oFiPYwT34N">https://t.co/oFiPYwT34N</a>時間がない中でどう情報収集したらよいか困っていたので、話題の情報のみをサクッと確認できるように作りました。ぜひ使ってみてください~! <a target="_blank" rel="nofollow noopener" href="https://t.co/DHVGfBC0BQ">pic.twitter.com/DHVGfBC0BQ</a></p>— 2z / AIメーカー開発 (@2zn01) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/2zn01/status/1069286642765062144?ref_src=twsrc%5Etfw">2018年12月2日</a></blockquote> <p>ぜひこのサービスを使って情報収集してみてください!</p> <p>■ツイレポ<br /> <a target="_blank" rel="nofollow noopener" href="https://twirepo.com/">https://twirepo.com/</a></p> <hr /> <p>明日は<a target="_blank" rel="nofollow noopener" href="https://twitter.com/Fujiyama_Yuta">@Fujiyama_Yuta</a>さんの「今作っているサービスを、この日までにリリースして記事を書きます!」です。</p> <p>お楽しみに!</p> 2z@AIメーカー tag:crieit.net,2005:PublicArticle/14548 2018-09-21T20:04:31+09:00 2018-10-18T07:36:18+09:00 https://crieit.net/posts/Elixir-ExTwitter ElixirのExTwitterでツイートする <p>Elixirには<a target="_blank" rel="nofollow noopener" href="https://github.com/parroty/extwitter">ExTwitter</a>というTwitter用のライブラリがある。<br /> それでツイートしてみた。</p> <h2 id="設定"><a href="#%E8%A8%AD%E5%AE%9A">設定</a></h2> <p>ExTwitterの説明通り。access_tokenとaccess_token_secretはユーザーのものを使うので空にしている。</p> <h2 id="ツイート"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88">ツイート</a></h2> <pre><code class="elixir"> def tweet(token, secret, body) do ex_twitter_configure(token, secret) ExTwitter.API.Tweets.update(body) end defp ex_twitter_configure(token, secret) do conf = [ consumer_key: Application.get_env(:extwitter, :oauth)[:consumer_key], consumer_secret: Application.get_env(:extwitter, :oauth)[:consumer_secret], access_token: token, access_token_secret: secret ] ExTwitter.configure(:process, conf) end </code></pre> <p>ログイン中のユーザーのアクセストークンを使うために、API実行前に設定をしている。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14508 2018-08-19T00:22:45+09:00 2018-10-22T13:16:57+09:00 https://crieit.net/posts/Twitter-User-Streams TwitterのUser Streams廃止と仮想通貨投げ銭まとめ <p>2018年8月23日にTwitterのUser Streams APIが廃止されます。それに伴い色々なサードパーティのアプリケーションが終了したり変更を余儀なくされています。ツイートで仮想通貨の投げ銭ができるサービスがいくつかありますが、現在どの様になっているのかまとめてみました。</p> <h2 id="MonaCoin(モナコイン)"><a href="#MonaCoin%EF%BC%88%E3%83%A2%E3%83%8A%E3%82%B3%E3%82%A4%E3%83%B3%EF%BC%89">MonaCoin(モナコイン)</a></h2> <p>モナコインはtipmonaというアカウントのコマンドで送金ができます。継続予定のようですが、対応が間に合わず一旦停止となるようです。再開され次第公式アカウントで報告があると思われます。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">企業様よりこちらの新APIに対応するためご協力いただけることは決定しており、そちらの面での問題は解決しているのですがTwitterのAPI審査が間に合わず今すぐには継続することが困難です。審査が完了しAPIが利用可能になり次第速やかに対応、tipmonaを再開いたします。</p>— モナコインちゃんbot*一時停止/詳細は固定ツイ* (@tipmona) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/tipmona/status/1030122995421859841?ref_src=twsrc%5Etfw">2018年8月16日</a></blockquote> <h2 id="Bitcoin(ビットコイン)"><a href="#Bitcoin%EF%BC%88%E3%83%93%E3%83%83%E3%83%88%E3%82%B3%E3%82%A4%E3%83%B3%EF%BC%89">Bitcoin(ビットコイン)</a></h2> <p>ビットコインはCoinTipというサービスで送金ができます。チップ機能以外は廃止したようです。ただ、たいして細かいツイートは無いようですので、おそらく専用のアプリケーション上から主な操作を行うためあまりツイート上の機能にこだわっていないのかもしれません。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">こんにちは、<a target="_blank" rel="nofollow noopener" href="https://twitter.com/hashtag/CoinTip?src=hash&ref_src=twsrc%5Etfw">#CoinTip</a> からのお知らせです。Twitterアカウントの自動化に関するルールの変更により、返信系のコマンド(balance, deposit)停止をし、チップのお知らせも削除致しました。チップがまだできますので、引き続きよろしくお願いします。</p>— CoinTip (@cointip_jp) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/cointip_jp/status/1022309672466898944?ref_src=twsrc%5Etfw">2018年7月26日</a></blockquote> <h2 id="Bitcoin Cash(ビットコインキャッシュ)"><a href="#Bitcoin+Cash%EF%BC%88%E3%83%93%E3%83%83%E3%83%88%E3%82%B3%E3%82%A4%E3%83%B3%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%EF%BC%89">Bitcoin Cash(ビットコインキャッシュ)</a></h2> <p>bch_tipというアカウントのコマンドがありますが、運営は終了となるようです。残高が残っている方は引き出しておきましょう。</p> <h2 id="NEM(ネム)"><a href="#NEM%EF%BC%88%E3%83%8D%E3%83%A0%EF%BC%89">NEM(ネム)</a></h2> <p>NEMはtipnemというアカウントのコマンドがあります。こちらのアカウントはゆるい感じでよく分からなかったのですが、User Streamsについて言及後、システムを更新したという言及があるので無事動いているのかもしれません。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">ユーザーストリーム死んだけど生きてます👋</p>— NEM Mainnet tipbot (@tipnem) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/tipnem/status/1030236637136019456?ref_src=twsrc%5Etfw">2018年8月16日</a></blockquote> <h2 id="XRP(リップル)"><a href="#XRP%EF%BC%88%E3%83%AA%E3%83%83%E3%83%97%E3%83%AB%EF%BC%89">XRP(リップル)</a></h2> <p>tipxrpというアカウントのコマンドがあります。User Streams APIの廃止に伴い、何らかの対応は行われているようです。ただ、不具合が出ているとの報告もあるようですので、使う場合はちょっと気にしながら利用した方が良いかもしれません。(固定ツイートを見ると丁度1年前くらいにpre-alphaと書かれていますがまだそうなのでしょうか?)</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>廃止になるサービスも、縮小しつつ継続するサービスもあるようです。ツイートで仮想通貨を投げ銭できると色々と使い道が考えられるので、是非頑張って欲しいものです。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14475 2018-06-29T00:08:42+09:00 2018-10-25T17:17:55+09:00 https://crieit.net/posts/bosyu bosyuの募集一覧を勝手に作ってみた(機械学習解説付き) <p>bosyu.meの一覧ページを作成してみました。ちなみに僕はbosyuの運営とは何の関わりもありません。</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>ちょっと前に<a target="_blank" rel="nofollow noopener" href="https://bosyu.me/">bosyu</a>という募集を行うことができるサービスがリリースされました。応募が来るとサービス上で簡単一覧できる便利なサービスです。</p> <p>ただ、Twitter上で自分のフォロワーさん宛に募集を行う形での利用を想定されて作られている感じのため、募集の一覧ページなどがありません。</p> <p>そのため、募集一覧ページを勝手に作ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://bosyu.alphabrend.com/">bosyu.meの募集一覧</a></p> <p>ただ、ただほんとに一覧するだけだとTwitter上で<code>#bosyu</code>というハッシュタグを使って見ればいいだけなので、独自の機能としてカテゴリ分類機能を追加し、カテゴリ毎の募集を見られるようにしてみました。(カテゴリの自動分類方法詳細は後述)</p> <h2 id="環境"><a href="#%E7%92%B0%E5%A2%83">環境</a></h2> <ul> <li>PHP7.2</li> <li>Laravel5.6</li> <li>Vue</li> <li>Vue Material</li> <li>Twitter API</li> <li>Google Compute Engine(f1-micro)</li> </ul> <h2 id="ツイートの取得"><a href="#%E3%83%84%E3%82%A4%E3%83%BC%E3%83%88%E3%81%AE%E5%8F%96%E5%BE%97">ツイートの取得</a></h2> <p>1時間ごとくらいにツイートの保存処理を行っています。</p> <p>こんな感じで初期化&取得しています。特に複雑な点はなく、基本通りそのままです。URLとハッシュタグで検索しているので、Twitter上での検索よりは精度が高いと思います。TwitterではURLで検索しても検索結果が出てきませんのでここはAPIのみの利点です。</p> <pre><code class="php"> $connection = new TwitterOAuth( env('TWITTER_CONSUMER_KEY'), env('TWITTER_CONSUMER_SECRET'), env('TWITTER_ACCESS_TOKEN'), env('TWITTER_ACCESS_TOKEN_SECRET') ); $params = [ 'q' => 'bosyu.me/users #bosyu', 'count' => 100, ]; if ($maxId = Tweet::getMaxId()) { $params['since_id'] = $maxId; } $tweets = $connection->get("search/tweets", $params); </code></pre> <p>下記のような感じでリツイートや既に取得済みのツイートなどをフィルタリングしています。</p> <pre><code class="php"><br /> public static function isAvailableStatus($status) { if (strpos($status->text, '#bosyu') === false) { return false; } if (!empty($status->retweeted_status)) { return false; } $bosyuId = self::getBosyuId($status); if (!$bosyuId) { return false; } return true; } </code></pre> <h2 id="検索フォーム"><a href="#%E6%A4%9C%E7%B4%A2%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0">検索フォーム</a></h2> <p>Vue Materialで表示を行っているので、検索フォームもVue Materialだけで実装しました。</p> <p><a href="https://crieit.now.sh/upload_images/53f19f7a13d95d01582e4f1298411d925b361ae6a3bf0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/53f19f7a13d95d01582e4f1298411d925b361ae6a3bf0.png?mw=700" alt="8ce5b9af-16f5-d75a-536d-b62a6e57971e.png" /></a></p> <p>カテゴリは選択たらすぐに画面が切り替わるのですが、Date Pickerはどうも選択時のcallbackが無いようだったので、仕方なく検索ボタンを押してもらう形にしました。デザインは綺麗なのに肝心な機能が抜けていたりして微妙ですね。誰か本家にプルリクエストを送って実装してください。</p> <p>別にフォームまるまるコンポーネント化しようとは思ってなかったのですが、上記理由でv-modelを利用した値の取得方法しかなかったため、コンポーネント化されています。全部v-modelで管理してボタンで<code>location.href</code>するだけのシンプルなコンポーネントです。</p> <pre><code class="html"><template> <div> <div class="row"> <div class="col"> <md-field> <md-select v-model="currentCategoryId" name="category" id="category" placeholder="Category" @md-selected="selectCategory"> <md-option :value="0">Category</md-option> <md-option v-for="category in categories" :value="category.id" :key="category.id"><span>{</span><span>{</span>category.name<span>}</span><span>}</span></md-option> <md-option :value="-1">その他</md-option> </md-select> </md-field> </div> </div> <div class="row"> <div class="col-5 col"> <md-datepicker v-model="currentDate" @md-selected="console.log('changed')" /> </div> <div class="col-5 col"> <md-field> <label>Keyword</label> <md-input v-model="currentKeyword"></md-input> </md-field> </div> <div class="col-2 col"> <md-button class="md-icon-button md-raised md-primary" @click="search()"> <md-icon>search</md-icon> </md-button> </div> </div> </div> </template> <script> import * as moment from 'moment'; export default { props: { categories: { type: Array, required: true }, categoryId: { type: Number, default: 0 }, date: { type: String, default: null }, keyword: { type: String, default: '' } }, data() { return { currentCategoryId: this.categoryId, currentDate: this.date === null ? null : moment(this.date).toDate(), currentKeyword: this.keyword } }, methods: { selectCategory(categoryId) { this.currentCategoryId = categoryId; this.search(); }, search() { let parts = []; if (this.currentCategoryId !== 0) { parts.push(`category_id=${this.currentCategoryId}`); } if (this.currentDate !== null) { parts.push('date=' + moment(this.currentDate).format('YYYY-MM-DD')); } const keyword = this.currentKeyword.trim(); if (keyword !== '') { parts.push('keyword=' + encodeURIComponent(keyword)); } location.href = '/?' + parts.join('&'); } } } </script> </code></pre> <h2 id="機械学習によるカテゴリの自動分類"><a href="#%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%81%AB%E3%82%88%E3%82%8B%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E3%81%AE%E8%87%AA%E5%8B%95%E5%88%86%E9%A1%9E">機械学習によるカテゴリの自動分類</a></h2> <p>今回作るサービスの肝です。これがなければTwitterで情報を見ることができるのでほとんど意味がなくなってしまいます。</p> <p>とりあえずざっと思いついた流れは下記のようなものです。</p> <ul> <li>カテゴリをいくつか作る。</li> <li>あらかじめツイートを集めて学習させる。</li> <li>機械学習の結果を用いて取得したツイートの本文でカテゴリ分けする。</li> </ul> <p>この機能はそのまま <strong>クラス分類(Classification)</strong> というらしいです。機械学習としては恐らくよく用いられる分類だと思いますので、既にいくつも簡単に実装できるAPIやライブラリなどが数多く見つかりました。せっかくなので見つけて検討した順番にここで紹介していきます。</p> <h3 id="Google Cloud Natural Language API"><a href="#Google+Cloud+Natural+Language+API">Google Cloud Natural Language API</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/natural-language/?hl=ja">Google Cloud Natural Language</a></p> <p>とりあえずGoogleなら何かあるだろ、と思って調べたら全くそのままのものがありました。これの<code>classifyText</code>メソッドを利用した「コンテンツの分類」という機能になります。しかもこれは学習させる必要がなく、予めGoogle側で定義されているカテゴリに分けてくれるというすぐれものです。しかもツイートであれば3万件ほどまで無料(多分)。</p> <p>しかし試してみましたが、下記の問題がありました。</p> <ul> <li>日本語対応してない</li> <li>短い文章は無理</li> </ul> <p>ということで一番手軽だったのですが、これは諦めることになりました…。</p> <h3 id="A3RT Text Classification API"><a href="#A3RT+Text+Classification+API">A3RT Text Classification API</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://a3rt.recruit-tech.co.jp/product/textClassificationAPI/">A3RT Text Classification API</a></p> <p>よくわからないのですがRecruitが無料で公開しているAPIです。こんなすごいものを無料で公開し続けるというのはいまいち良く分からないです。APIになっているのでインフラも無料では済まないでしょうし。どうなってるんだろう…。</p> <p>機能的にはこれで問題なかったのですが、とりあえず最初にGoogleのAPIを知ってしまったので学習が要らないAPIを引き続き探しました。このAPIはデフォルトで求人関連のモデルが入っているので、その他の分類を行うためには自分でモデルを作る必要があります(簡単ですがデータを用意する必要があります)。</p> <h3 id="Watson Natural Language Classifier"><a href="#Watson+Natural+Language+Classifier">Watson Natural Language Classifier</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://www.ibm.com/watson/jp-ja/developercloud/nl-classifier.html">Watson Natural Language Classifier (自然言語分類)</a></p> <p>これもA3RTと一緒だと思います。IBMが公開しているもので有料ですが無料枠があります。A3RTと同じ理由で次を探しました。</p> <h3 id="fastText"><a href="#fastText">fastText</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/facebookresearch/fastText">facebookresearch/fastText: Library for fast text representation and classification</a></p> <p>こちらはfacebookが公開しているもので、A3RT、Watsonと同じクラス分類の機能があります。</p> <p>なんかもうこれでいいんじゃないかな…と思ってこれを試しました。色々探しましたがGoogleのもののように良い感じに最初からカテゴリ分けされてるっぽいものはなさそうですし、ツイートであれば学習データを集めるのは特に難しいことではないし、学習させるというのも大事なことだと思ったためです。</p> <p>また、下記のようなわかりやすい記事を見つけたのも試そうと思ったきっかけです。<br /> <a target="_blank" rel="nofollow noopener" href="http://tech.wonderpla.net/entry/2017/10/10/110000">機械学習で大量のテキストをカテゴリ別に分類してみよう!</a></p> <p>具体的には</p> <ol> <li>学習データのテキストファイルを作成</li> <li>テキストをモデルに変換</li> <li>モデルによりテキストを分類</li> </ol> <p>という感じで非常に簡単をクラス分類を行うことができるライブラリになっており、割とすんなり実装できました。</p> <p>ちなみに僕は結局途中からpipで準備するのが面倒になったのでpythonもやめて直接コマンドを叩く形で実装しました。</p> <h4 id="モデル作成"><a href="#%E3%83%A2%E3%83%87%E3%83%AB%E4%BD%9C%E6%88%90">モデル作成</a></h4> <pre><code class="sh">./fasttext supervised -input data.txt -output model </code></pre> <h4 id="クラス分類実行"><a href="#%E3%82%AF%E3%83%A9%E3%82%B9%E5%88%86%E9%A1%9E%E5%AE%9F%E8%A1%8C">クラス分類実行</a></h4> <pre><code class="sh">./fasttext predict-prob model.bin 分類する文章が保存されているテキストファイルのパス </code></pre> <h4 id="試してみた"><a href="#%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F">試してみた</a></h4> <p>なんか、うまくいきませんでした。ツイートなので文章が全体的に短すぎるのか、何時間かしかかけずに集めたデータが少なすぎたのか、おかしなカテゴリに入ってしまうものが多発しました。</p> <p>ということでここまで調べて実装しましたが<strong>機械学習でカテゴリ分けをするのはやめました</strong>。</p> <h2 id="実際に行ったカテゴリ分けの方法"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AB%E8%A1%8C%E3%81%A3%E3%81%9F%E3%82%AB%E3%83%86%E3%82%B4%E3%83%AA%E5%88%86%E3%81%91%E3%81%AE%E6%96%B9%E6%B3%95">実際に行ったカテゴリ分けの方法</a></h2> <p>ツイートに含まれるキーワードによりカテゴリ分けをする方法です。ひとつのカテゴリにいくつでもキーワードを設定できるようにしました。非常に的確にカテゴリ分類ができるようになりました。しかも非常に簡単です。そして非常にダサいです。</p> <p>でも、今回の場合は募集ツイートに限るものだし、さほどバリエーションがあるとは思えません。文章も短く、精度も低くなりがちです。こういう場合にはわざわざ機械学習を導入せず、シンプルな方法を選択することも必要な場面はあると思います。今回はそれで良さそうな気がしています。分類されないものは全部「その他」カテゴリに入れています。</p> <p>よろしければ実際に見てみてください。本家が一覧ページを作ったら消え去る運命のサイトです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://bosyu.alphabrend.com/">bosyu.meの募集一覧</a></p> だら@Crieit開発者