【Node.js puppeteer7.1.0でブラウザ操作自動化＆スクレイピング】

スクレイピングしたくてやっぱスクレイピングといえばPythonかなーと思ったけど、Node.js今自分の中で熱いからライブラリーー探したらあったよってこと🤭

読み方はパペッティア

Chromeの操作を自動化できるライブラリ（正しくはChromiumというChromeの元になったwebブラウザを操作する）
色々な操作をする時の参考になればいいなとおもう。
使ってみた感想はNode.js慣れしている人ならかなり簡単にスクレイピングできるなって感じです。
***
#####よく操作するクラスの種類#####

・Browser　
　　ブラウザの生成、終了のときくらいしかつかわない

・Page　　 
 　　最も利用するクラス。ページの移動、formへの入力、Elementの取得などができる

・ElementHandle　
　　Pageクラスの「``.$()``, ``.$$()``, ``.$eval()``, ``.$$eval()`` 」などを利用してElementを取得したときの返り値はこのクラス。指定したElementに直接click()やhover()をさせたりできる。
***
#####【操作するブラウザを生成】#####

```jsx
const browser = await puppeteer.launch({
    headless: false //falseにしておくとBrowserが表示されて動く
    slowMo: 50      //ブラウザ操作のスピード調整ができる
})
const page = await browser.newPage()
await page.setViewport({  //ブラウザ幅、高さの設定
	  width: 1200,
    height: 800,
})
```
***
#####【指定したURLにログイン】#####

```jsx
// LOGIN_URL = formのあるページ
// LOGIN_USER_SELECTOR = input[type=text]
// LOGIN_USER = username
// LOGIN_PASS_SELECTOR = input[type=password]
// LOGIN_PASS = password
// LOGIN_SUBMIT_SELECTOR = input[type=submit]
// TARGET_URL = login後開きたいページ

await page.goto(LOGIN_URL, { waitUntil: 'domcontentloaded' })
await page.waitForTimeout(2000) 
await page.type(LOGIN_USER_SELECTOR, LOGIN_USER) 
await page.type(LOGIN_PASS_SELECTOR, LOGIN_PASS)

await Promise.all([ // ログインボタンクリック
    page.waitForNavigation({ waitUntil: 'networkidle0' }),
    page.click(LOGIN_SUBMIT_SELECTOR),
])
await page.goto(TARGET_URL)
```

・{ waitUntil: `load`, `domcontentloaded`, `networkidle0`, `networkidle2` } の4つのoptionがあり、ページ遷移が終わったことを決定するタイミングの違いがある。

・`page.waitForTimeout(2000)` 指定したミリ秒で処理を一時停止する。

　→ `goto()`の後は数秒待たせないと`type()` によるform入力がうまく行かない確率が高まる。

・`click()` と`waitForNavigation()` を同時に行うのは、click後のページ遷移が完了する前に次の処理を行ってエラーになるのを防ぐため。
***
#####【別ウィンドウ、サブウィンドウ、ポップアップへの対処法】#####

```jsx
const [popup] = await Promise.all([
  new Promise(resolve => page.once('popup', resolve)),
  page.click('a[target=_blank]'),
])
```

・`click()` で開かれたサブウィンドウなどは`[popup]`に入る。サブウィンドウやポップアップを操作するにはこの`[popup]` を操作する。
***
#####【要素を取得するよく使う4つの方法＋１】#####

```jsx
const element = await page.$("#name")
const elements = await page.$$(".names")
const value = await page.$eval("#name", el => el.value)
const values = await page.$$eval(".names", el => el.value)
const nameArray = document.querySelectorAll('.names')
```

・$(), $$(), は引数のセレクタでElementHandleクラスを生成する。

内部的に、`document.querySelector()`と`document.querySelectorAll()`が行われている。

・$eval(), $$eval()は第２引数に関数をセットすることで引数に指定した要素を操作した値を取得したりできる。

・ElementHandleクラスは、forEachなどができなくて不便なことがある。そういうときは、

`document.querySelectorAll()`で要素を取得すれば普通にforEachなどを適用させられる。
***
#####【タブ切り替え】#####

```jsx
await page.bringToFront()
// page.に指定したページを操作できるように切り替える
```
***
#####【実際に使うときの流れ】#####

ログイン→ログイン後画面でクリック→出てきたサブウィンドウ操作→元画面に戻る→終了

```jsx
const puppeteer = require('puppeteer')
require('dotenv').config()
const env = process.env

(async () => {
	//ブラウザ生成
	const browser = await puppeteer.launch({
	  headless: false //falseにしておくとBrowserが表示されて動く
	  slowMo: 50
	})
	const page = await browser.newPage()
	await page.setViewport({
	  width: 1200,
	  height: 800,
	})
	//form入力&submitでログイン
	await page.goto(env.LOGIN_URL, { waitUntil: 'domcontentloaded' })
    await page.waitForTimeout(2000) //少し待たせないと入力ミスが起きやすい
    await page.type(env.LOGIN_USER_SELECTOR, env.LOGIN_USER) 
    await page.type(env.LOGIN_PASS_SELECTOR, env.LOGIN_PASS)
	await Promise.all([ // ログインボタンクリック
    page.waitForNavigation({ waitUntil: 'networkidle0' }),
    page.click(env.LOGIN_SUBMIT_SELECTOR),
  ])
	//ログイン後ページへ飛ぶ
	await page.goto(env.TARGET_URL)

	//clickによって表示されたpopup取得
	const [popup] = await Promise.all([
    new Promise(resolve => page.once('popup', resolve)),
    page.click('input[value="~"]'),
  ])

	//もとのページへback
	await page.goBack({ waitUntil: 'networkidle0' })
	//ブラウザ終了
	await browser.close()
})()
```
・dotenvモジュールで定数管理している
***
**puppeteerを使う上での注意点**

ページロードが終わるタイミングと要素取得のタイミングがズレると要素の取得ができなかったり、ページ遷移が終わる前に次の処理に行ってしまっていたりするので、常にタイミングには気をつけないといけない。