😎
Puppeteerでfirebase認証のかかったサービスを自動操作する
きっかけ
選手から提供してもらった写真をもとに「キャップ野球選手ガチャ」という機能を提供しているのですが、その発展版として、現状のカードを育成してペナントをシミュレーションするゲームの提案がありました。
その実装をする上で、同じく自分が開発したサービスである
- 試合を記録するスコアブックアプリ
をpuppeteer で自動操作すれば、成績も自動集計できて便利なのではと思いました。
ただし、CAP-SCOREBOOK には firebase 認証を使っています。
firebase 認証をどうするかという問題
firebase 認証も各プロバイダの認証を puppeteer で通せばいいので、
まず twitter から試してみることにします。
そうだ twitter にログインしよう
const puppeteer = require("puppeteer");
const ACCOUNT = "******";
const PASS = "*******";
const browser = await puppeteer.launch({
headless: true,
ignoreHTTPSErrors: true,
executablePath: "/usr/bin/chromium-browser",
args: ["--no-sandbox", "--no-zygote"],
});
try {
const page = await browser.newPage();
await page.setDefaultNavigationTimeout(0);
//twitterログイン
await page.goto("https://twitter.com/login");
await page.waitForSelector('input[name="session[username_or_email]"]');
await page.type('input[name="session[username_or_email]"]', ACCOUNT);
await page.type('input[name="session[password]"]', PASS);
await page.click("div[role=button]");
await page.waitForNavigation();
} catch (error) {
console.error(error);
} finally {
await browser.close();
}
- ログインはできるものの、毎回新規ログインだと見做されて通知が多い
- 挙句、複数回試すと不正ログインと見做されて twitter の別の認証画面に飛ばされる
そうだ google にログインしよう
google は headless モードを bot 判定する
通常のpuppeteerでheadless:false
の場合は何ともないのですが、
headless:true
だとbotと判定されログインがブロックされる仕様になっています。
そこで。
- puppeteer-extra
- puppeteer-extra-plugin-stealth
を導入します。
const puppeteer = require("puppeteer-extra");
const SCOREBOOK_HOST = "http://localhost:3050";
const email = "*****";
const password = "*******";
puppeteer.use(require("puppeteer-extra-plugin-stealth")());
// Launch puppeteer browser.
puppeteer.launch({ headless: false }).then(async (browser) => {
try {
console.log("Opening chromium browser...");
const page = await browser.newPage();
//スコアブックサインイン
await page.goto(SCOREBOOK_HOST + "/register");
await page.waitForSelector("img");
//googleサインインボタンを押す
await page.click("img");
await page.waitForNavigation();
await page.waitForSelector("#identifierId");
await page.type("#identifierId", email);
await page.waitFor(1000);
await page.keyboard.press("Enter");
await page.waitForNavigation();
await page.waitFor(1000);
await page.type('input[type="password"]', password);
await page.waitFor(1000);
await page.keyboard.press("Enter");
await page.waitFor(15000);
} catch (error) {
console.error(error);
} finally {
await browser.close();
}
ReactNativeWeb を puppeteer で操作する
testIDを付与する
- ReactNativeWebのコンポーネントではclassNameが指定ができない
- class名は自動生成される。デプロイし直すと別名になっているので固定でアクセスできない
-
testID
プロパティを付与できるのでそれを頼りに要素をセレクトする
RNwebに関する解釈が間違っているかもしれないのでこの方法がベストかどうかはわかりません。
SPAでは要素が描画されていない場合もあるので、
-
waitForSelector
で待つ - 該当の要素を操作する
が基本の組み合わせです。
以下はtestID
にchange_input_type
を指定した場合の要素の探し方です。
await page.waitForSelector('div[data-testid="change_input_type"]';
await page.click('div[data-testid="change_input_type"]');
docker で puppeteer を使う
dockerfile
FROM node:14
# 最新のchromeをインストールする
RUN apt-get update \
&& apt-get install -y wget gnupg ca-certificates \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y --allow-unauthenticated google-chrome-stable \
&& rm -rf /var/lib/apt/lists/* \
&& wget --quiet https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh -O /usr/sbin/wait-for-it.sh \
&& chmod +x /usr/sbin/wait-for-it.sh
# puppeteer付属のchromiumを使わないと指示する
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN npm --global config set user root && \
npm install puppeteer puppeteer-extra puppeteer-extra-plugin-stealth --unsafe-perm
# 必要であれば日本語フォント(スクショなどに必要)
RUN mkdir /noto
ADD https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip /noto
WORKDIR /noto
RUN unzip NotoSansCJKjp-hinted.zip && \
mkdir -p /usr/share/fonts/noto && \
cp *.otf /usr/share/fonts/noto && \
chmod 644 -R /usr/share/fonts/noto/ && \
/usr/bin/fc-cache -fv
WORKDIR /
RUN rm -rf /noto
WORKDIR /src
メモリが少ないので複数起動には向いてない
当初VPS上で実行しようとしていたのですが、
- 3イニング10分で終わる
- 試合数が増えると(総当たり)同時並行実行しないと1週間で終わらない
- 同時並行実行するとおそらくメモリが足りなくなる
という問題があり、試合データの準備をcronで行い、
ローカルで未完了の試合だけをpuppeteerで実行する方法にしました。
Discussion