— puppeteer に一歩ずつ入ってみる(JavaScriptの定跡についても学びながら)
どんなライブラリでも、最初に require で(プログラム中に)読み込むことから始まる。
require('puppeteer');
require は関数なので、引数を括弧で包んで渡す。
ライブラリの名前は文字列として(シングルクォートまたはダブルクォートで)囲んで表現する。
この require とほぼ同じ機能を殆どのプログラミング言語で提供されている(Rubyでも require、JavaやPythonでは import )。
JavaScriptの場合、require は(ほぼ)単なる関数なので、プログラムの実行状態に変化を及ぼさない。実際には Puppeteer (も含めてあらゆるライブラリ)を使うには、次のように、
require の結果は変数に代入しておく。
const puppeteer = require('puppeteer');
undefined
を返す(画面に表示する)が、 実際には Puppeteer の機能を使うための取っ掛かりになるオブジェクトが値として代入されている(require を代入なしで呼んだときに返した値)。ブラウザを(プログラムから)起動する。
puppeteer.launch
と入力すると、[Function: launch]
のように表示される。このオブジェクトの、launch 属性が指し示すもの(値)が、関数であることを示している。
このように、オブジェクトは、その中に属性値(或いはフィールドと呼んだりメンバーと呼んだりする)として様々な値を抱え込んでいる。それらは、数値や文字列であったり、関数であったり、(さらに別の)オブジェクトだったりもする。
関数を動作させるためには、括弧をつけて(特に与える引数がなければ空の括弧でいい)呼び出す。
puppeteer.launch()
これでヘッドレスブラウザが起動する。ただし、ヘッドレス(画面表示なし)なので、 画面には何の変化もなく、起動したことを目でみて確認ができない。
プログラムが安定動作してきて、目で確認する必要がなくなったら、このようにヘッドレスで粛々と仕事をこなしてくれるプログラムは有用である、が、今は目で確認したいので、
puppeteer.launch({headless: false})
のように、オプションつきで呼び出す。
launch 関数(メソッドとも呼ぶ)の詳細は、前述の puppeteer クラスのページの下の方にある(ここ)。
呼び出し方(API)が、
puppeteer.launch([options])
のように表記され、
options <Object>
とあるので、オブジェクト(ブレースで囲んで、キー: 値
の のペアをカンマ区切りで並べたもの)を渡すことになる。
キー(あるいはフィールド)として、
headless <boolean>
が含まれている。boolean 型で値を指定することになっていて、デフォルト値は true。 ここで false を指定すると、headless mode でない(つまり画面に表示されるブラウザとしての)起動となる訳だ。
なお、launch メソッドの記述の末尾近くに、
returns: <Promise<Browser>>
とあるのも確認されたい。(Promise については後程扱う)。
エディタを開く
プログラム片を入力
適切なフォルダに、適当な名前をつけて、.js
という拡張子で保存
コマンドプロンプトを、上記のフォルダをカレントフォルダとした状態で起動
(エクスプローラのアドレスバーに cmd
と入れての起動を推奨する)
node xxxx.js
(保存したファイル名を xxxx.js とした場合)のように入力すればプログラムが動作する。
ソースを変更して再度実行する時は、コマンドプロンプトの中で 矢印キーで前のコマンドを呼び出して再利用すればいい。
まず昔からある関数定義を見る。
function a() {
console.log("abc");
console.log("cdefg");
}
これは名前をつけて関数を定義する最も一般的な書き方。
ただし、上記のコードをファイルに保存して実行しても、プログラムとしては何も起きない。
関数を定義するだけで、それを呼び出さなければ、何もしないプログラムと同等の意味しかない。意味のあるプログラムにするには、
function a() {
console.log("abc");
console.log("cdefg");
}
a()
a()
のように、その関数を呼び出してあげる必要がある(ここでは2回呼んだので合計4行の出力が発生する筈)。
名前をつけない関数もある。function キーワードの直後に(名前をすっ飛ばして)すぐ括弧が置かれる。
function () {
console.log("abc");
console.log("cdefg");
}
無名関数、匿名関数などと呼ばれる。
変数に代入して後から呼び出す
var aa=function () {console.log("aa is called")}
// ... 途中に何が入ってもいい (と、推察できそうな場所では、以下 ... を省略する
aa // REPL だと [Function: aa] が表示されるだろう。
// ...
aa() // 括弧をつけると関数が呼び出される(実行される)
別の関数の引数として渡す。
function doit(fnc) { // 無名関数を引数fncの値として受け取って、
fnc() // それを実行する(括弧つきで呼んでいる)、
} // 受け側の(無名でない)関数、の定義
doit(function(){ // doit を呼ぶときに、無名関数を渡している
console.log("anonymous fnc is called")
})
関数に(無名の)関数を渡すとき、ブレースの内部を独立した行にするために、 上記のような改行と字下げの構造になることが多い。
JavaScriptでは、非同期の機能が多く、
関数を呼び出すときに、次に行うべきことを関数として渡す、
ということが頻繁に行われるため、function(){} の入れ子構造も頻繁に起き、
読みづらいプログラムになることも多い(という点では悪評が高い)。
即時関数として使う。(function(){...})()
のような構文になる。
(function(){
console.log("a")
console.log("b")
})()
無名関数は、function キーワードを使わない記法でも書ける(アロー記法と呼ぶ)。
function(){...}
// ↓
()=>{...}
function
と書く代わりに、括弧の後ろ(右)にダブルアロー(=>
)を書く。前の使用例をダブルアローで書いた例を示す。
doit(()=>{
console.log("anonymous fnc is called")
})
//
(()=>{
console.log("a")
console.log("b")
})()
アロー記法の場合、以下の例のように省略が可能なケースもあり、記述をややシンプルにできる。
function doit(fnc) { fnc("a"); fnc("b"); }
// と定義しておいて、
doit((s)=>{console.log(s)}) // ちゃんと書くとこうだが、
doit(s=>{console.log(s)}) // 引数1つの無名関数は引数を囲む括弧が省略できる
doit(s=>console.log(s)) // 本体が単一の式のときはブレースも省略できる
// のいずれでも大丈夫
とりあえず、functionを使う書き方とアロー(=>
)を使う書き方は、上記のように互換(同じ意味)だと認識してプログラミングを初めていい(今の段階ではそれで支障がない)。
ただ、小さい点で相違があることも頭に隅には入れておくこと。いずれ高度なコードを書くようになるとぶつかる可能性がある。その時はこういうサイトを参考に対処しよう。
puppeteer.launch()
が Promise
型の値を返していたのをご記憶だろうか。
Promise
は、JavaScriptで非同期処理を書きやすくするために後から導入されたデータ型で、
ドキュメント(例えばここには、Promise
を生成する方法なども書かれているが、とりあえず我々は、その生成はライブラリ(Puppeteerも含めて)の内部に任せて、使い方を覚えておけばいいだろう。
ライブラリの関数が返してきた Promise
型の値は、処理が完結していない未解決状態を表している。その処理は、成功することもあれば失敗することもある。まずは成功を前提とした書き方を紹介する。
puppeteer.launch().then(browser=>browser.newPage())
ここでは Promiseオブジェクトに対して then メソッドを呼んでいる。
これまでに(関数の解説で)見てきたように、コールバックは function
キーワード で記述しても、ダブルアローを使っても、関数名を渡してもいい。上の例はダブルアロー =>
を使った簡便な記法を使ったもの。
扱っているオブジェクトの意味を認識しやすいように、ここで browser
というような名前を仮引数に使っている例が(ドキュメントや解説サイトに)多いので上記でもそれに倣ったが、 もちろん慣れたプログラマは b=b.newPage()
のようにより短い書き方をしても構わない。
コールバックは勿論もっと複雑なものであっても構わない。ただし、本体が複数の文から成る場合は本体をブレース { }
で囲み、必ずreturn文を1つ含めて関数の値を返させることが必要になる(上記の例が省略形であったことを思い出されたい)。
この例では、newPage() メソッドは、Promise
コールバックが Promise を返さない時は、then メソッドがそれを察知して、Promise に包んで返す。
いずれにせよ then は Promise を返す ことになる訳だ。
ということは、then が返す値に対して、さらに thenメソッドを呼ぶことも可能になる。
puppeteer.launch()
.then(browser=>browser.newPage())
.then(page=>page.goto("http://www.google.com"))
.then(page=>page.waitforSelector("input [name='q']"))
このように非同期処理を順々に(逐次的に)行わせるために Promise を then で繋いでいく書き方を Promise チェーン などと呼ぶ。イメージ的には オブジェクト指向言語のメソッドチェーンと同じようなもの。
なお、Promiseには catch メソッドも用意されていて、エラー処理に重宝されている。
puppeteer.launch()
.then(...)
.then(...)
.catch(error => console.log(error));
このようにチェーンの最終段に catch を置いておくと、途中で発生した様々な種類のエラーを一括で捕捉してくれるので、各ステップ毎にエラー処理を記述する必要がなくなる(細やかなプログラムにするためには個別の対処も必要になるが、入門の段階ではこのような機能を使って楽をしていいだろう)。
(async()=>{
const browser= await puppeteer.launch()
let page=await browser.newPage()
await page.goto("http://www.google.com")
await page.waitforSelector("input[name='q']")
})();
このように、大枠としてのasync関数(アローによる無名関数定義そのものを括弧で包んでおき、その直後に空の括弧を置くことで、定義した直後に呼出を行っている)の中で、
とりあえず1ステップずつ、await で結果を得て次に進む、という書き方。
中には待たないでいい処理や、互いに並行して実行できる処理もある場合もあるので、 上級の段階になれば細やかに指示する記法を覚えるといいが、当面は(性能は犠牲になるが) この書き方で進めて行こう。