Puppeteerを使用してAmazon ほしい物リストをスクレイピングする

# 概要
最近、Amazonのほしい物リストからKindleの価格を通知するLINE Botを開発しました。
その過程で、Puppeteerを利用して、ほしい物リストの商品名と価格のスクレイピングを行いました。

以下のようなほしい物リストであれば、
![wishlist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/5f9af600-2dd7-4e10-6059-f4dfddabfc20.png)

以下のように、商品名と価格がスクレイピングされます。
![LINE.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/42dec6d4-d352-d459-3538-67597c029ad8.jpeg)

この記事では、そのスクレイピング手法について紹介します。
スクレイピングの方法や実装は以下記事を参考にしています。

https://qiita.com/nobu17/items/35419cb346a88c655770

# 技術的スタック

node:v20.0.0
puppeteer-core：21.9.0

# Puppeteerの基本実装

```javascript:getWishlistData.js
const puppeteer = require("puppeteer-core");
const chromium = require("@sparticuz/chromium");
```
最初に`puppeteer-core` と `@sparticuz/chromium` を使って、Chromiumを起動します。
今回のプロジェクトでは、Lambdaにデプロイしており、そのサイズ制限を回避するために `puppeteer` ではなく、`puppeteer-core` と `@sparticuz/chromium`を使用します。

```javascript:getWishlistData.js
const browser = await puppeteer.launch({
    args: chromium.args,
    defaultViewport: chromium.defaultViewport,
    executablePath: await chromium.executablePath(),
    headless: chromium.headless,
    ignoreHTTPSErrors: true,
});
```

`puppeteer.launch`を使用してPuppeteerに設定を適用し、Chromiumのインスタンスを起動します。
この設定には、ブラウザの起動オプション（例えば、ヘッドレスモードの有無など）が含まれます。

```javascript:getWishlistData.js
const page = await browser.newPage();
```

起動後、`browser.newPage`メソッドを呼び出して新しいページを開きます。
このページオブジェクトを使用し、特定のURLへアクセスしたり、ページ上での操作（クリックや入力など）、スクレイピングを行うことができます。

```javascript:getWishlistData.js
await page.goto(process.env.AMAZON_WISHLIST_URL);
```

`page.goto`メソッドを使用して、スクレイピング対象のウェブページ（今回であればAmazon ほしい物リストのページ）にアクセスします。

# ほしい物リストのスクレイピング

```javascript:scrapeAmazonWishlist.js
await page.evaluate(async () => {
```

`page.evaluate`メソッドを使うことで、ブラウザ上で任意のスクリプトを実行し、その結果を取得することができます。

```javascript:scrapeAmazonWishlist.js
const distance = 500;
const delay = 100;
while (!document.querySelector("#endOfListMarker")) {
    document.scrollingElement.scrollBy(0, distance);
    await new Promise((resolve) => {
        setTimeout(resolve, delay);
    });
}
```

[参考記事](https://qiita.com/nobu17/items/35419cb346a88c655770)にもあるように、Amazon ほしい物リストに多くの商品がある場合、すべての商品情報が一度に表示されないことがあります。
商品を全て表示するためにはページをスクロールする必要があり、リストの最後に到達すると`#endOfListMarker`という要素が表示されます。
この要素が見えた時点で、リストにある商品の読み込みが完了したと判断できます。

そのため、まずページを特定の距離だけスクロールし、指定された遅延時間を待つことで、ページの内容が動的に読み込まれるのを待ちます。
この処理は、`#endOfListMarker`が見つかるまで繰り返されます。

```javascript:scrapeAmazonWishlist.js
const itemList = [];
document.querySelectorAll('div[id^="itemMain_"]').forEach((detailDiv) => {
    const titleElement = detailDiv.querySelector('a[id^="itemName_"]');
    let title = titleElement ? titleElement.textContent.trim() : "";

    let price = -1;
    const priceElement = detailDiv.querySelector("span.a-offscreen");
    if (priceElement) {
        const priceText = priceElement.textContent.trim();
        price = Number(priceText.replace("￥", "").replace(",", ""));
    }

    if (title && price !== -1) {
        itemList.push({ title, price });
    }
}
```

次に、`querySelectorAll`を使ってほしい物リスト内の商品要素を取得し、商品ごとにタイトルと価格情報を抜き出します。
タイトルはそのまま取得し、価格は余分な文字を取り除いて数値に変換します。
正確に取得できた商品情報だけを配列に追加します。

最後に、この配列を整形してLINEに通知すると、以下のように表示されます。

![LINE.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/42dec6d4-d352-d459-3538-67597c029ad8.jpeg)


スクレイピングとLINE通知の機能を含むコードは、GitHubのリポジトリで公開しています。
興味のある方は是非チェックしてみてください。

https://github.com/GorillaSwe/amazon-wishlist-to-line

# 【応用】Kindle版と紙の本の価格やポイントをスクレイピングする

上記実装を応用して、以下のようにKindle版と紙の本の価格やポイントを取得するようにしました。
![LINE_Kindle.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/3f09c964-f9b4-8768-131d-9301146f29ce.jpeg)

Kindleを購入時に取得できるポイントや紙の本の価格は、各商品の詳細ページにアクセスしないと収集できないため、ほしい物リストから商品の基本情報を取得した後、各商品の詳細ページへアクセスするようにしました。
![Details.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/3af6f8a6-2f48-a88b-545c-233ab029351d.png)


```javascript:scrapeWishlistItems.js
const itemList = [];
document.querySelectorAll('div[id^="itemMain_"]').forEach((detailDiv) => {
    const titleElement = detailDiv.querySelector('a[id^="itemName_"]');
    if (titleElement) {
        const title = titleElement.textContent.trim();
        const href = titleElement.href;
        itemList.push({ title, href });
    }
});
```
各商品のタイトルと詳細ページのURLを取得し、配列に加えます。
```javascript:scrapeItemDetails.js
async function scrapeItemDetails(page, items) {
  const itemDetails = [];
  
  for (const item of items) {
    const details = await getItemDetails(page, item);
    itemDetails.push({ ...item, ...details });
  }
  
  return itemDetails;
}

async function getItemDetails(page, item) {
  await page.goto(item.href);
  const details = await page.evaluate(() => {
    const getTextContent = (selector) =>
      document
        .querySelector(selector)
        ?.textContent.trim()
        .replace("(", "")
        .replace(")", "") || null;

    const kindlePrice = getTextContent(
      "#tmm-grid-swatch-KINDLE .slot-price > span"
    );
    const kindlePoints = getTextContent(
      "#tmm-grid-swatch-KINDLE .slot-buyingPoints > span"
    );

    const paperbackOrHardcoverSelector = document.querySelector(
      "#tmm-grid-swatch-PAPERBACK"
    )
      ? "#tmm-grid-swatch-PAPERBACK"
      : "#tmm-grid-swatch-HARDCOVER";
    const bookPrice = getTextContent(
      `${paperbackOrHardcoverSelector} .slot-price > span`
    );
    const bookPoints = getTextContent(
      `${paperbackOrHardcoverSelector} .slot-buyingPoints > span`
    );

    return { kindlePrice, kindlePoints, bookPrice, bookPoints };
  });

  return details;
}
```
取得したURLをもとに各商品の詳細ページに遷移した後、各商品のKindle版と紙の本の価格やポイントを取得し、それらを集約した新しい配列を返します。


作成された配列を整形してLINEに通知すると、以下のように表示されます。

![LINE_Kindle.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/458297/3f09c964-f9b4-8768-131d-9301146f29ce.jpeg)

スクレイピングとLINE通知の機能を含むコードは、GitHubのリポジトリで公開しています。
こちらのリポジトリは _**Kindle**_ 向けのものとなっています。
興味のある方は是非チェックしてみてください。

https://github.com/GorillaSwe/kindle-wishlist-to-line

# まとめ

この記事では、Puppeteerを使用してAmazon ほしい物リストから商品情報をスクレイピングする方法を解説しました。
間違っている箇所や疑問に思ったことがあれば、ぜひコメントをください！

最後まで読んでいただき、ありがとうございました。

上記スクレイピングを基にしたLINE Botの開発について記事を書いているので、こちらも読んでいただけると嬉しいです。

https://qiita.com/GorillaSwe/items/fc207dd6abc1cd0589ef