こんにちは、にょろりんこの備忘録的な開発ブログです。
今日は「プロキシの使用量明細をもとに、アクセス不要なドメインを見極めてブロックする」という、地味ながらも効果絶大な最適化のお話です。
Puppeteerを使ってクローリングをしていると、ページにアクセスするたびに大量のリクエストが飛びます。
その中には、ページの表示や動画の取得に本当に必要なリソースだけでなく、分析用スクリプトや広告、外部フォントなど、アクセスしなくてもページが成り立つようなドメインも含まれています。
プロキシの使用量明細(usage report)を見ていると、どのドメインにどれだけ帯域を使ったかがわかります。
このうち、明らかに不要なドメインはブロック対象として扱うことで、プロキシの帯域を節約できますし、ページの読み込みスピードも向上します。
例えば、以下のようなドメインはブロックしてOKです:
fonts.googleapis.com(フォント。表示に関係なし)
www.googletagmanager.com(トラッキング)
mc.yandex.ru(ロシア系のアクセス解析)
pagead2.googlesyndication.com(広告)
web.static.mmcdn.com(ポルノCDNの外部JS。必要ない場合が多い)
*.doubleclick.net(広告関連)
実際には、これらが無くても抽出可能かどうか1つ1つ確かめる必要がありますが、こういった地味な開発がスピード向上や帯域削減の改善に役に立つわけです。ちりも積もればなんとやらですね。

この情報、どうやって使うの?
これらをMySQLの 例えばblocklist_domains という名前のテーブルに保存しておき、Puppeteerのpage.on('request') でリクエストをフックし、ワイルドカード含めてブロックするという構成にします。
このようにしておくことで、
・そもそもアクセスしないのでスピードが向上
・あとからドメインを追加・削除しやすい
・再デプロイ不要でブロック設定を反映できる
・ドメイン単位でアクセスを止められるため、帯域節約に直結する
といった利点があります。
「再デプロイ不要でブロック設定を反映できる」とは
コードを変更したり再起動したりせずに、ブロック対象のドメインを追加・変更できるという意味です。
具体的にはこういうことです:
sae-pornsの仕様ではブロックするドメインを MySQLの blocklist_domains テーブルに保存しているので、fetch-elements.js は毎回 DBからリアルタイムでブロック対象を読み込こんでいます。
だから、あとから新しいドメイン(例:ads.example.com)をテーブルに追加するだけで、Puppeteerを再起動せずに、そのドメインのリクエストが即ブロックされるというわけです。
しかし、初期はブロック対象を コードにハードコーディングしていたため、「再デプロイが必要な構成」な構成になっていました。
再デプロイが必要なコード例
const blockedDomains = ['ads.example.com', 'fonts.googleapis.com']; // ←手動で書いてる
DBを使わずにコードにハードコーディグにしてしまうと:
・ドメインを増やすたびにコードを編集
・サーバーに再アップロード(デプロイ)
・サーバー再起動 or プロセス再起動(PM2等)
という手間が発生します。
なので、コードとしては以下のようにします。
※enabledカラムでフラグ管理
//main.js
※メインの方ではブロック対象ドメインをセットアップする関数を呼ぶだけ。
const { setupRequestBlocking } = require('./fetch-elements');
await setupRequestBlocking(page);
//fetch-elements.js 責務分離版、全体エレメントを抽出
//責務分離版、全体エレメントを抽出する前に書く// DBからブロックドメインを取得
const results = await pool.query(`
SELECT domain FROM blocklist_domains WHERE enabled = 1
`);
const blockedDomains = results.map(row => row.domain.toLowerCase());// 不要ドメインをブロック(ワイルドカード対応)
await page.setRequestInterception(true);
page.on('request', req => {
const hostname = (new URL(req.url())).hostname.toLowerCase();
const type = req.resourceType();const shouldBlock = blockedDomains.some(domain => hostname.endsWith(domain));
if (
shouldBlock ||
['image', 'stylesheet', 'font', 'media'].includes(type)
) {
req.abort();
} else {
req.continue();
}
});
それでは皆さま、よき開発ライフを。
※このコードが実際に使われている「追跡されないアダルト動画の検索エンジンSae-Porns」はこちら!よかったら見ていってください。(18歳未満の方はご利用できません。)