まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

タグ:bot

Techcrunch の記事によると、3D スキャンデータの販売を行っている Triplegangers というサイトが ChatGPT のサービスを公開している OpenAI のウェブクローラーのクローリング(スクレイピング)が原因で DDoS 攻撃を受けているかのような状態になり、サイトダウンを引き起こしていた可能性がある、と発表されていました:
https://techcrunch.com/2025/01/10/how-openais-bot-crushed-this-seven-person-companys-web-site-like-a-ddos-attack/

記事によると原因となったスクレイピングボットはサイト全体から数十万枚にも及ぶ画像データを全てダウンロードしようとしていた形跡があり、その結果サイトがダウンしてしまった、らしいです。ウェブアプリケーション/ウェブサービスを運用する立場としても他人事ではなく、怖いニュースです。。。

この件に関しては、Triplegangers 側は「事前承諾なしにサイトデータのスクレイピングを禁じる」旨を記載していたようです。が、Techcrunch は「ウェブ上の記載のみで robots.txt の設定は不充分だった」とも指摘している一方、「そもそも robots.txt も(紳士協定が守られる保証はないので)絶対ではない」とも指摘していて、絶対的な回避方法や情報が提供されているわけではありませんでした。

たしかに robots.txt はウェブクローラーに対して「参照していいページ/しないでほしいページ」を指定する方法ではあるのですが、あくまで紳士協定であって、その協定というかルールを守らないクローラーがないとは言いきれません。特に最近は LLM の学習データをウェブから集めようと試みる人が少なくないことが想像できるので、普通のウェブサイトがいつの間にか学習データ提供元になっていたり、今回のような大量のスクレイピング実行されたり(※今回記事になった例ではスクレイピング時に数百もの異なる IP アドレスが使われていたらしく、悪意がなくても悪質なスクレイピングであったと感じています)するとサイトダウンを引き起こしかねないこともあって、小規模な個人サイトであっても気になってしまうものでした。現に生成 AI 検索エンジンの1つである Perplexity のクローラーは過去に robots.txt を無視していたと指摘されている前科もあって、いつ他人事でなくなるかもわかりません。

そこで「robots.txt の指示を守らないクローラーがやってくる前提で、クローラーボットによるインデックシング/スクレイピングをウェブアプリケーションで拒否する」方法を考え、サンプルを作って公開してみました。


【サンプル】
Node.js で作ったサンプルはこちらです:
https://github.com/dotnsf/block-llm-bots

"git clone" などでダウンロードすると、app.jsblock_llm_bots.js という2つの JavaScript ファイルがあることが分かります。アプリケーションとしてクローラーを拒否するモジュールは後者で、前者はそれを組み込んだサンプルとなっています。以下は app.js のコードです:
(app.js)
//. app.js
var express = require( 'express' ),
    app = express();

app.use( express.Router() );

//. LLM クローラーボットブロックを有効にする
app.use( require( './block_llm_bots' ) );

//. .env 内に記載された UserAgent からのリクエストだとこの処理は実行されない
app.get( '/', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  res.write( JSON.stringify( { status: true }, null, 2 ) );
  res.end();
});

var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );

黒字部分がオリジナルのウェブアプリケーションで、赤字部分がそのウェブアプリケーションにクローラー拒否のモジュールを組み込んでいる1行です(緑字はコメント部です)。block_llm_bots.js が同じディレクトリ上にあれば、この1行だけでクローラーブロックが有効になります。

ブロック対象とするクローラーの(UserAgent の)種類を環境変数 BLOCK_BOTS に(複数の場合はカンマ区切りで)指定します。サンプルでは .env ファイル内で以下のように指定されています:
# ブロックしたいボットの UserAgent をコンマ区切りで羅列する
BLOCK_BOTS=PerplexityBot,GPTBot,ChatGPT-User,OAI-SearchBot,Google-Extended

この例では "PerplexityBot,GPTBot,ChatGPT-User,OAI-SearchBot,Google-Extended" と指定されています。この指定で Perplexity(PerplexityBot), OpenAI(GPTBot, ChatGPT-User, OAI-SearchBot), Google(Google-Extended) の3か所の LLM クローラーからのアクセスを拒否しています。

このサンプル(app.js)を使って試しに動作確認してみましょう。Node.js が導入された環境でこのファイル一式をダウンロードし、依存ライブラリをインストールします:
$ npm install

そして(必要に応じて .env ファイルを編集して拒否するクローラーの種類を変更した上で)アプリケーションを起動します:
$ npm start

特に指定しない場合、アプリケーションは 8080 番ポートで HTTP リクエストを待ち受ける状態になります(変更する場合は環境変数 PORT で指定します)。

この状態で(別ウィンドウなどから)curl を使ってアプリケーションにアクセスしてみます:
> curl "http://localhost:8080/"
2025011202


{ "status": true } というアプリケーションの「本来の期待通りの」挙動結果が返ってきました。

では次に OpenAI のクローラーをエミュレートして、User-Agent ヘッダに "GPTBot" を指定してアクセスしてみます:
> curl -H "User-Agent: GPTBot" "http://localhost:8080/"
2025011203


想定通りに GPTBot からのアクセスが遮断された結果、今度は何も表示されませんでした。

この時、サーバー側のコンソールを見ると、"GPTBot" からのアクセスの際にはブロックされた旨が表示されているはずです。期待通りの(特定のボットからのアクセスを遮断するような)挙動が実現できていることが確認できました:

2025011204


このサンプルのように block_llm_bot.js を組み込むか、または同様の拡張をアプリケーションに行うなどして(robots.txt を無視してくるようなクローラーに対しても)スクレイピングを禁止する機能が実現できそうです。自作サービスにも組み込んでおこっと。


とはいえ、本当に悪質な連中だと UserAgent すらも偽装してくるのかなあ、、そういうのはどうするべきなんだろう?


BotKit を使って導入した Slack のボットの挙動を自分なりにカスタマイズしてみましょう。まずは以下のエントリを参考にして、BotKit を導入し、ボットがデフォルトの挙動で動くような状態を作っておいてください:
Slack の BotKit を使う


さて、ボットの実体は bot.js ファイルです。このファイルをテキストエディタで開くと、初期化処理などの後にこのような contoller.hears() の処理がいくつか定義されていることに気付きます:
controller.hears( コマンド, 種類, 処理 );

例えばコマンドとして ['hello','hi'] が定義されているものとして、このような記載を見つけることができます:
controller.hears( ['hello','hi'], 'direct_message,direct_mention,mention', function(bot, message){
       :
  (処理の中身)
       :
} );

これは次のような意味になります:
(1) 'hello' または 'hi' というコマンドを、
(2) 'direct_message'(1対1でのメッセージ)か、'direct_mention'(@XXX hello のように直接メンションされた場合)か、'mention'(hello @XXX のようにメッセージ中でメンションされた場合)に、
(3) function(bot,message){ ... } の内容を実行する

つまり前回のエントリでボットに対して @XXX hello というメッセージをダイレクトメンションで送った時に実行される処理がこれ、ということになります。


では簡単な処理でカスタマイズしてみましょう。bot.js 内に以下のようなコードを追加します:
controller.hears(['double (.*)'],'direct_mention',function(bot, message) {
    var matches = message.text.match(/double (.*)/i);
    var msg = matches[1];
    bot.reply(message,msg+' '+msg);
});

これはボットに対してダイレクトメンションで "@XXX double ABCDE" のようなメッセージが送られた時にハンドルし、"ABCDE ABCDE" のように double に続く部分を2度喋る、という処理をするようなコードです。

この bot.js を保存して実行(実行方法は前回のエントリ参照)し、"@XXX double Happy" のようにダイレクトメンションでメッセージを送ると、"Happy Happy" のような反応をしてくれるはずです:
2016031802


この例では処理内容がかなりシンプルな例でしたが、5行程度の追加でここまでは出来てしまいました。実際には受け取ったパラメータを元にデータベースや外部などから情報を作り出して返す、といったより本格的な Slack ボットを作ることもできると思っています。

応用編というわけではないのですが、REST API を使って外部から情報を取り出して表示する、というサンプルも1つ作ってみました。こちらも同様に bot.js に追加します:
   :
   :
controller.hears(['(usd|eur|gbp|aud|nzd|cad|chf)'],'direct_mention',function    (bot, message) {
  var matches = message.text.match(/(usd|eur|gbp|aud|nzd|cad|chf)/i);
  var cur = matches[0].toUpperCase() + "JPY";
  var request = require( 'request' );
  url = 'http://fx.mybluemix.net/';
  request( url, function( error, response, body ){
    if( !error && response.statusCode == 200 ){
      var json = JSON.parse( body );
      var dt = json['datetime'];
      var rate = json['rate'];
      var p = rate[cur];
      bot.reply( message, dt + ' ' + p );
    }
  });
});

以前作った為替の JSON サービス(http://fx.mybluemix.net/)を使い、ダイレクトメンションで USD(米ドル)EUR(ユーロ)GBP(英ポンド)AUD(豪ドル)NZD(NZドル)CAD(カナダドル)CHF(スイスフラン)のいずれかを受け取った場合、それらの通貨が日本円だといくらになるのか、をリアルタイムに計算して返信してくれる、というボットです:
2016032101


Node.js、というか JavaScript での実装に慣れない人がいるかもしれませんが、比較的簡単な言語だと思うので、この Slack ボットを作る機会と一緒に勉強して慣れておくと面白いと思います。


こんなニュースがありました:
グーグルが「Gmail」の利用規約を更新 - メール内容の解析を明示

グーグルは Gmail に書かれた内容を読んでいる、ということ。
その是非が色々言われているようですが、その件についてコメントは控えます。

僕は知ってました。というか、そういうものだと思ってました。
普段から自分でいくつかのウェブサービスを遊びで開発して公開したりしてます。
ある時、まだ誰にも話してないし、リンクも作っていない作りたてのウェブサイトのURLを Gmail で知り合いに送った所、その数日後から Google のクローラーボットがやってくるようになりました(苦笑)。本当にこのメールがきっかけだったのかどうかは分からないのですが、原因がメール内容しか思い当たらなかったので、以来そういうものだと思っていました。

で、それを逆手にとったというわけではないですが、裏ワザを1つ紹介します。

これから公開しようとしているウェブサービスを開発したら、公開前にわざと GMail にその URL を書いて誰かに(例えば自分の別のアドレスに)送ります。それでグーグルはその新サービスの URL を知ることになるので、数日中にクローラーボットが送り込まれて、グーグルの検索エンジンにインデックスしてくれます。

これで、サービスがスタートする際には既にサービス内の大半のページがグーグル検索結果に表示される状態を作っておくことができます。厳密には、サービススタート前に(キーワード次第では)検索結果に表示されるのはともかく、実際に訪問されては困る、ということもあると思います。そういう場合は UserAgent を見て、グーグルボットの場合だけは訪問を許し、それ以外の場合はエラーにする、という処理を加えておいて、サービス開始直前に無効にする、といった対策で訪問を回避することもできます。


と、まあグーグルが勝手にメールを解析してくれるのであれば、それを勝手に有効活用させてもらいましょう、という方法でした。


 

まずはこの2つのスクリーンショットを見比べてください:

(スクリーンショット1)
2014031401

(スクリーンショット2)
2014031402


どちらもアマゾンのウェブページを同じ Chrome ブラウザで見ている時に取得したものです。全般的に似たような内容ですが、パッと見で「同じではない」ことはわかりますよね。

でもどちらも全く同じページ(同じURL)を指しています。どちらのページもアマゾンのトップページから "ThinkPad" を検索した時の結果ページです。PC 用とモバイル用、というわけでもなさそうですよね。 ではこの違いはなんでしょう?



・・・答は User-Agent の違いでした。(1)は普通の Windows 向け Chrome 標準の User-Agent でアマゾン内を検索したものですが、(2)は Google のクローリングボットの User-Agent(Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)) に偽装してから検索したものです。
2014031403

ここからわかることはおそらくアマゾンはGoogle のクローリングボットが来た時用の、なんらかの手を加えて結果を返すようにしている、ということです。そしてそれはおそらく SEO 対策であろう、と推測されます。

普通に人間がウェブページを見る時に見やすい〈求める)情報と、SEO 対策を重視した画面が同じとは限りません。SEO 的に有利なページを作りたい一方で、ユーザーにはパーソナライズやレコメンドを表示するなど使い勝手を損ないたくもない。そのためにはこのような表示出し分けの対策をしている、ということだと思います。

ちなみに (1) の HTML は約80Kバイト、(2) は約110Kバイトでした。SEO 対策済み(?)のサイトの方が3割程度情報量が多いと考えるべきなのか、人間向けのサイトの方が情報をまとめていると考えるべきなのか。


詳しい内容を調べたわけではないのですが、とりあえずこの2つのページの大きな違いとして、(1) は Shift_JIS 、(2) は UTF-8 で記述されていました。まあ根本的に違うエンジンが使われていると考えるべきでしょうね・・・



このページのトップヘ