LoginSignup
18
13

More than 5 years have passed since last update.

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

Posted at

はじめに

Daiさんがかなり前にTinderの自動化をpythonでやっていたので、それを参考に僕もpythonでTinderの自動化を実装してみました。

Pynder PythonでTinderのAPIをいじる|Review of My Life

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

世界中で大流行のマッチングアプリ、「t○nder」のAPIを用いて遠隔サイバーナ○パをしてみた

pythonだとpynderというライブラリーがあるので簡単に実装できます。→GitHub

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

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

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

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

Tinder API

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

ちなみに、facebookのaccess_tokenを使うのですが、これが曲者でけっこう取得するのに苦労します。僕は、このサイトに書いてある方法で取得しました。

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

api_tokenの取得

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

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)
    }
}
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"`
}
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を叩きます。
```

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)
    }
}
types/types.go
type ResultUser struct {
    ID   string `json:"_id"`
    Name string `json:"name"`
}

type Users struct {
    Users []ResultUser `json:"results"`
}
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で実装していきます。

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)
    }
}
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個走らせてみることに。

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

悲報

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

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

参考にしたサイト

18
13
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13