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?

More than 3 years have passed since last update.

node.js + puppeteer + axios でポケモンカードの画像(漆黒のガイストのカードリスト)をスクレイピングしてみる

Last updated at Posted at 2021-05-14

はじめに

sveltekitでホームページつくりました。良かったら見てください。
安全にインターネットを楽しもう
Twitter

やりたいこと

・公式のカードページから画像をスクレイピングしたい
・最新弾である[漆黒のガイストのカードリスト]の画像を全部保存したい

必要なもの

・node.js
・puppetter
・axios

手順

まずはnpm init -yでさくっと環境をつくる。

npm init -y
npm i puppeteer
npm i axios
touch index.js

で、今後 node index.jsで起動していきます。

まずはページを表示するだけ

const puppeteer = require("puppeteer");

(async () => {
  //↓実際にブラウザで動きを見たければfalseに
  const browser = await puppeteer.launch({headless: true}); 
  const page = await browser.newPage();
  await page.goto("https://www.pokemon-card.com/card-search/index.php?mode=statuslist&pg=732");
  await page.screenshot({ path: 'screenshot.png' })
})();

これでフォルダにscreenshot.pngというファイルができて、スクリーンショットが撮れてたらOKです。

img要素をまとめて配列に入れる

次に、img要素を拾ってくる処理をつくります。

const puppeteer = require("puppeteer");
(async () => {
  //↓実際にブラウザで動きを見たければfalseに
  const browser = await puppeteer.launch({headless: true}); 
  const page = await browser.newPage();
  await page.goto("https://www.pokemon-card.com/card-search/index.php?mode=statuslist&pg=732");
  //使わないよ await page.screenshot({ path: 'screenshot.png' });

  //imgタグのセレクター。cssとかidで指定もできるよ。
  const listSelector = await "img";
  const datas = await page.$$eval(listSelector, (list) => {
    return list.map((data) => data.src);
  });
  await console.log(datas)
})();

すると、こんな配列がconsoleに出たかと思います。

node.js
[
  'https://www.pokemon-card.com/assets/images/logo.svg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039432_P_REDEIBA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039433_P_REDEIAN.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039434_P_SEREBIIV.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039435_P_SEREBIIVMAX.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039436_P_TANEBO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039437_P_SARUNORI.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039438_P_BACHINKI.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039439_P_GORIRANDA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039440_P_KODAKKU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039441_P_GORUDAKKU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039442_P_NYURA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039443_P_MANYURA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039444_P_POWARUNAMAMIZUNOSUGATA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039445_P_RABUKASU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039446_P_OTAMARO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039447_P_KAPUREHIRE.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039448_P_UOCHIRUDONV.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039449_P_MERIPU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039450_P_MOKOKO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039451_P_DENRYUU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039452_P_SHIMAMA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039453_P_ZEBURAIKA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039454_P_EMONGA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039455_P_ZERAORAV.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039456_P_GOSU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039457_P_GOSUTO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039458_P_GENGA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039459_P_YAMIRAMI.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039460_P_KURESERIA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039461_P_GOBITTO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039462_P_GORUGU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039463_P_MAIKA.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039464_P_KARAMANERO.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039465_P_ABURI.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039466_P_ABURIBON.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039467_P_KOKUBABADOREKKUSUV.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039468_P_KOKUBABADOREKKUSUVMAX.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039469_P_GAMAGARU.jpg',
  'https://www.pokemon-card.com/assets/images/card_images/large/S6K/039470_P_GAMAGEROGE.jpg',
  'https://www.pokemon-card.com/assets/images/ajax-loader.gif'
]

あとはこの配列にmapで逐次アクセスし、画像をつくるだけです。
それではその部分の関数もつくりましょう。

URLの入った配列に順番にアクセスし、画像を作成する関数。

const axios = require("axios");
const fs = require("fs");

/* urlSplit関数 
https://www.pokemon-card.com/assets/images/card_images/large/S6K/039432_P_REDEIBA.jpg
こういうアドレスがあったとき、[S6K]039432_P_REDEIBA.jpgというファイル名にしたいので、URLを分割して配列に入れておく。
 */
const urlSplit = (str) => {
  const array = str.split("/");
  const returnArray = [array.slice(-2)[0], array.slice(-1)[0]];
  return returnArray;
};

const imageSave = async (url) => {
  //Urlを上の関数で分割しておく。
  const imgName = await urlSplit(url);

  //axiosで取りにいく。
  const res = await axios.get(url, { responseType: "arraybuffer" });

  //取れた画像をfsで書き込む。./img/[S06K]039432_P_REDEIBA.jpgと保存される。
  fs.writeFileSync(
    `./img/[${imgName[0]}]${imgName[1]}`,
    new Buffer.from(res.data),
    "binary"
  );
};

ちなみにlogo.svgとかajax-loader.gifみたいなファイルもできちゃいます。
嫌ならif文とかで良い感じに除外してください。
僕は面倒なので手動で消してます。

ページ遷移

このままだと1ページ目しか取れないので、ページを移動して2ページ目、3ページ目と取ります。
ちゃんと関数に分割したほうが良いのですが、今回は面倒なので同じ作業を3回書きます。

  const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
  await page.click(".next");
  await sleep(5000);

こんな感じの処理を入れたら、次のページに移動してちょっと待ってくれます。
あとは同じように保存の処理を入れるだけです。

完成物

const puppeteer = require("puppeteer");

const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(
    "https://www.pokemon-card.com/card-search/index.php?mode=statuslist&pg=732"
  );
  const listSelector = await "img";
  const datas3 = await page.$$eval(listSelector, (list) => {
    return list.map((data) => data.src);
  });
  await console.log(datas3);
  await datas3.map((item) => imageSave(item));

  await page.click(".next");
  await sleep(5000);
  const datas = await page.$$eval(listSelector, (list) => {
    return list.map((data) => data.src);
  });
  await console.log(datas);
  await datas.map((item) => imageSave(item));

  await page.click(".next");
  await sleep(5000);
  const datas2 = await page.$$eval(listSelector, (list) => {
    return list.map((data) => data.src);
  });
  await console.log(datas2);
  await datas2.map((item) => imageSave(item));
  await browser.close();
})();

const axios = require("axios");
const fs = require("fs");
const imageSave = async (url) => {
  const imgName = await urlSplit(url);
  const res = await axios.get(url, { responseType: "arraybuffer" });
  fs.writeFileSync(
    `./img/[${imgName[0]}]${imgName[1]}`,
    new Buffer.from(res.data),
    "binary"
  );
};

const urlSplit = (str) => {
  const array = str.split("/");
  const returnArray = [array.slice(-2)[0], array.slice(-1)[0]];
  return returnArray;
};

スーパー愚直な画像スクレイピングでした。
もっともっとコードは綺麗にできます。
同期的に処理して、同時に異なるアドレスにアクセスして高速化とかもできると思います。
今回は動きをわかりやすくしたいので、async awaitで手続き型っぽい動きになってます。
とりあえず目的が達成できれば良いだけなら、これをnode index.jsで画像がダウンロードできます。

と、いうことで画像が保存できましたとさ。

image.png

終わりに

sveltekitでホームページつくりました。良かったら見てください。
安全にインターネットを楽しもう
Twitter

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?