free(malloc(sizeof(MRM)));

虚無・アクセラレーション

Google Form自動回答Botでクソアンケートに勝つ(Python3)

ある時、僕の学校にあるGoogle Formで作られたアンケートに答えるよう手紙が来ました。
見てみると結構長め、さらには回答必須という地獄のような物でした。やるのは面倒だし回答はしなきゃダメ……

そうだ、自動化すればいいじゃないか!
「退屈なことはPythonにやらせよう」

というわけで結構前にGoogle Formに自動で回答を送信するBot(不完全)を作ったので備忘録程度に書きます。
といっても先人にGoogle Form回答自動化してる方がいらっしゃったのでそれを参考にカチカチとやるだけでした。先人に感謝。

Google FormのURL

最初はURLがhttps://docs.google.com/forms/d/e/1FAIpQLSc7J4XPNKYodJlKDeDzfNJHlEGSFbSWqzxhgORDm0ODQWj0Tw/viewformのように最後にviewformがくっついてます。で、情報を送信するとhttps://docs.google.com/forms/d/e/1FAIpQLSc7J4XPNKYodJlKDeDzfNJHlEGSFbSWqzxhgORDm0ODQWj0Tw/formResponseとなり、最後にviewformの代わりにformResponseがくっつきます。
また、Google Formでは回答の送信をURLパラメータを使って行っています。

URLパラメータとは

GETメソッドでサーバーとやりとりする際に、URLの後ろにくっついてる?以降のやつです。「クエリ文字列」とも言います。サーバーにデータを送る際、もうURLに載せちゃおうかということで送信データはリクエストURLの後ろに付けられます(http://example.com?hoge=fugaとか)。?の後に「名前=値」のような形式で記述します。さっきの例では名前がhogeで値がfugaです。
あとパラメータが「結果に影響を与える外部からのデータ」という意味を持ってたりします。

やること

Google Formさんはフォームの情報を?の後に色々つけて送信しているってことは、プログラム側でそのURLを再現してあげればOKです。URLを作ってそれを送信するのはPythonに、パラメータキーや送信する値はjsonに書いていきます。
そのためには、Google FormのURLパラメータの取得が必要です。取得していきます。 ちなみに、こっから作ったTestのGoogle Formを元にやっていきます。URL f:id:admarimoin:20181119001102p:plain

URLパラメータの取得

Chromeの場合は、Ctrl + Uでソースコードが見れるので、そっから検索でentryを探してあげて下さい。
f:id:admarimoin:20181119001529p:plain
HTMLのinputタグのname属性に記載されている内容がパラメータのキーになります。ここでは

name="entry.367274191"

ですね。おそらく生成されるURLは/formResponse?entry.367274191=回答となります。

.jsonファイル作り

cfg.json

{
    "form_url": "https://docs.google.com/forms/d/e/1FAIpQLScHpzwQEFsDSzBxyInrRzxNEVyXqOdDsU5LNDYad87e-JMDgQ/",
    "entry": {
        "ans": 367274191
    },
    "output":{
        "ans": "あかんこ"
    }
}

form_urlには送信したいFormのURL、entryには先程取得したパラメータキーの数字outputにはentryに対応する答えを送信します。ここでは「あかんこ」か「じゃぱん」のどちらかですね。これらの変数は自由に変更してOKですが、entryの数字とそれに対応する答えの変数(ここではans)は同じにしましょう。

mainファイル作り

requestsライブラリが必要です。
main.py

#coding: utf-8
import requests
import json

fname = "cfg.json"
with open(fname, "r") as f:
    cfg = json.load(f)

    params = {"entry.{}".format(cfg["entry"][k]): cfg["output"][k] for k in cfg["entry"].keys()}
    res = requests.get(cfg["form_url"] + "formResponse", params=params)

ほぼ先人様のコードの パクリ オマージュです。paramsにURLパラメータを入れてrequest.getでGETリクエストをしてます。うーん便利。送信されてるか不安な人は

if res.status_code == 200:
    print("Done!")
else:
    res.raise_for_status()
    print("Error")

のようにステータスコードを見てあげて下さい。
実際リクエストしているURLを見てみます。

print(res.url)

を適宜どっかにつけてやると、

https://docs.google.com/forms/d/e/1FAIpQLScHpzwQEFsDSzBxyInrRzxNEVyXqOdDsU5LNDYad87e-JMDgQ/formResponse?entry.367274191=%E3%81%82%E3%81%8B%E3%82%93%E3%81%93

Google FormのURLの仕組みはこのようになっていたことがわかります。=以降のURLエンコードを直してやると「あかんこ」になるので、ちゃんと/formResponse?entry.367274191=回答になっています。

Tips

回答なし

選択肢以外の回答

cfg.jsonのoutput部分を選択肢にない回答にしてやったらどうなるのでしょうか。
結論から言うと、回答としてカウントされません。
f:id:admarimoin:20181119001702p:plain 画像を見てもらうと分かる通り、全体の22件の回答に対し17件の回答しか来ていません。これはダメですね。必須回答なので必ず22件無ければならないはずなのですが…

無回答

cfg.jsonでentryもoutputも何も指定せずにリクエストを送ったらどうなるのでしょうか。すると、以外なことに(?)

https://docs.google.com/forms/d/e/1FAIpQLScHpzwQEFsDSzBxyInrRzxNEVyXqOdDsU5LNDYad87e-JMDgQ/viewform

と、formResponseではなくviewformのリンクをリクエストしています。もちろん回答は送信できていません。

課題

inputタグからname属性をわざわざ探すところが結構面倒なのでWebスクレイピングとか使って自動で収集、あわよくば入力出来るようにしてみたいな〜ぐらいには考えてます。

参考URL

PythonでGoogle Formの自動回答をしてみた - Qiita