ぽっぺんしゃんにょろりんこ

匿名・非追跡型アダルト動画検索エンジンの設計ノート

Node.js + Puppeteerで不要リソースをブロックし、軽量&安全にページを取得するという話

こんにちは、にょろりんこ的、備忘録技術ブログです。

以前に「インデックスする必要のないページは最初から開かない」という話をしましたが、今回は逆に「開いたページでも、いらないものは一切読み込まない」という話です。

 

関連記事

pop-ancient.hatenablog.com

pop-ancient.hatenablog.com


特に動画サイトには、画像・スタイル・フォント・外部スクリプトなど、不要な通信のかたまりが大量に含まれています。

Sae-Pornsでは、これらを一網打尽にブロックすることで、圧倒的な軽量化を実現しています。

これは「ただのページ遷移」ではありません

本来、fetchElements() は以下のような1行のコードだけで済む処理です:

await page.goto(url);

しかし、これでは現実的に まったく足りません。特に動画サイトのように、

といった「帯域を食い尽くす要素」が山ほどある環境では、ただ単にページを開くだけでは、遅いし、重いし、エラーも起きやすい。

そこで Sae-Porns では、この page.goto() をベースに、次のような処理をひとまとめにしたラッパー関数として fetchElements() 関数を設計しています。

これには以下のような処理を盛り込み、page.goto() をより強化しています。

  • 不要な外部ドメインの遮断(DB連携)
  • 画像・フォント・動画などの静的リソースを一括ブロック
  • 通信が安定するまで待機してから処理を継続
  • 失敗時は null を返すことで、後続処理の安全性を担保

つまりこれは、ただのページ遷移ではなく、「余計なものは全部シャットアウトして、安全・高速・軽量にHTMLだけ取得する」ための入口処理なのです。

ティアラ_青空と

fetchElements() 関数の解説

では、実際のコードをもとに解説していきます。

この関数は、1ページに対して1回だけ使うべき設計になっており、PuppeteerのPageインスタンスを受け取って、指定URLへ遷移しつつ不要なリソースを遮断します。

① ブロックリストはMySQLから取得

async function fetchElements(page, url) {
  try {
    console.log(`Navigating to ${url} ...`);

    // DBからブロックドメインを取得
    const results = await pool.query(`
      SELECT domain FROM blocklist_domains WHERE enabled = 1
    `);
    const blockedDomains = results.map(row => row.domain.toLowerCase());

ここでは、blocklist_domainsテーブルからenabled = 1のドメインを抽出しています。
ポイントは、プログラム内にハードコードしていないということ。

ブロック対象をDBで管理することで、後からWebインターフェースやスクリプトで柔軟に更新できるようにしています。

② Puppeteerのリクエスインターセプトでブロック

    // 不要ドメインをブロック(ワイルドカード対応)
    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();
      }
    });

この部分がブロックの本体処理です。

ブロックの判定基準は以下の2つ:

  • hostname が blocklist に含まれる(サブドメイン含む末尾一致)
  • リソースタイプが image, stylesheet, font, media のいずれか

たとえば fonts.googleapis.com がDBに登録されていれば、fonts.googleapis.com も sub.fonts.googleapis.com も、末尾一致でブロックされます。

endsWith(domain) によって 簡易的なワイルドカードとして機能しているのがポイントです。

③ ページ遷移処理

    // ページ遷移(描画安定まで待機)
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 120000 });

    return page;
  } catch (err) {
    console.error(`ページ遷移エラー: ${err.message}`);
    return null;
  }
}

ここでページ遷移を実行します。networkidle2は「2本以下の通信状態が500ms以上続いたら描画完了とみなす」条件で、ある程度の安定表示を期待できる設定です。

タイムアウトを長め(120秒)にしている理由

動画サイトはサードパーティ通信が多く、完了が遅くなるケースもあります。ただし、上記のブロックが効いていれば、ほとんどのケースで数秒で完了しています。

また、最後をreturn null にしているのは、エラー発生時に後続処理を安全にスキップさせるためです。

すべて成功すれば、この時点で「リソースを最小限にした状態のpageオブジェクト」が完成します。

この page を使って、後続の fetchTitle や fetchDuration、fetchImage などを無駄な通信をせずに実行できます。

地味な最適化こそが命綱

毎日1,000件以上のページと向き合う処理系にとっては、こうした基礎こそが命綱です。

fetchElements() は、ただのページ遷移ではありません。「いらないものは見ない・読まない・触らない」という、Sae-Pornsの設計哲学そのものです。

それでは皆さま、よき開発ライフを。

※このコードが実際に使われている追跡されないアダルト動画の検索エンジンSae-Pornはこちら!よかったら見ていってください。(18歳未満の方はご利用できません。)

sae-porns.org