Google Apps Script (以下、GAS)で、困ったことがあったので備忘録として残しておこうと思います。
やろうとしたこと
特定ハッシュタグにおける、ツイートに書いてあるリンクを集めようとしていました。 そのリンクは、特定のドメインのみでフィルタリングしたいとも思っていました。 これらをRESTful APIとして提供したかったので、手軽に作れるGASで作ろうと考えていました。
取り組んでみたこと
Twitterに書くリンクは、全て短縮URLになります。
そのため、短縮URLにアクセスし、リダイレクト先のURLを取りに行く必要がありました。
GASでは、リクエストメソッドであるfetchがあります。そのfetchのfollowRedirects
というオプションをfalseにし、responseHeaderのlocationを取ることで、解決(リダイレクト先のURL取得が)できます。
また、1リクエストだけをするfetchでは、直列処理になってしまうため、大変遅いです。 複数リクエストが同時にできるfeatchAllを使うことで、並列処理ができ、パフォーマンスが良いです。 要するに次のようなコードで解決しようと考えていました。
let urlList: Array<string> = ['https://t.co/XXXX', 'https://t.co/YYYY']; const locationList: Array<string> = []; while (true) { const requestList: Array<URLFetchRequest> = urlList.map((url: string) => { return { url: url, method: 'get', followRedirects: false, muteHttpExceptions: true, } }); const responseList: Array<HTTPResponse> = UrlFetchApp.fetchAll(requestList); urlList = []; responseList.forEach((response: HTTPResponse) => { const allHeaders: any = response.getAllHeaders(); const location: string = allHeaders['Location']; if (location) { locationList.push(location); urlList.push(location); } }); if (urlList.length === 0) { break; } } return locationList;
追記 (20200228)
TwitterのAPIレスポンスに urls
がありました。説明はありませんでしたが、Tweetに貼られたリンク(短縮URLと、オリジナルURL)の情報が入るそうです。
"urls": [ { "url": "https://t.co/Rbc9TF2s5X", "expanded_url": "https://twitter.com/i/web/status/1125490788736032770", "display_url": "twitter.com/i/web/status/1…", "indices": [ 117, 140 ] } ]
困ったこと
この手段だと、Locationを1つ1つ辿っていくことになります。
そのため、リダイレクトを自動的に追う( followRedirects: true
)よりも、処理コストが大きいです。まあ、そこは目を瞑ります。
次です。
fetchやfetchAllは、muteHttpExceptions: true
としたとしても、ExceptionErrorが発生してしまいます。
そうすると、例えば1000件のURLをfetchAllした場合、どれが成功で、どれが失敗で、どれが未実施か がわからないというところです。
Promise.allSettled が使えれば、解決できるのかなと思いますが、現状Promiseは使えません。
私が思う解決策としては、
- fetchAllではなく、fetchを使う
- fetchAllでリクエストする件数をいくつかの塊に分ける。(一気にではなく、分ける)
最後に
そもそもなのですが、今回やろうとしたことってGASの良さがないですよね。 GASは、GSuites連携を簡単にできるという良さがあります。
しかし、今回はちょっとしたクローラーを作りたいだけでした。もちろん、GASでも作れると思いますが、いくつかを妥協しないといけなくなります。
もし、そこが妥協できないのであれば、別の手段を検討する必要があります。
教訓
- 表面的
- fetchAllするときは、リダイレクト先URLを取得しない
- 根本的
- 目的に適したツールを選択する
ちなみに、このツールは、並列処理をシンプルにコーティングできるgolangで書き直そうと考えています。