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

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

Node.jsで、もうクロールしないURLと、またクロールするURLを分けてフラグ管理する話

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

今回は、私が自作している Node.js 製のクローラーの中で使っている flag-update.js という小さなファイルについて紹介します。

ティアラ_手をマルにする

このファイルの役割はとてもシンプルで、対象のURLの「処理状況フラグ(visitedカラム)」を更新するだけです。しかし、クローラー全体の挙動をコントロールするうえで、この処理は意外と重要な意味を持っています。

まず前提として、Saepornsのクローラーでは、MySQLのurlsテーブルにある visited カラムで、各URLの処理状態を管理しています。処理対象かどうかを判別するためのフラグとして使っていて、意味は以下のように決めています。

  • visited = 0:まだ処理していないURL(初期値)
  • visited = 1:処理が完了したURL、または404などで二度と処理しないと判断したURL
  • visited = -1:一時的な問題でスキップされたURL(あとで再処理したい)

flag-update.js にはこの visited フラグを更新する関数が2つだけ定義されています。コードとしては以下のようになります。シンプルなコードですね。

//flag-update.js
/**
 * URLsテーブルの visited カラムを更新するためのモジュール
 * - visited =  1 → 正常処理済み
 * - visited = -1 → ブロック・スキップ・エラー等
 */

const VISITED_NORMAL = 1;
const VISITED_SKIPPED = -1;

async function markUrlVisited(pool, id) {
  try {
    await pool.query(
      'UPDATE urls SET visited = ? WHERE id = ?',
      [VISITED_NORMAL, id]
    );
    console.log(`visited=1 に更新しました (ID=${id})`);
  } catch (err) {
    console.error(`markUrlVisited エラー: ${err.message}`);
  }
}

async function markUrlSkipped(pool, id) {
  try {
    await pool.query(
      'UPDATE urls SET visited = ? WHERE id = ?',
      [VISITED_SKIPPED, id]
    );
    console.warn(`visited=-1 に更新しました (ID=${id})`);
  } catch (err) {
    console.error(`markUrlSkipped エラー: ${err.message}`);
  }
}

module.exports = {
  markUrlVisited,
  markUrlSkipped
};

ひとつは markUrlVisited(pool, id) という関数で、正常に処理が完了したときにそのURLの visited を 1 に更新します。もうひとつは markUrlSkipped(pool, id) という関数で、ページの取得に失敗した場合や、IPブロック、タイムアウトなどによって一時的にアクセス不能だったURLに対して visited = -1 を設定します。

重要なのは、visited = -1 にしたURLは「完全にあきらめる」のではなく、「あとでまた見るかもしれない」と判断して一時的に保留しているという点です。

たとえば、429エラー(リクエスト過多)や、「空のページが返ってくる現象」は、時間を置けば再び見れるようになるケースが多いです。そういった場合、ドメインを一時的にクールダウン(アクセス停止)リストに入れつつ、対象URLの visited を -1 にしておきます。

一方で、明らかにもう見る必要がないURL、たとえば404エラーやURL構造上のミスなどは、visited = 1 にして処理済み扱いにしています。

こうしてフラグの値を分けておくことで、「再挑戦すべき失敗」と「永久にスキップすべき失敗」をコード上で明確に分離することができます。

visited = -1 にしたURLについては、夜間に走らせているCRONジョブによって、毎日一度 visited = 0 に戻しています。つまり一度スキップされたURLでも、翌日以降のクローリング対象として自動的に復帰する設計です。

CRONジョブはこういう感じ

# visited=-1 を visited=0 に戻して再処理対象にする(毎朝5:00)
0 5 * * * node /var/www/html/reset-visited-minus1.js >> /var/log/reset-visited.log 2>&1

reset-visited-minus1.jsは以下

// reset-visited-minus1.js
// visited = -1 のURLを再度 visited = 0 に戻す(再試行キュー復帰)

const mysql = require('mysql');
const util = require('util');

// DB接続設定
const pool = mysql.createPool({
  connectionLimit: 10,
  host: '*****',
  user: '*****',
  password: '*******',
  database: '****'
});
pool.query = util.promisify(pool.query);

(async () => {
  try {
    const [result] = await pool.query(`
      UPDATE urls
      SET visited = 0
      WHERE visited = -1
    `);
    console.log(`visited=-1 → 0 に変更: ${result.affectedRows} 件`);
  } catch (err) {
    console.error(`エラー: ${err.message}`);
  } finally {
    pool.end();
  }
})();

 

これにより、突発的なブロックやネットワーク障害に対しても柔軟に対応でき、再クローリングのチャンスを確保しつつ、無限リトライによる無駄も避けられます。

このように、flag-update.js は一見するとただのSQL発行用ユーティリティに見えますが、クローラー全体の再試行制御や安定運用にとって非常に重要な「状態管理の中核」を担っているファイルです。

地味だけど大事な処理。それが、今回紹介した flag-update.js でした。

それではみなさん、よい開発ライフを。

このコードを実際に使って動いている、あなたを追跡しないアダルト動画検索エンジン「Sae-Porns」はこちら

※18歳未満の方はご利用になれません。

sae-porns.org