質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.76%

  • Node.js

    1732questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

  • マルチスレッド

    51questions

    マルチスレッドは、どのように機能がコンピュータによって実行したのかを、(一般的にはスレッドとして参照される)実行の複合的な共同作用するストリームへ区分することが出来ます。

node.jsのpuppeteerで並列?処理する方法

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 330

E3KUROSUKE

score 188

node.jsのpuppeteerモジュールの練習で、複数のURLのスクリーンショットを保存していくようなコードを書いているのですが、ブラウザを起動するためのpuppeteer.launch()がとても遅いので、並列で行いたいのですが、nodeでのやり方がわかりません。

const pp = require("puppeteer");
const uuid = require("node-uuid");

const urls = [
    "url0",
    "url1",
    "url2"
]

async function getScreenshot(url) {
    const browser = await pp.launch();
    let page = await browser.newPage();
    await page.goto(url);
    await page.screenshot({
        path: `${uuid.v4()}.png`,
        fullpage: true
    });
    browser.close();
}

(async () => {
    for (let url of urls) {
        await getScreenshot(url);
    }
})();

上記のコードだと順次処理です。

node.jsの特徴としてシングルスレッドが紹介されている記事をよく見るので、並列化はできないのでしょうか?

よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • karamarimo

    2018/04/19 13:18

    browserを1つにして共用するのはだめなのでしょうか?

    キャンセル

  • E3KUROSUKE

    2018/04/19 18:28

    ひとつにしたのですが、あんまり速くなかったのでブラウザ起動が遅いわけじゃなかったようです。

    キャンセル

回答 1

checkベストアンサー

+3

GitHubのPage.jsを見に行くと、
817行目にcloseメソッドが用意されていますね。

ブラウザを消して立ち上げるのではなく、1ページ毎に立ち上げて操作という風にしてみてはどうでしょう?
ちょっと下のコードに修正して動かしてみてください。

const pp = require("puppeteer");
const uuid = require("node-uuid");

const urls = [
    "url0",
    "url1",
    "url2"
]

const getScreenshot = async (browser, url) => {
    let page = await browser.newPage();
    await page.goto(url);
    await page.screenshot({
        path: `${uuid.v4()}.png`,
        fullpage: true
    });
    await page.close();
}

(async () => {
    const browser = await pp.launch();
    for (const url of urls) {
        await getScreenshot(browser, url);
    }
    browser.close();
})()

おまけ: 並列っぽく操作していく

これだと逐次処理なのでそこそこ速くはなったかと思いますが、
もっと速度が欲しいかもしれません。

そういう時は2通りの手段があります。
(やってることはどちらも同じです)

  • 動作の発火とawaitを分ける
  • Promise.allを使う

動作の発火とawaitを分ける場合のコードはこうなります。
即時実行関数以外は同じなので省いておきます。

(async () => {
    const browser = await pp.launch();
    cosnt promises = [];
    for (const url of urls) {
        promises.push(getScreenshot(browser, url));
    }
    for (const promise of promises) {
        await promise;
    }
    browser.close();
})()

Promise.allを使う手法はこんな感じです。

(async () => {
    const browser = await pp.launch();
    await Promise.all(
        urls.map(url => getScreenshot(browser, url))
    );
    browser.close();
})()

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/04/19 18:34

    回答ありがとございます!
    おまけでご紹介いただいた方法を試してみたいと思います。

    このようにすると何故並列っぽくなるのかが理解出来ていないので、Promiseとasync/awaitをもっと勉強してきます。

    キャンセル

  • 2018/04/19 18:44 編集

    ざっくり説明すると、Promiseというのは「今実行中だよ」オブジェクトなのです。
    https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise
    > pending, fulfilled, rejected
    この3状態ですね。

    promise内で`resolve(value)`が叩かれた時点で状態がpending -> fulfilledに変化。
    それと同時にthenに繋いだコールバック関数を発火します。

    失敗すれば`reject(err)`が実行され、状態がpending -> rejectedに変化。
    それと同時にcatchに繋いだコールバック関数を発火します。

    async / awaitというのは糖衣構文です。
    asyncで包まれた関数は内部をどう書こうが、絶対に`new Promise(fn)`を返します。
    awaitは実行中のPromiseをひたすら見張る構文で、pending -> fulfilledに変化するまで一生待ち続けます。

    従って、まず一度pending中のPromiseを全部作ってしまうのがうまいやり方で、
    Promiseを返させた時点で裏で実行し続けているわけですね。
    各Promiseは各々が勝手に処理を完了させ、まばらにfulfilledを返し始めます。

    後からそれぞれがfulfilledになるのを待つという事をしたのがおまけのコードです。
    Promise.allは引数の配列全てがPromiseである必要があり、
    その配列の中身のPromise全てを見張り、全てがfulfilledになったら自分がfulfilledになるという挙動をします。
    for文+awaitで各Promiseを1個ずつawaitで待っても同様の事が実現可能です。

    もしこの説明で分からなくても、Promiseやasync / awaitを見に行けば理解が進むかと思います。
    がんばって下さいね

    キャンセル

  • 2018/04/19 18:55

    なるほど・・・
    mapで一括で作ったpending状態のPromiseの配列を一括でawaitしてるんですね。
    asyncで包んだ関数をmapの処理やforで回しおわった状態が「並列っぽく」処理している状態で、待ってあげないとプログラムが終了しちゃうからawaitで待っているという感じですかね。
    使い方に慣れるの大変そうですが、すごく便利なので頑張って勉強してみます!
    わかりやすい説明ありがとうございました!

    キャンセル

  • 2018/04/19 19:01

    質問文のコードの時点で只者ではないとは思ってましたが、
    この説明で分かるとは流石の理解力ですね。

    勉強頑張ってください!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.76%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Node.js

    1732questions

    Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

  • マルチスレッド

    51questions

    マルチスレッドは、どのように機能がコンピュータによって実行したのかを、(一般的にはスレッドとして参照される)実行の複合的な共同作用するストリームへ区分することが出来ます。