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

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

Node.js + Puppeteerで noindex ページを検出し、インデクシングを回避するというお話 

こんにちは、あなたを追跡しないポルノ動画検索エンジン、Sae-Porns管理人のにょろりんこです。

検索エンジンを作成する際、あなたのクローラーは noindex をちゃんと避けてますか?

クローリング/インデクシングする時に、気をつけなければいけないこと。それは noindex 設定になっているページをうっかりインデクシングしないこと。

考えるティアラ

たとえば、運営者が明示的に「検索結果に載せないで」と指定しているページを、こちらの都合でインデクシングしてしまうのは、技術以前にリスペクトの問題。

そのページがどんなに重要な情報を含んでいても、検索対象に含めてしまえば意図しないトラブルを招くことになります。

今回紹介するのは、そんな状況を回避するため、Puppeteerで noindex を検出してスキップする方法。

検索エンジンに限らず、情報収集系のツールやDB構築系スクレイパーなら、これは必ずやっておかなくてはならない処理です。

とはいえ、実はこの noindex 処理、思った以上にやっかいです。

私も最初の頃は、「robots.txt でクロールする/しないを制御しているのだから、robots.txt を見るだけで十分でしょ?」と思ってました。

たしかに robots.txt は、検索エンジンクローラーに「このURLは見に来ないでね」と伝えるための設定ファイルで、たとえばこんな風に書きます:

User-agent: *

Disallow: /private/

これは「すべてのクローラーは /private/ 以下にアクセスしないで」という意味です。

つまり、ページの中身をクロール(=アクセス・解析)するのを禁止する指示になります。

でも、ここにひとつ大きな落とし穴があるんです。

私たちクローラー開発者が robots.txt を見て、「このページはクロール禁止か」と判断してアクセスを避けた場合、そのページの中に書かれている <meta name="robots" content="noindex"> は、そもそも読むことができません。

つまり、「インデックスしてほしくない」というページ作成者の意思が、robots.txt によってかえって届かなくなるケースがあるんです。

私たちがクロールを控えることで、逆に noindex 指定を見落としてしまうという矛盾ですね。

さらに厄介なのが、クロールはしていないのに、検索エンジンが外部リンクなどからそのURLを知ってしまうと、中身を見ずに URL だけをインデックスしてしまうことがある、という点です。

これでは、ページ作成者が意図していた「検索結果に載せないでほしい」という希望が守られません。

こうした事情を踏まえると、単に robots.txt だけを見てアクセス可否を判断するだけでは不十分だということがわかります。

実際の運用では、「このページは見せてもいいけど、インデックスは避けてほしい」といった部分的な制御が非常に多く、私たちクローラー側も、ページを実際に開いて meta タグの noindex 指定を正確に読む処理が求められるんです。

だからこそ、Puppeteerなどを使ってページを実際に開き、現場でタグを確認するような設計が重要になるわけですね。

サンプルコード:メイン側

// main.js

const { isIndexable } = require('./is-indexable');

// ページがインデックス可能かどうか判定(robotsメタのnoindex検出)
const indexable = await isIndexable(page);
console.log(`Indexable 判定: ${indexable}`);

// 解析スキップの判定処理
if (!indexable) {
  console.log('noindex タグ検出。スキップします。');
  await incrementAnalyzed(pool, id); // analyzed = 1 を設定(再解析を防止)
  return;
}

この処理は極めてシンプルですが、クローリングの精度とポリシー尊重の両立という観点では非常に重要な役割を担っています。

サンプルコード:責務分離モジュール

// is-indexable.js

async function isIndexable(page) {
  try {
    const meta = await page.$('meta[name="robots"]');
    if (!meta) return true;

    const content = await page.evaluate(el => el.getAttribute('content') || '', meta);
    return !content.toLowerCase().includes('noindex');
  } catch (err) {
    console.warn(`isIndexable 判定中にエラー: ${err.message}`);
    return true;
  }
}

module.exports = { isIndexable };  

この isIndexable() 関数では、ページに <meta name="robots" content="noindex"> が含まれているかどうかをチェックします。

たとえば content="noindex,nofollow" のようなケースでも、noindex を拾えばスキップ対象になります。

ただし、単に無視するのではなく、DBの urls.analyzed カラムを更新(+1)することで「処理済み」と明示的に記録します。これにより、二重アクセスや再解析を防止する仕組みになっています。

※urlsテーブルには、解析予定のURLが入っています。

つまり、「インデックスすべきかどうか」は isIndexable() で、「解析すべきかどうか」は analyzed カラムで管理するという、責務分離構成です。

責務分離構成にしておけば、将来的に処理が複雑化しても、個別モジュールの修正だけで済むというメリットがありますね。

検索エンジン開発は、小さな判断の積み重ねで品質が決まります。

ページを尊重する姿勢も、そのひとつ。

「俺は俺の責務を全うする」。そんな気持ちで、今日も Sae-Porns を磨いています。

※18歳未満の方はご利用できません

sae-porns.org