📚

Goのスクレイピングライブラリ「rod」でシステムデータが限界突破した話

2024/12/19に公開

何をしてどうなった?

ブラウザごしの作業を自動化したいという軽い気持ちでスクレイピングを始めた。

すごい楽になった。

ほとんどのことをスクレイピングにすればいいんじゃね?と思った。

毎日のタスクをほとんどコードにした。

ストレージの空きがなくなった。

PCが起動できなくなった。

どゆこと?

Goでのスクレイピングライブラリでかなり有名なrodはChromiumを動かしてスクレイピングを簡単にするもので、これを使えば大抵のブラウジングが再現できます。

しかしながら、rodの挙動をあまり理解せずに使ってしまうとrodの挙動を成り立たせるためのキャッシュデータまで気が回ってないと思います。

筆者は便利で最高じゃんというウワベの理解だけで日頃行うブラウザ操作の大半をコードにし、コマンド一発でちょっとした作業を終わらせるようにしていました。

ただ、調子こいて色々やった末、キャッシュデータの存在に気づかず、大量のデータでストレージを圧迫し、ついにほとんどのPC動作に悪影響が及び、最終的にPCがまともに起動しなくなるということが起きました。

この記事では、この出来事の詳細と、その解決方法まで記録しておこうと思います。

状況説明

筆者の開発マシンはmac miniで、ストレージは最小限にとどめています。

こちらがストレージの内訳の画面ですが、rodを使っていくと赤い枠線部分のシステムデータにキャッシュデータが積もっていきます。

PCの動作がとにかくおかしくなって、PCの空き容量がなくなっていますとアラートが表示されていた時はこのシステムデータの量が200GBを超えていました。ストレージ全体容量が256GBなのでかなり致命的な消費です。

そのアラート表示時に、とりあえず再起動をしてみようと実施したところ起動せず、セーフモードでの起動も試そうとしてもやはり起動せずという状況に陥りました。一応、しつこく起動を繰り返していたら起動できるようなっていました。

どうしてシステムデータが急激に増えたのかまだ謎のまま色々調べていくとシステムデータの中のキャッシュデータが怪しいということに気づき始め、最終的にrodのキャッシュデータが大量に保存されていたのを見つけました。

この時の探索方法なのですが、システムデータに該当するディレクトリを一つずつ容量を確認していきながら見つけて行ってます。

コマンドで探そうとしたのですが、permissionの問題もあってうまく探せず手作業のほうがまだ早いという感じでした。こうゆう時に不要なデータを削除するアプリなどがあればよかったのですが、そうゆうことには無頓着だったので一切用意はありませんでした。

rodのキャッシュデータは /private/var/folders/**/**/**/rod というパスに格納されていました。(**は環境によっておそらく変わるであろうハッシュ値のような名称です)

こちらがディレクトリの中身

そのキャッシュデータはこのように1つのローンチ分を1ディレクトリとして保存しているようです。

これら1つのディレクトリがおおよそ11MBとなっており、筆者の状況ですとこのディレクトリが大量に積もり、最終的には200GBまで増えていたということになりそうです。

このディレクトリを特定した後、rodで複数のブラウザをローンチしてみるとディレクトリが増えていきシステムデータを圧迫していくという確認もできました。

rodディレクトリを探し出す場合はこちらのコマンドを利用しています。

find /private/var/folders -type d -name "rod" 2>/dev/null

以下からはコードの解説です。

対策前のコード

このようなコードで起動させます。
なお、rodは v0.116.2 を利用しています。

func Launch(isDebug bool) *rod.Page {
	var l *rod.Launcher
	if isDebug {
		//debugモード
		l = launcher.New().Headless(false).Devtools(true)
	} else {
		//通常モード
		l = launcher.New().Headless(true)
	}
    url := l.MustLaunch()
    browser := rod.New().ControlURL(url).MustConnect()

	// ステルスモードに変更
	page := stealth.MustPage(browser)

	return page
}

キャッシュなどを残さない方が確実に作業完了できることが経験上あったのでステルスモードを利用しています。そして page という変数を使って各種ブラウジングしていくという流れです。

基本的にこれでほとんどのことはできるのですが、先述したrodのキャッシュデータというものが動作時にシステムデータとして保存されていきます。

対策後のコード

func Launch(isDebug bool) *rod.Page {
	var browser *rod.Browser

	// クリーンアップオプションを設定
	l := launcher.New().
		Delete("--user-data-dir") // セッション終了時にユーザーデータを削除

	if isDebug {
		//debugモード
		l = l.Headless(false).Devtools(true)
	} else {
		//通常モード
		l = l.Headless(true)
	}
	url := l.MustLaunch()
	browser = rod.New().ControlURL(url).MustConnect()

	// ステルスモードに変更
	page := stealth.MustPage(browser)

	return page
}

このように Delete("--user-data-dir") をつけたことでキャッシュデータを作成しないようにしました。システムデータの圧迫もしないようです。

ステルスで起動するのを共通化したかったため*rod.Pageを返却するのは変更しませんでしたが、*rod.launcherを返却するようにすれば呼び出し側でdeferを使ってCleanUp()すれば同様の動作をするようです。

終わりに

スクレイピングは便利には便利なのですが、それを実現するChromiumのライブラリ自体の挙動を知らずに使ってしまうと筆者のようなことがあるかもしれないと思い記事にしました。

状況発生時はかなりパニックになり、最悪マシンを修理に出す必要があるかもとかなり不安になりました。そうゆう罠に陥らないように注意しましょうという教訓を得られてよかったです。

Discussion