Instagramぶっこ抜きを作った
instagramのページURLを与えると投稿画像、投稿文、ユーザー名を取得するシステム(?)を作った。
当初はphpで作っていたが、勉強がてらGoで書き直してみたら57秒→8秒と約7倍になった出来事を話したい。
結論から言うと、GoとPHPで立てるサーバーの設定が違ったからだった。
勘違いワロタ、、、
デモ
まずはどんなものを作ったか紹介。
実際に使ってみたいという方はこちら
構成
サーバー側でやっていることは単純で、
- cURLする
- 正規表現で情報を抜き出す
- JSONレスポンスとして返す
これだけ。
PHPのソース
エラー処理とか載せると長くなるので割愛。クラス分けとかしていない完全にペラ1のスクリプト。
これをGoで書き直した。
<?php $request = json_decode(file_get_contents('php://input'), true); $urls = $request['URLs']; foreach ($urls as $url) { $json = getJsonByUrl($url); $username = getUsername($json); $post_text = getPostText($json); $image_url = getImageUrl($json); $result = [[ 'Username' => $username, 'ImageURL' => $image_url, 'PostText' => $post_text, 'OrgURL' => $url, ]]; echo json_encode($result); } return; /** * InstagramにcurlしてJSONを取得する */ function getJsonByUrl($url) { $url = preg_replace('/\n|\r|\r\n/', '', $url); $curl_result = fetch($url); preg_match_all('/window._sharedData.*{.*}/', $curl_result, $match); $json_str = str_replace('window._sharedData = ','',$match[0][0]); return json_decode($json_str); } function getImageUrl($json) : string { return $json->entry_data->PostPage[0]->graphql->shortcode_media->display_url; } function getPostText($json) : string { return $json->entry_data->PostPage[0]->graphql->shortcode_media->edge_media_to_caption->edges[0]->node->text; } function getUsername($json) : string { return $json->entry_data->PostPage[0]->graphql->shortcode_media->owner->username; } function fetch(string $url) : string { $ch = curl_init(); $options = [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_CUSTOMREQUEST => 'GET', ]; curl_setopt_array($ch, $options); $resp = curl_exec($ch); curl_close($ch); return $resp; }
Goで書き直してみたら早くなった
phpとgoの比較動画。
ざっくり計算だとPHPが完了までにかかる時間は57秒、Goは8秒で終わった。
なんでこんなに違うのか考察してみる。
やってることはPHPもGoもほぼ同じ。
・サーバー立てる
・httpリクエスト投げる
・正規表現で抜く
・JSON形式に変換して吐き出す
これのどこかでパフォーマンスに大きく差が出ているはず。。
立てたサーバーの設定が違うのが大きな原因
ここで冒頭の結論に戻るのだがlocalhostで立てるサーバーがPHPとGoでは違う。
サーバーを立てるのはそれぞれ下記の方法で立てた。
・phpの場合
$ php -S localhost:9000
・Goの場合
net/http
を使用。main.goに下記を記載。
http.ListenAndServe(":9000", api.MakeHandler())
Chromeのデベロッパーツールで5リクエスト出した時のパフォーマンスを計測してみると以下の結果に。
明らかにレスポンスの挙動が違う。。。
Goでは一気に5レスポンス返ってきているのに対し、phpでは1レスポンス待ってから次の処理が開始されているように見える。
PHPのビルトインサーバーについて
PHPManualに全ての答えが書いてあった。
ビルトインウェブサーバー このウェブサーバーは単一のシングルスレッドプロセスしか実行しないので、 リクエストがブロックされると、PHP アプリケーションはストールします
https://php.net/manual/ja/features.commandline.webserver.php
つまり今回の場合、phpで5リクエスト投げた時2つ目以降の処理はWAITになっていた、ということだった。
Herokuにデプロイして試してみた
ならばと思い、apache等でちゃんとマルチスレッドで動いているサーバーなら実行は早くなるのか試してみた。 下記はHerokuにデプロイして検証したもの。
おお、、、ちゃんと早くなっている。
何回か計測してみると、やはりGoの方が速いがビルトインサーバーでやっていたときよりは段違いに速くなった。
ちなみにherokuのapache2の同時実行数は4でした。(phpinfo()で見れるEVN['WEB_CONCURRENCY']がそれらしい)
じゃあGoの同時実行数はどうなの?
Goでnet/http
でサーバー立てたときの同時実行数がわからない、、、。
ググってもどうも調べ方が悪いのか出てこない。そもそも俺の考え方が違うような気がする。
一応Chromeのデベロッパーツールで見てみたところ、30リクエストまでは同時に実行できていた。
終わり
Goのhttpサーバーについてもうちょっと調べたい。
Goにしただけでめっちゃ速度上がった!と思っていたので少し残念だったが、ワンレスポンスだけ見たら3倍以上Goのほうが早かったので、言語の処理速度もやはり関係はしている。
なによりGoは書いていて楽しいからしばらくハマっていたい。
今の所やりたいことは
- Herokuじゃなくてlambdaで動かす
- 画面のリクエスト分割(1URL1リクエストではなくある程度URLをまとめて1リクエスト)
- テストを書く
ぐらい。割とすぐいけそうな予感はしてる。