サンプルやPuppeteerの記事でよく見る$eval
や$$eval
はCSSセレクタで対象の要素を指定してコールバックを渡したらその中ではブラウザのコンテキストで処理ができる(Element
が渡る)のでelement.href
のように直接呼び出せる
便利ではあるがここ最近CSSセレクタで対象要素を指定するのがなかなか難儀なためそれなりに使い慣れているXPathで指定してよしなにやりたいなと考えるようになった
page.$eval, page.$$eval
CSSセレクタで対象要素を特定する
page.$eval
コールバックに渡る要素は単数
const searchValue = await page.$eval('#search', el => el.value); const preloadHref = await page.$eval('link[rel=preload]', el => el.href); const html = await page.$eval('.main-container', e => e.outerHTML);
page.$$eval
コールバックに渡る要素は複数
const divCount = await page.$$eval('div', divs => divs.length); const options = await page.$$eval('div > span.options', options => options.map(option => option.textContent));
要素の特定にXPathを使う
今回はこっちがやりたかった
page.$x('//a');
返ってくるのはPromise<Array<ElementHandle>>
そもそもコールバックは渡せないし扱うのはElementHandle
eval
に渡すコールバック内で処理するものと違いElementHandle
からテキストを抽出するのは少し面倒
eval
で使うようにelement.href
など直接呼び出せない
同じようなのりで書くとfunction href not found
と怒られる
具体的な例としては少々雑だが次のようにする必要がある
const elementHandles = await page.$x('//a'); const url = await(await elementHandles[0].getProperty('href')).jsonValue();
参考
node.js - getting property from ElementHandle - Stack Overflow
複数要素であれば次のようにする
- 特定文言を含んだリンク先を抽出する
const links = await page.$x('//a[contains(@href, "hoge")]'); await Promise.all(links.map(async(e) => await (await e.getProperty('href')).jsonValue()));
- タグ内のテキストを抽出する場合
const list = await page.$x('//div'); await Promise.all(list.map(async(e) => await (await e.getProperty('textContent')).jsonValue()))
こんな感じで取ってこれる
メソッド化
参考のサイトにもあるが毎度このあたりのコード書くのも微妙なのでメソッド化すると良さそう
const getProperty = async (elementHandle, property) => { return await ( await elementHandle.getProperty(property)).jsonValue(); }
- 呼び出し側
const links = await page.$x('//a'); const hrefs = await Promise.all(links.map(async(e) => await getProperty(e, 'href')));
ElementHandle
調べていたらこのElementHandle
要素を操作するという観点から見るとなかなか便利
ElementHandleからclick()
やfocus()
などで操作できる
なので本来は操作の用途で使うのがベターそう
まとめ
PuppeteerでXPathを用いて要素を特定する場合
page.$x
を使う- ElementHandleよしなにテキストを抽出できるイディオムをおさえておく
await(await elementHandle.getProperty('href')).jsonValue()
eval
でコールバックの中でゴニョゴニョやってデータ取得するというのも要素特定がDevToolでさくっと確認できるので楽な場合もある
ただ、直近触ってみた感じだと個人的にはpage.$x
を使い、XPathで要素を指定+ElementHandleでよしなにやるパターンがやりやすいなと感じた
おまけ
余談だがPuppeteerのAPIドキュメントを読んでいたらpage.$x
の他にもpage.$
やpage.$$
というメソッドもあるよう
- 参考
こちらはCSSセレクタで要素を特定し、ElementHandleが返ってくる
なのでXPathが嫌だけどElementHandle使いたいパターンのときはこちらを使うのが良い