headless chromeをpuppeteerで動かして色々やるときに、重いページを開いた後に諸々エラーが起きるようになった。
[puppeteer browser.close is not function]や[Protocol error(Runtime.evalute): Target closed]、[spawn ENOMEM]など。あらゆるエラーを見た気分…。
初めはbrowser.close is not functionが多かったが、そのうちTarget closedされたり、spawn ENOMEMが出てしまう。
検索すると出てくるawait漏れはない。
以下、色々と調査した記録。
メモリ使用量調査
まずはメモリの使用量が多すぎることが疑われたので、どんな使用状況か調査することにした。
process.memoryUsage();
これでメモリの使用量を調べる。
ただこのまま出すと単位が大きすぎるのでMBにして返す関数を使う。
function getMemory() {
var memory = process.memoryUsage();
var message = [];
for(var key in memory) {
message.push(`${key}: ${Math.round(memory[key] / 1024 / 1024 * 100) /100}MB`);
}
return message.join(' , ');
}
これを落ちやすいポイントに入れて調べる。
見てみると、全体使用メモリ量(rss)が数倍にも膨れ上がり、その後失敗するパターン。ヒープメモリ(heapUsed)は大したことなかった。
となれば、手動でガベージコレククタを実行してメモリを解放するようにすればいい?
あと、このページを開くためのPHPで使ったサーバーの使用量もrssに含まれている?…と思ったけれど、そうだったとしてもPHP側のメモリ使用量を取ってみたところ、こちらが異様に負荷をかけている形跡はなかった。
ガベージコレクタの手動実行
node --expose-gc xxx.js
–expose-gcをつけてファイル実行。実行したい場所は以下のように書く。オプションなし実行の時はガベージコレクタしない。
if(global.gc) {
global.gc();
}
これでようやく落ちなくなると思ったけれど、それでも大きいページを開いた後はクラッシュしやすい。
もう一度メモリ使用量を見てみると、ガベージコレクタ後にヒープメモリは多少サイズが小さくなっていたが、全体メモリはほとんど変わっていない。増大したまま。
ガベージコレクタを連続でかけても変わらない。
基本的にメモリが足りていないらしい。
hedadelss chromeの起動オプションを追加
–disable-dev-shm-usage
を追加。
/dev/shmの使用を禁止して、パーティションが小さすぎることによるクラッシュを回避する…らしい。
これで一時は落ちなくなったと思われたものの、やはり不安定。どちらかというと落ちる。それもspawn型で落ちる。これが起きると本当にどうしようもない…。
browser.close()をマメにやるといいよ、というものも見かけたのでやってみたが、それでも改善しない。
headless chromeで大きいページを開くなということか。
開く予定の重いページを省力化(2022年3月22日追記)
ガベージコレクタと起動オプションを追加してもどうにもならないので、「表示するページそのものの改良」をした。
ここでいう開く予定の重いページというのはHTMLのコード量が多いページのこと。画像が多すぎる、javascriptの実行が多いなどではない。
また、1ページに書き出すHTMLの量が多いと、いくら一部をdisplay:noneにしていても結局落ちることはわかっている。
通常の表示用のコード以外にもjsonを大量に出力しているページでもあったので、この出力を減らして対応した。
本来取りたい1ページを1回で表示できず、spawnさせないために、puppeteerでページクリックして次の取りたいデータへ、と移動して必要なページ内の情報を取っていくことになった。
たとえば取りたいデータがそのページに30件あるとして、30件を1ページに表示してから情報を取ろうとすると高確率でspawnするから、1回アクセスで10件表示にして、2回ページクリックして30件情報を取る、といった流れ。
これで落ちなくなったし、起動オプションの追加やガベージコレクタもやらずに済んだ。
もし色々調べたけどうまくいかない!となったら表示しようとしているページそのものの軽量化(HTMLコード量の削減)を試してみて。
まとめ
起動オプションの追加、ガベージコレクタ、もちろんawait忘れもないのに落ちる!となったら、
表示しようとしているページそのものがHTML量が多すぎていないか確認。出力コード量を減らせるなら減らす。
これまではこういったエラーが(ほとんど?)起きることなく、headless chromeが落ちたら再接続をかけることでうまくいっていた。
なので、オプションやガベージコレクタでどうにかするよりも「表示するページそのものの改良」をした方が早いかもしれない(ここまで思い至るのに色々試していて、すごく時間がかかった)、と思ってやったらうまくいった(2022年3月22日追記)。
HTML量が多いと読み込み切れなくてクラッシュしているのかもしれないので1ページで表示する量を減らして、ページ移動にする、という予想が当たった。
サーバーサイドの改修だけでうまくいけば、nodejsのプログラムは設定を変えたりすることなくページ移動用リンクを判別してあればクリックして…といった処理を書けば事足りる。
情報取得用のプログラムそのものを大幅に変えたり、余計なオプションをつけたりガベージコレクタをいちいち実行しなくてよくなったのは幸いだった。
あと、今回の対象の重いページは自分で調整できるページだったことも。この対象の重いページが外部ページだったらもうどうにもならない。