0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

G's ACADEMY【技術記事書いてみた編】Advent Calendar 2024

Day 22

Puppeteerを使って、画像切り出しをやってみた

Last updated at Posted at 2024-12-22

Puppeteerで何をした?

某移行案件で、古く使われてるサイトもあり、デザインモック、psdデータなど残ってなかった。ある日、「結構長いページですけど、午前中に画像もらえますか?」と言われた。

ついに来た。無茶振り。

ざっと見て、100枚ほどの画像を2時間でやれと。ふむ。
puppeteerのおかげで一気に書き出しができたので、誰かの助けになれたらーと思っての記事です。

(開発者ありがとう T__T)

Puppeteerとは?

puppeteerの意味

Puppeteerの意味は人形使い。パペットを扱う人が、puppeteerだそうです。かっこいいですね、ブラウザーをパペットとし、コントロールしちゃう発想。

Puppeteerができることは

Puppeteerは、DevToolsプロトコルまたはWebDriver BiDiを介してChromeやFirefoxを制御するための高レベルAPIを提供するJavaScriptライブラリです。(ChatGPT訳)

Puppeteerでは、マウス、タッチイベント、キーボード入力を通じてページ上の要素とやり取りすることができます。通常、まずCSSセレクタを使用してDOM要素をクエリし、次に選択した要素に対してアクションを実行します(ChatGPT訳)

簡単にいうと、CSSセレクタから切り出しして、画像やテキスト情報など取得することができる。実際、いろんなデバッグ作業もこれで実現できるらしいですが、今回はCSSセレクトの切り出しのみやりました!

実装

↑pupppeteerの公式サイトにQuick Startがありますので、基本サンプルコードで、できます。そして、通信は必要なので、今回はnode.jsで作ってみました。

const puppeteer = require('puppeteer');
const path = require('path');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.setJavaScriptEnabled(true);
  await page.setViewport({
  
  //サイズを決める、画質と画像サイズに影響ある部分です。
    width: 1980,
    height: 800,
    deviceScaleFactor: 2,
  });

  await page.setRequestInterception(true);
  page.on('request', request => {
    if (request.url().includes('cookie')) {
      request.abort();
    } else {
      request.continue();
    }
  });

  const targetURL = 'https://サイトのアドレスを入れてください';
  await page.goto(targetURL, { waitUntil: 'domcontentloaded' });

//ポイント①
//CSSを読み出しできてから待ってもらう
  await page.waitForSelector('CSSのセレクトを指定', { visible: true });

  await page.evaluate(() => {
    const style = document.createElement('style');

//ポイント②
// 意外にも重要なフォント指定

    style.textContent = `
      * {
        font-family: 'DNP Shuei Mincho Pr6', serif !important;
      }
    `;
    document.head.appendChild(style);
  });


//ポイント③
//追従するヘッダー、フッターを書き出しさせないため
  await page.evaluate(() => {
    const header = document.querySelector('#header');
    const footer = document.querySelector('#footer');
    if (header) header.style.display = 'none';
    if (footer) footer.style.display = 'none';
  });

  await page.evaluate(async () => {
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    let previousHeight = 0;

    while (true) {
      const currentHeight = document.body.scrollHeight;
      window.scrollTo(0, currentHeight);
      await delay(500);
      if (currentHeight === previousHeight) break;
      previousHeight = currentHeight;
    }
  });

↑ポイント①-③は苦労しました!!!
① 指定した要素が確実にDOM上で操作可能な状態になるのを保証します。
② 全然違うフォントや、タイトル崩れなど起きたので、フォント指定は意外にも重要でした。
③ 一番大変だったところ、サイトの都合上、追従ヘッダー、フッター(時々ボタン)などを、ここでミュートさせる。

ここまで来たら、あとはハッピーです!

  // スクリーンショットを撮る親要素のセレクタ
  const parentSelector = '指定する親要素';
  const fileName = '画像名を設定.jpg';

  const parentElement = await page.$(parentSelector);
  if (parentElement) {
    await parentElement.screenshot({
      path: path.resolve(__dirname, fileName),
      quality: 100,
    });
    console.log(`${fileName} にスクリーンショットを保存しました`);
  } else {
    console.log(`${parentSelector} が見つかりませんでした`);
  }

  await browser.close();
})();

↑これで、画像が綺麗に切り取れました。
上記の部分を改造して、同じタグのものを順番に切り出すことも可能だったので、自分が担当したサイトが、こういったCSSの使い回しが多かったので、、、

このようにして、書き出しの部分をちょっと変更して、複数枚を一気に書き出しました!!
指定階層のものを、for文で回して、順番に書き出してもらい、100枚なんて、10分で終わった(やった!!)

const selectors = [
    { parentSelector: '#id1', childSelector: '.css1', baseFileName: '基本ファイル名1' }
  ];

  for (const item of selectors) {
    const parentElement = await page.$(item.parentSelector);
    if (parentElement) {
      const childElements = await parentElement.$$(item.childSelector); // 複数要素を取得
      if (childElements.length > 0) {
        for (let i = 0; i < childElements.length; i++) {
          const childElement = childElements[i];
          const fileName = `${item.baseFileName}_${i + 1}.jpg`;
          await childElement.screenshot({
            path: path.resolve(__dirname, fileName),
            quality: 100,
          });
          console.log(`${fileName} にスクリーンショットを保存しました`);
        }
      } else {
        console.log(`${item.childSelector} が ${item.parentSelector} の中に見つかりませんでした`);
      }
    } else {
      console.log(`${item.parentSelector} が見つかりませんでした`);
    }
  }

最後に node ファイル名 で書き出しして、終わり!!!

やってみて良かったこと & 気づき


すごく早くできたかつ、指定要素のみ書き出したので、ヒューマンエラーのない工程ができた。


こうやって早く出来上がったのが、コーダーさんがすごく綺麗に階層を整理してくれたおかげです。たまに、クソCSS書く自分もいますが、人のコード読んで、身が引き締まります。


なんか問題ある時に、こうやってライブラリーの力を借りてできたこと、本当に、開発者に感謝しかない。特にバタバタしていた期間に、puppeteerに出会えたことに感謝〜

参考サイト

  1. Puppeteer公式サイト
  2. Puppeteerの魅力は何か~ヘッドレスChromeを自由自在に操る
  3. 【データ収集に!】Puppeteerの使い方を徹底解説!スクレイピングの初心者にもおすすめ【Node.js】
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?