Trackback Spam を防ぎたい

書いた人: noriaki 2007,08月02日(木) 22:40

ブログを書いているとコメントスパムやトラックバックスパムとの闘いは避けられないんですが,あまりにもひどいのでなんとか防いでみようと思い立ちました.その際の考え方や,利用しているブログシステム「typo」に追加した機能などをご紹介します.

現状

まず,現状のスパム対策はどうなっているかを説明します.スパムには大きく分けてコメントスパムとトラックバックスパムが存在するわけですが,どちらも誘導したいWebページのリンクを一方的にこのブログに貼り付けていきます.

コメントスパムは実のところほとんどありません.これは,このブログがjavascriptを利用したコメントシステムを採用しており,スパマーが使っているであろうツールはjavasriptを理解しないためだと思います.

一方,トラックバックスパムは毎日100件のオーダーで届いています.各エントリにほとんど表示されていないのは,デフォルトで付いているAkismetプラグインを利用して自動判定を行っているためです.

問題点

しかし,Akisumetはスパムかどうかの判定はしてくれるのですが,それが本当にスパムかどうか最終的に判断するのは私であり,スパムと判断したものを削除するのも私なので,毎日確認と削除を行う手間がかかってしょうがないのです.

解決策

ここで,トラックバックの意義を考えたとき,トラックバックを送信した側のブログエントリにはトラックバックを受信したブログエントリへのリンクが存在するはずです.そこで,ブログシステム側でトラックバック送信ブログにリンクが存在するかどうかをチェックすることによってトラックバックスパムをそもそもはじいてしまうことにしました.

具体的な施策

基本的な考え方は,トラックバックの ping を受信したら,トラックバック登録する前に送信元のエントリを取得して,リンクとしてエントリURLが存在しているかチェックします.さらに,各エントリのトラックバックURLの表示部分にリンクの有無を調べることを書いておけば,スパムじゃないトラックバックを送ってくださる人は通るでしょう.

とりあえず,typoではトラックバックの受信をどうやってるのか調べました.typoのディレクトリでgrep-findしてみると,app/controller/articles_controller.rb に trackback アクションを発見.

app/controller/articles_controller.rb

  # Receive trackbacks linked to articles
  def trackback
    @error_message = catch(:error) do
      if params[:__mode] == "rss"
        # Part of the trackback spec... will implement later
        # XXX. Should this throw an error?
      elsif !(params.has_key?(:url) && params.has_key?(:id))
        throw :error, "A URL is required"
      else
        begin
          settings = { :id => params[:id],
                       :url => params[:url],      :blog_name => params[:blog_name],
                       :title => params[:title],  :excerpt => params[:excerpt],
                       :ip  => request.remote_ip, :published => true }
          this_blog.ping_article!(settings)
        rescue ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid
          throw :error, "Article id #{params[:id]} not found."
        rescue ActiveRecord::RecordInvalid
          throw :error, "Trackback not saved"
        end
      end
      nil
    end
  end

どうやら,app/models/blog.rb 内の ping_article! メソッドを呼んでるみたいです.

app/models/blog.rb

  def ping_article!(settings)
    settings[:blog_id] = self.id
    article_id = settings[:id]
    settings.delete(:id)
    trackback = published_articles.find(article_id).trackbacks.create!(settings)
  end

そこで,blog.rb 内にトラックバック送信元ページにリンクが存在するかチェックするメソッドを追加し,ping_article! 内で呼び出すようにしましょう.チェックしてリンクが存在しなければエラーにして,articles_controller.rb の trackback アクションでエラーをキャッチするという方針でいきます.

app/models/blog.rb

  class TrackbackSpam < StandardError; end ## Error class

  def ping_article!(settings)
    settings[:blog_id] = self.id    article_id = settings[:id]
    settings.delete(:id)
    article = published_articles.find(article_id)
    raise Blog::TrackbackSpam unless check_link_exist(article, settings[:url]) ## check link exist
    trackback = article.trackbacks.create!(settings)
  end

  # checking article link existance
  def check_link_exist(article, url)
    return nil unless /^http/ =~ url
    article_url = URI.parse(settings['canonical_server_url']) + article.location
    regexp = Regexp.new(%q(href=['"]?) + Regexp.quote(article_url.to_s) + %q(["']?))
    doc = open(url).read.toutf8
    regexp =~ doc
  end
  private :check_link_exist

例外クラスの Blog::TrackbackSpam を追加し,トラックバックを送信してきたブログ内にリンクが存在するかどうかを調べます.

自分のブログエントリのURLを取得するのにsettings・・みたいな手間のかかることをやっているのは,普通はうまく絶対パスでURLを取得できる以下の方法が上手くいかなかったからです.

article.location(nil, false)

また,正規表現で探しているのはなぜかというと,最初はREXMLで読もうと思ったのですが,ルーズなHTMLを解析できなかったので諦めました.Hpricotとかを利用した方がパフォーマンスは良さそうですね.

app/controller/articles_controller.rb

  # Receive trackbacks linked to articles
  def trackback
    @error_message = catch(:error) do
      if params[:__mode] == "rss"
        # Part of the trackback spec... will implement later
        # XXX. Should this throw an error?
      elsif !(params.has_key?(:url) && params.has_key?(:id))
        throw :error, "A URL is required"
      else
        begin
          settings = { :id => params[:id],
                       :url => params[:url],      :blog_name => params[:blog_name],
                       :title => params[:title],  :excerpt => params[:excerpt],
                       :ip  => request.remote_ip, :published => true }
          this_blog.ping_article!(settings)
        rescue ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid
          throw :error, "Article id #{params[:id]} not found."
        rescue ActiveRecord::RecordInvalid
          throw :error, "Trackback not saved"
        rescue Blog::TrackbackSpam
          throw :error, "Link not exist in sent trackback page"
        end
      end
      nil
    end
  end

変更点は以下を追加したことです.その後,typo を再起動しました.

        rescue Blog::TrackbackSpam
          throw :error, "Link not exist in sent trackback page"

今までは一日100件以上来ていたトラックバックスパムが明日になってどうなっているか楽しみです.また結果をご報告しますので,お楽しみに.

あ,エントリページのトラックバック欄を直すの忘れてますね.それは,また次回.

このエントリをdel.icio.usにブックマークしているユーザ数このエントリをdel.icio.usに追加する
このエントリをはてなブックマークしているユーザ数このエントリをはてなブックマークに追加する
 | Tags ,

コメント

  1. hidetox said 約1分 later:

    http://wizardbible.org/32/32.txt 文字参照を使う方法はマニアックかつ手軽でよさそうです。

  2. noriaki said 約13分 later:

    hidetoxさん

    コメントありがとうございます.

    文字列参照を埋め込むんですかー.マニアックですね(笑)でも,javascriptを有効にしていない人にもコメントしてもらえるのでアクセシビリティが高まりそうですね.

このエントリはアーカイブされています。
コメントする場合は、お手数ですが「このページのURL」を記載した上で、新しいエントリにお願いします。