Tinderをpythonで自動化したら遅かったからgolangでgoroutine使って書いたらクソ速くなった話

# はじめに
[Daiさん](https://twitter.com/never_be_a_pm?lang=ja)がかなり前にTinderの自動化をpythonでやっていたので、それを参考に僕もpythonでTinderの自動化を実装してみました。

[Pynder PythonでTinderのAPIをいじる|Review of My Life](https://review-of-my-life.blogspot.com/2017/06/get-tinder-api-by-python.html)

あと、以下の記事も参考にしました。

[世界中で大流行のマッチングアプリ、「t○nder」のAPIを用いて遠隔サイバーナ○パをしてみた](https://qiita.com/dq_yosuke_takahashi/items/7be24ea41f2964c9f681)

pythonだと[pynder](https://github.com/charliewolf/pynder)というライブラリーがあるので簡単に実装できます。→[GitHub](https://github.com/Dragon-taro/tinder)

が、１日分のいいねを使おうとするとたしか100人分（間違ってるかも）をいいねすることになるので、なんと26〜27秒もかかってしまいます。
別にそれぐらい良いじゃないかと思うかもしれないのですが、僕は早○漏なのでその30秒弱がどうしても待てませんでした。

そこで、**速い言語を使ってさらに並列処理でいいねしてやろう**と思いました。（サーバーにあまり負荷をかけすぎない程度に）

巷ではGo言語と呼ばれるやつが流行っていてしかもそいつは簡単に並列処理が実装できるのだとか。使うしかない。

というわけで実装していきます。

# Tinder API
Tinderをプログラムするのはpynderだけかと思っていたらそうでもなく普通に[APIのドキュメント](https://gist.github.com/rtt/10403467)もありました。（冷静に考えたらpynderがあるんだから当たり前かw）このAPIをgoで叩いていきます。

ちなみに、facebookのaccess_tokenを使うのですが、これが曲者でけっこう取得するのに苦労します。僕は、[このサイト](https://gist.github.com/taseppa/66fc7239c66ef285ecb28b400b556938)に書いてある方法で取得しました。

しかもこれ2時間で消えちゃうのでけっこう面倒なんですよね、、

# api_tokenの取得
先ほど取得したfacebookのaccess_tokenを使ってtinderのapi_tokenを取得します。

```go:main.go
package main

import (
	"encoding/json"
	"log"
	"time"

	"github.com/Dragon-taro/tinder-go/functions"
	"github.com/Dragon-taro/tinder-go/types"
)

func main() {
	body, err := functions.Http("auth", "", "POST")
	if err != nil {
		log.Fatal(err)
	}
	var user types.User
	if err := json.Unmarshal(body, &user); err != nil {
		log.Fatal(err)
	}
}
```

```go:types/types.go
package types

type User struct {
	User struct {
		ID       string `json:"_id"`
		Name     string `json:"name"`
		APIToken string `json:"api_token"`
	} `json:"user"`
}
```

```go:functions/api.go
ackage functions

import (
	"bytes"
	"io/ioutil"
	"log"
	"net/http"
)

var jsonStr = []byte(`{"facebook_token": "YOUR FACEBOOK TOKEN", "facebook_id": "YOUR FACE BOOK ID"}`)

func Http(path string, token string, method string) ([]byte, error) {
	url := "https://api.gotinder.com/" + path
	req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonStr))
	if err != nil {
		log.Fatal(err)
	}
	body, err := request(req, token)
	return body, err
}

func setHeader(req *http.Request, token string) {
	req.Header.Set("Content-type", "application/json")
	req.Header.Set("User-Agent", "Tinder/3.0.4 (iPhone; iOS 7.1; Scale/2.00)")
}

func request(req *http.Request, token string) ([]byte, error) {
	setHeader(req, token)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)

	return body, err
}
```

これで、`user.User.APIToken`でapi_tokenを取得できます。
長々となってしまったのですが、Headerにfacebookのaccess_tokenをのせて良い感じにリクエストを送って、jsonを良い感じにパースして返しています。

`main.go`で構造体に入れるコードにしたのですが、apiの関数がuserを返す実装にしてもよかったのかなと思いました。
あと、`jsonStr`のハードコーディングはよくないけど、わかりやすいようにここにかいてます。
てかエラー処理が微妙なんですよね、、200以外も余裕で許容しちゃってる、、

それにしても一ヶ月半前は、I/O？Unmarshal？そもそもポインタ？みたいなことを言ってた僕がこうやってgoを書いて、この実装にした方がよかったかもなんて議論ができてるだけで驚きです。笑

# 近くの人を集めていいねする
先ほどのapi_tokenをHeaderにセットして今度はGETを叩きます。
```


```go:functions/api.go
func setHeader(req *http.Request, token string) {
	req.Header.Set("Content-type", "application/json")
	req.Header.Set("User-Agent", "Tinder/3.0.4 (iPhone; iOS 7.1; Scale/2.00)")
	if token != "" {
		req.Header.Set("X-Auth-Token", token)
	}
}
```
```go:types/types.go
type ResultUser struct {
	ID   string `json:"_id"`
	Name string `json:"name"`
}

type Users struct {
	Users []ResultUser `json:"results"`
}
```

```go:main.go
func main(){
    // ...
    likeTenUsers(user)
}

func likeTenUsers(user types.User) {
	body, err := functions.Http("user/recs", user.User.APIToken, "GET")
	if err != nil {
		log.Fatal(err)
	}

	var users types.Users
	if err := json.Unmarshal(body, &users); err != nil {
		log.Fatal(err)
	}
}
```

これで取得できます。どんなに頑張っても10人しか取得できなかった、、ドキュメメントみたらGETなのにbody書いてるかと思ったらPOSTやし、ここのAPIどんな設計やねん、、

何はともあれ、あとはLIKEするだけ！

# 並列高速LIKE
バズワード感がすごいw
goroutineで実装していきます。

```go:main.go
func likeTenUsers(ch chan bool, user types.User) {
	body, err := functions.Http("user/recs", user.User.APIToken, "GET") // token := user.User.APIToken
	if err != nil {
		log.Fatal(err)
	}

	var users types.Users
	if err := json.Unmarshal(body, &users); err != nil {
		log.Fatal(err)
	}

	c := make(chan string)

	for _, u := range users.Users {
		go functions.Like(c, user.User.APIToken, u)
	}

	for range users.Users {
		log.Print(<-c)
	}
}
```

```go:functions/api.go
func Like(c chan string, token string, u types.ResultUser) {
	path := "like/" + string(u.ID)
	_, err := Http(path, token, "GET")

	if err == nil {
		c <- u.Name + "さんをLikeしました！"
	}
}
```

これで並列処理の実装は完了です！うーん、並列処理の書き方とか使い方とかこれであってるのかな、、？
とにかく実行してみると動くはず、、！そして、速い予感、、！

# 並列処理の並列処理
これが果たして許されるのかは疑問だけど、理論上はいけるだろうということで10人取ってきてその全員いいねする、という処理を並列で10個走らせてみることに。

```go:main.go
func main() {
    // ...
    ch := make(chan bool)
    for i := 0; i < 10; i++ {
        go likeTenUsers(ch, user)
    }
    for i := 1; i <= 10; i++ {
		<-ch
	}
}
```
もはやチャンネルの受け取り方がこんなにも雑でいいのかはよくわかりませんがなんとか実装することができました。

そして、実行してみると、、

！！？？

なんと！**3〜4秒ほどで100人のいいねが終わりました！！**

短くなった時間より開発にかかった時間の方がなげーよ。

おしまい。

GitHubはこちら → [Dragon-taro/tinder-fist](https://github.com/Dragon-taro/tinder-fist)

# 悲報
同時にリクエストを送りすぎて、`429 Too Many Requests`を返されましたw
429を回避するためにsleepとか入れてたら結局pythonよりもおそくなるという、、
というか、pythonの方ももしかしたらある程度エラーがでてたのかな？

結論、サーバーに負荷をかけるのはやめましょう。

## 参考にしたサイト

- [Pynder PythonでTinderのAPIをいじる|Review of My Life](https://review-of-my-life.blogspot.com/2017/06/get-tinder-api-by-python.html)
- [世界中で大流行のマッチングアプリ、「t○nder」のAPIを用いて遠隔サイバーナ○パをしてみた](https://qiita.com/dq_yosuke_takahashi/items/7be24ea41f2964c9f681)
- [net/http の POST もろもろ](https://stackoverflow.com/questions/24455147/how-do-i-send-a-json-string-in-a-post-request-in-go)
- [上に同じ](https://qiita.com/yyoshiki41/items/fc878494e19b9de93d56)
- [構造体の nest](https://qiita.com/kiida/items/e465ad268bbacf529432)
- [josn の parse](https://qiita.com/nayuneko/items/2ec20ba69804e8bf7ca3)