仮想DOMしか使えない私がPuppeteerで要素取得方法を理解するまでに苦労した話

# Goal

Puppeteerで要素取得（テキストの値取得）をできるようになること。


# 読者対象

- Puppeteerが実行可能。
- Puppeteerで要素の取得をまだしたことがない。


# いきなり結論

やりたいことがズバリで非常にわかりやすかったです、go_sagawaさんありがとうございましたm(_ _)m
https://qiita.com/go_sagawa/items/85f97deab7ccfdce53ea

ここから先はどういう過程を経て結果にたどり着いたかの話になります(;^_^A


# Puppeteerと戦う

## 公式ドキュメントを見てみる

基本は公式ドキュメントにあるはずなので例をみてみる。
https://pptr.dev/
スクショやPDF変換といった目を引く機能はあるが、私の欲しいセレクタ使った要素取得がないぞ…
そのあとAPIリファレンスからそれらしいメソッドがないか眺めてみるがわからないorz


## Tools for Web Developersの方を見てみる

ググるとTools for Web DevelopersのほうにもPuppeteerのページがあるのを発見。
Get Startedにはあるだろう(￣▽￣)と思ってみたら・・・ないじゃないか、というかほぼPuppeteerとサンプル一緒orz
https://developers.google.com/web/tools/puppeteer/get-started


## Githubにexampleあるんじゃないか？と閃く

予想通りexamplesフォルダがある（ ＾ω＾）おっ
TopページのReadmeよく読むとResourceセクションにリンク張ってあるんですけどね(;^_^A
search.jsってのがズバリですよねキターー(ﾟ∀ﾟ)ーー!!
https://github.com/GoogleChrome/puppeteer/blob/master/examples/search.js

>```javascript:search.js
>const puppeteer = require('puppeteer');
>
>(async() => {
>  const browser = await puppeteer.launch();
>  const page = await browser.newPage();
>
>  await page.goto('https://developers.google.com/web/');
>
>  // Type into search box.
>  await page.type('#searchbox input', 'Headless Chrome');
>
>  // Wait for suggest overlay to appear and click "show all results".
>  const allResultsSelector = '.devsite-suggest-all-results';
>  await page.waitForSelector(allResultsSelector);
>  await page.click(allResultsSelector);
>
>  // Wait for the results page to load and display the results.
>  const resultsSelector = '.gsc-results .gsc-thumbnail-inside a.gs-title';
>  await page.waitForSelector(resultsSelector);
>
>  // Extract the results from the page.
>  const links = await page.evaluate(resultsSelector => {
>    const anchors = Array.from(document.querySelectorAll(resultsSelector));
>    return anchors.map(anchor => {
>      const title = anchor.textContent.split('|')[0].trim();
>      return `${title} - ${anchor.href}`;
>    });
>  }, resultsSelector);
>  console.log(links.join('\n'));
>
>  await browser.close();
>})();
>```

'Headless Chrome'で検索して、結果のリンク先URLを複数行出力するサンプルと見える。
後半の```Extract the results from the page.```のコメント箇所が要素取得部分。
```page.evaluate()```ってなんだろう？と公式APIドキュメントを見ると…
https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-pageevaluatepagefunction-args

>```javascript
>const result = await page.evaluate(x => {
>  return Promise.resolve(8 * x);
>}, 7);
>console.log(result); // prints "56"
>```

>```javascript
>const bodyHandle = await page.$('body');
>const html = await page.evaluate(body => body.innerHTML, bodyHandle);
>await bodyHandle.dispose();
>```

サンプルが何を伝えたいのか意味が分からないorz

ひとまずevaluate()は放置して、その中身を見てみるとdocument.querySelectorAll()で要素取得して、各アンカー要素を表示のために整形しているのは理解できる。
じゃあevaluate()使わないで中のFunction外に出してやってみるかと試してみたら・・・

```terminal
(node:5148) UnhandledPromiseRejectionWarning: ReferenceError: document is not defined
```

documentがないとのエラーでようやくevaluate()を理解する( ﾟдﾟ)はっ！
evaluate()の中身はブラウザ側で実行するコード、故にdocumentがつかえるのかと。


## 理解はしたが冗長さに嫌気がさす

理解はした。
だけどdocument.querySelectorAll()を実行してたり、resultsSelectorを第二引数で渡さないといけなかったりとコード量が多い。
要素取得なんて使用頻度の高いAPIはラップしたAPIあってもいいんじゃないの？と思ってググってたら、冒頭にリンクを載せたgo_sagawaさんの記事でそれがあることを理解m(_ _)m
\$eval()、複数なら\$\$eval()がそれとのことなので書き直してみた。

```javascript:search.js
// 複数の場合
const links = await page.$$eval(resultsSelector, anchors => {
    return anchors.map(anchor => {
        const title = anchor.textContent.split('|')[0].trim();
        return `${title} - ${anchor.href}`;
    });
});

// 単数の場合
const link = await page.$eval(resultsSelector, anchor => {
    const title = anchor.textContent.split('|')[0].trim();
    return `${title} - ${anchor.href}`;
});
```

\$eval()、もしくは\$\$eval()がコールバックを呼び出す前にdocument.querySelector()、もしくはdocument.querySelectorAll()を実行して結果を渡すAPIと分かれば、非常にシンプルな記述になる。
anchorを表示のために整形しないなら１行で書けてしまう。

```javascript:search.js
// 複数の場合
const links = await page.$$eval(resultsSelector, anchors => anchors.map(anchor => anchor.textContent));

// 単数の場合
const link = await page.$eval(resultsSelector, anchor => anchor.textContent);
```

Get Startedにはぜひこれのサンプルが欲しかったというのが本音。
まあ、公式のAPIドキュメントのメソッド説明にたどり着けていたら、しっかり書いてあるんですけどね(;^_^A
https://pptr.dev/#?product=Puppeteer&version=v1.11.0&show=api-pageevalselector-pagefunction-args


# まとめ

ReactばかりでDOMを知らないでいると、ドキュメントを見てもquerySelectorAll()の記述をスルーしてしまって、かなり消耗するという話でした(;^_^A
正直Google産じゃなければ、技術選定ではドキュメントがわかりにくいということで飛ばしていたかもw

要素取得は\$eval()、もしくは\$\$eval()、ブラウザの中で何か実行したい場合はevaluate()と分かれば、あとはDOMの勉強すればはかどりそうですが果たして・・・to be continued?