PuppeteerでTwitterとFacebookのOGP更新ツールを作った話(Gatsby/Vercel/CloudFunctions/Puppeteer)

## 作ったもの

URLを入力したら、TwitterとFacebookでキャッシュされているOGP情報を更新するツール
https://ogp-validator.bonos.work/

![OGP Validatorの紹介GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/e4471e25-b877-2f11-b7ad-f177a384a90f.gif)

## 使用した技術・サービス

- [Puppeteer](https://github.com/puppeteer/puppeteer) ヘッドレスブラウザで自動操作
- [GatsbyJS](https://www.gatsbyjs.org/) フロントエンドのフレームワーク
- [GatsbyCloud](https://www.gatsbyjs.com/) GatsbyのCIツールとして
- [CloudFunctions](https://cloud.google.com/functions/) サーバーレスAPI
- [Vercel](https://vercel.com/) Webサイトのホスティング

## どのように動いているのか

Gatsbyを静的ホスティングさせているサイトと、OGPの更新処理するサーバーレスAPIの、２つのサービスで構築しています。


### フロントエンド（GatsbyJS/GatsbyCloud/Vercel）

#### コーディング(React/Typescript)
GatsbyJSをフレームワークに、React/Typescriptでコーディングしました。
１ページだけの簡単なサイトだったので、Reduxなどでの状態管理は行わずスピード感を持ってコーディングしつつ、Typescriptでの型定義はしっかり行いました。
スピードを担保するため、コンポーネントに分解したい気持ちを抑えて、多少のスパゲッティコードには目をつぶりました。

```tsx
export type ValidateResponse = AxiosResponse<Blob | ValidateError>

export type DoValidateFunc = (
  type: Type,
  siteURL: string
) => Promise<ValidateResponse>

export type ValidateError = { error: string }

export type HandleSubmitFunc = (event: React.FormEvent, siteURL: string) => void

export type HandleErrorFunc = (message: string) => void
```
（型定義の安心感の虜です）

#### デプロイ

デプロイ周りはコードを一切書かずに、GithubをGatsbyCloudに接続 → GatsbyCloudとVercelを接続だけでリリースできました。


GatsbyCloudは、Githubのリポジトリと接続し、ブランチとディレクトリ、環境変数を指定すれば、そのブランチにプッシュされたときに自動デプロイされる便利サービスです。

Gatsbyで開発している人は、わざわざテスト環境を用意せずともGatsbyCloudだけでテストサイトを公開できてしまいます！さらに、**プライベートリポジトリでもorganizationのリポジトリでも無料で使える**ところがメリットです。

<img width="1123" alt="Gatsby Dashboard 2020-07-25 15-10-54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/cd3786ab-0322-1e3b-15d7-466ace36fb89.png">


しかし、**GatsbyCloudだけでは、独自ドメインでの公開ができない**ので、ビルドツールとして利用し、デプロイ先はVercelを利用しました。
GatsbyCloudの管理画面から様々なホスティングサービスに簡単に接続でき、Vercelにも2,3クリックで接続完了しました。

<img width="1124" alt="スクリーンショット 2020-07-25 15.21.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/cc24356c-890b-0f7f-0b3d-7084a20916d2.png">
（GatsbyCloudと接続できるホスティングサービス）

Vercelの管理画面では、独自ドメインの設定とデフォルトの`ogp-validator.vercel.app`から独自ドメインにリダイレクトするように設定するだけでした。

<img width="1052" alt="スクリーンショット 2020-07-25 15.27.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/3e06706f-7e95-9c94-60ae-0f327e351815.png">



Vercelだけ使えばよくね？と思われるかもしれませんが、personalリポジトリしかデプロイできなかったので、２つのサービスを利用することになりました。personalリポジトリで開発していたらもっと簡単にデプロイできますね。

### バックエンド（CloudFunctions/Puppeteer）

#### 環境

GCPのCloudFunctionsを利用して、サーバーレスAPIで開発を行いました。

他にもCloudRunやVercel(実はサーバーレス関数機能がある)を検討しましたが、消去法的にCloudFunctionsに決めました。

はじめは**CloudRunで開発していたのですが、割引を適用しても料金が発生している**ことに気づきました。

<img width="852" alt="スクリーンショット 2020-07-25 16.30.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/0ceef681-f8d6-7229-b43a-43914c4ead85.png">

調べると、ストレージで料金が発生していました。
CloudRunのためのDockerコンテナを保存している[ContainerRegistory](https://cloud.google.com/container-registry?hl=ja)は、**無料枠適用外のMulti-region**に保存されてしまうそうで、保存先を変更することができませんでした。。。（ContainerRegistoryを無料枠運用できる方法あれば知りたい）

もう一つのVercelサーバーレス関数が管理する上では一番ラクだったのですが、1リクエスト10秒以内・月に160リクエストまでと無料枠での運用は厳しかったので断念。

<img width="731" alt="Vercel利用枠。Hobbyが無料プラン" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/cf7b8d61-2c18-bf30-2b91-231ce1a7f2fc.png">
（Vercel利用枠。Hobbyが無料プラン）

結果、APIは1つだし頻繁にデプロイするわけではないので、無料枠の幅が広いCloudFunctionsを使うことに決めました。

#### 自動操作の仕組み

仕組みとしては、Puppeteerでヘッドレスブラウザを操作し、[Twitter Card validator](https://cards-dev.twitter.com/validator)と[Facebookシェアデバッガー](https://developers.facebook.com/tools/debug/)にアクセスして、クライントから送らてきたURLを入力すると、それぞれのサイトでシェアされたときのUIが表示されるので、それをスクショした画像を返しています。

はじめてPuppeteerを使いましたが、この手順自体は簡単に書くことはできました。

躓いたのがTwitterもFacebookもログインしている状態じゃないと利用できなかったので、ログインさせる必要がありました。そこで、**ログインさせる処理を書いて無事動作させることができたのですが、しばらくするとエラーを吐くようになりました。**

原因は、実行するたびに違うデバイスからログインしていると認識され、ログイン後、さらに電話番号を求められたりしました。
これらの追加ログインにも対応修正を行ったのですが、SMSメッセージに認証コードが送られたりreCAPTHA認証はどうしても自動化できないログインを求めらるものもあり難航しました。

<img width="597" alt="スクリーンショット 2020-07-25 15.51.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/828d1534-c1a5-9a81-b099-44f41f25070d.png">
<img width="743" alt="スクリーンショット 2020-07-25 15.50.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/7fbbc0aa-ea8b-4abc-a545-d9a659a4bf78.png">


最終的にどう解決したかというと、**Puppeteer起動時にログイン済みのCookieを持たせ、そのデバイス・ブラウザではログインしている状態にする**ことができました。
さらに嬉しいことに、ログイン処理（ログイン画面に遷移して、ID/PW入力して、ログイン完了したらCard validatorのページに遷移する）が不要になったので、1,2秒時間が短縮されました。


## まとめ

最近シゴトでReact/Typescriptを使うことになったので勉強がてら利用し、無料枠で収まりそうだったのでサーバーレスを利用し、次のサービスではニューモーフィズムを取り入れたいなとデザインしました。

フロントもバックもデザインも新しい経験を積むことができ、またツールとしても自分が欲しかったものなので、無事リリースまでたどり着きました。
期限を決めたこと・最低限も機能に絞って臨んだこともモチベーションが下がらずに開発できたコツかなと思います。

今後もちまちま改善していこうと思うので、Puppeteerの速度改善や機能に対するアドバイスいただけたら嬉しいです。

![ogp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92083/91c12fca-3f19-bfff-f07f-01bc7508112f.png)

Twitter & FacebookのOGP更新ツール「OGP Validator」
https://ogp-validator.bonos.work/

ブログやメディア運用している方、エンゲージ率もクリック率のためにも是非ご利用ください。