scRUBYt!を使う


scRUBYt! は Web Spidering & Scraping ツールです。スクリプト言語 Ruby のライブラリです。 Web ページを取得/変換し、情報を取り出すツールの作成に使用します。ライセンスは GPL v2 です。

ここでは、 Windows におけるインストールの注意点、パッチ、トラブル、実例(の示唆)を提供します。使用した scRUBYt! のバージョンは 0.3.4 です。

詳細な解説はありません。経験の提供を目的としています。

はじめに


scRUBYt! の概略は google で検索するか、 @IT の記事「進化する“Webスクレイピング”技術の世界」を参照してください。

簡単に説明すると、 html から XPath で参照点を指定しデータを取り出すライブラリです。 developerWorks の古い記事「Webベースのデータ・マイニング」をご存知の方は想像しやすいかもしれません。

以下、説明で使用する %RUBY_HOME% は Ruby バイナリをインストールしたディレクトリです。

インストール


gem でインストールします。

本家の解説はこちら。

Installation Instructions - Scrubyt
http://wiki.scrubyt.org/index.php?title=Installati...

用意するもの


バージョンは現時点(2008/11/13thu)で動作している環境です。後述するパッチをあてています。
  • Ruby バイナリ
    • ActiveScriptRuby 1.8.7 (2008-08-11 patchlevel 72)
  • Ruby 拡張開発ライブラリ
    • asr に同梱
  • Ruby をビルドしたコンパイル環境
  • scRUBYt! 0.3.4
  • 依存ライブラリ( gem を使う場合は自動でインストールされます。依存ライブラリの依存ライブラリは明記してません)
    • hpricot 0.6.164-x86-mswin32
    • mechanize 0.8.4
    • ParseTree 3.0.2
    • ParseTreeReloaded 0.0.1
    • RubyInlineAcceleration 0.0.1
    • ruby2ruby 1.2.1
    • RubyInline 3.8.1

scRUBYt! が依存するライブラリ ParseTree が RubyInline に依存し、これが C コンパイラを要求します。 Windows 版 Ruby は rbconfig.rb などの制約から、 Ruby のビルドに使用した環境でなければ拡張ライブラリを作成できません。 ActiveScriptRuby 向けの拡張ライブラリを作成する場合は vc6 環境を用意してください。 vc6 などの商用もしくは古い環境を用意できない場合は、フリー開発環境でビルドされた Ruby バイナリを用意し、フリー開発環境をセットアップしてください。異なる開発環境でも rbconfig.rb を書き換え *.lib を再作成すれば誤魔化せるかもしれませんが(僕は ActiveScriptRuby でやりました)、方法は説明しません。躓く人が多いようなので、説明します。

なお、 ParseTree の so ファイルが作成されたあとは、コンパイラは不要です。 %INLINEDIR% ディレクトリに作成された so ファイルをコピーすれば、コンパイラのない環境でも scRUBYt! を利用できます(todo 再確認)。

ParseTree 3.x.x 以降、 gem install で Windows 用にビルドされたパッケージがインストールされるようになりました。現在は scRUBYt! を使用する範囲ではコンパイル環境は不要です。以下に説明する設定も(おそらく)不要です。

gem install


scRUBYt! 0.3.4 は RubyInline のバージョン 3.6.3 を要求するのですが、このバージョンはダウンロード出来ない(2008/5/17ごろ)ので -f オプションをつけて強引にインストールします。 scRUBYt! をインストールする前に gem install -v 3.6.3 RubyInline すればいけます。
gem install -f scrubyt

Ruby の gem ライブラリ ディレクトリ %RUBY_HOME%/lib/ruby/gems/1.8/gems に潜って、依存ライブラリにユニットテストが用意さているか確認してください。 test ディレクトリがあれば、その下のテスト スクリプトを実行し、テストが通ることを確認します( RubyInline のテストは %TEMP% の下にゴミを作るかもしれない。それに mkdir でトラブって動かなかったような...)。インストールと同時にテストする場合は -t オプションをつけてインストールします。

環境設定


依存ライブラリ RubyInline が環境変数 INLINEDIR を参照します。環境変数 INLINEDIR はコンパイルに使用するディレクトリです。ソース、ビルドした拡張ライブラリなどが保存されます。

適当な場所(たとえば %RUBY_HOME%/lib/ruby/gems/1.8/gems/.ruby_inline )を決め、環境変数 INLINEDIR に設定します。

パッチ


RubyInline が caller の処理で windows のファイル パスの構造を考慮してないため問題が発生します。
--- inline.rb.orig 2008-06-21 00:39:00 +0900
+++ inline.rb  2008-06-21 00:39:00 +0900
@@ -253,7 +253,8 @@
                 0
               end
 
-      file, line = caller[1].split(/:/)
+#      file, line = caller[1].split(/:/)
+      file, line = caller[1].scan(/(.+):(\d+)(?=$|:)/).first
       result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless $DEBUG and not $TESTING
 
       @src << result
@@ -295,7 +296,8 @@
       raise "Couldn't discover caller" if stack.empty?
       real_caller = stack.first
       real_caller = stack[3] if real_caller =~ /\(eval\)/
-      real_caller = real_caller.split(/:/, 3)[0..1]
+#      real_caller = real_caller.split(/:/, 3)[0..1]
+      real_caller = real_caller.scan(/(.+):(\d+)(?=$|:)/).first
       @real_caller = real_caller.join ':'
       @rb_file = File.expand_path real_caller.first
 
@@ -429,7 +431,7 @@
                   '-I', hdrdir,
                   config_hdrdir,
                   '-I', Config::CONFIG['includedir'],
-                  "-L#{Config::CONFIG['libdir']}",
+#                  "-L#{Config::CONFIG['libdir']}",
                   '-o', so_name.inspect,
                   File.expand_path(src_name).inspect,
                   libs,

scRUBYt! が要求する RubyInline のバージョンが合わないため、 gem のバージョン チェックでエラーになります。 RubyInline の新しいバージョンを使用するには、要求するバージョンを 3.6.3 固定ではなく 3.6.3 かそれ以上に変更します。 %RUBY_HOME%/lib/ruby/gems/1.8/specifications/scrubyt-0.3.4.gemspec を下のように書き換えます。
s.add_dependency(%q<RubyInline>, [">= 3.6.3"])

ParseTree がコンパイル オプション -Werror を付けて、これがエラーになる場合は parse_tree.rb の当該箇所をコメントアウトします。 extern もエラーになるかもしれない( ParseTree 3.0.2 では対処されている)。
--- parse_tree.rb.orig	2008-06-15 11:19:27 +0900
+++ parse_tree.rb	2008-11-08 15:25:27 +0900
@@ -288,7 +288,7 @@
     # 1) Get me a login on your box so I can repro this and get it fixed.
     # 2) Fix it and send me the patch
     # 3) (quick, but dirty and bad), comment out the following line:
-    builder.add_compile_flags "-Werror"
+#    builder.add_compile_flags "-Werror"
 
     builder.prefix %{
         #define nd_3rd   u3.node
@@ -1051,7 +1051,7 @@
 }
 }
 
-    builder.prefix " extern NODE *ruby_eval_tree_begin; " \
+    builder.prefix " RUBY_EXTERN NODE *ruby_eval_tree_begin; " \
       if RUBY_VERSION < '1.9.0'
 
     builder.c %Q{

vc7tk

vc7tk で asr の拡張ライブラリをビルドするためのパッチです。

config.h のパッチです。
--- config.h.orig       2007-03-17 11:54:54 +0900
+++ config.h    2008-06-14 18:03:07 +0900
@@ -1,6 +1,8 @@
+/*
 #if _MSC_VER != 1200
 #error MSC version unmatch
 #endif
+*/
 #define STDC_HEADERS 1
 #define HAVE_SYS_TYPES_H 1
 #define HAVE_SYS_STAT_H 1

vcvars32.bat の設定例です。 config.h の変更でコンパイルできるはずですが、念のため。
rem vcvars32.bat - vc7tk
rem @echo off

rem call D:\EXPF\Mingw\cmd.bat

set VC7TKInstallDir=D:\EXPF\Microsoft Visual C++ Toolkit 2003
set VC7TKMSVSDir=%ProgramFiles%\Microsoft Visual Studio .NET 2003\Vc7

Set PATH=%VC7TKInstallDir%\bin;%PATH%
Set INCLUDE=%VC7TKInstallDir%\include;%VC7TKMSVSDir%\include;%INCLUDE%
Set LIB=%VC7TKInstallDir%\lib;%VC7TKMSVSDir%\lib;%LIB%

rem psdk
set PSDKInstallDir=D:\EXPF\PSDK4WS2003R2
call %PSDKInstallDir%\SetEnv.Cmd /2000 %*
set INCLUDE=%MSSdk%\include\mfc;%INCLUDE%
set INCLUDE=%MSSdk%\include\atl;%INCLUDE%
set INCLUDE=%MSSdk%\include\crt;%INCLUDE%
mingw

mingw で asr の拡張ライブラリをビルドするためのパッチです。 RubyInline の実行に必要な程度の変更です。 rbconfig.rb は hpricot に付属する extras/mingw-rbconfig.rb を使うほうが簡単かもしれない...(未確認)

vc7tk と同じく config.h を変更します。

RubInline の inline.rb の当該行をコメントアウトします。
        " -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
      when /mingw32/ then

rbconfig.rb のパッチです。
--- rbconfig.rb.orig	2008-05-15 16:17:22 +0900
+++ rbconfig.rb	2008-06-22 12:57:45 +0900
@@ -22,12 +22,12 @@
   CONFIG["SHELL"] = "$(COMSPEC)"
   CONFIG["BUILD_FILE_SEPARATOR"] = "\\"
   CONFIG["PATH_SEPARATOR"] = ";"
-  CONFIG["CFLAGS"] = "-MD -Zi -O2b2xg- -G6"
+  CONFIG["CFLAGS"] = "-O2 -mtune=pentium3"
   CONFIG["CPPFLAGS"] = ""
   CONFIG["CXXFLAGS"] = ""
   CONFIG["FFLAGS"] = ""
   CONFIG["LDFLAGS"] = ""
-  CONFIG["LIBS"] = "oldnames.lib user32.lib advapi32.lib wsock32.lib "
+  CONFIG["LIBS"] = "-luser32 -ladvapi32 -lwsock32 "
   CONFIG["exec_prefix"] = "$(prefix)"
   CONFIG["bindir"] = "$(exec_prefix)/bin"
   CONFIG["sbindir"] = "$(exec_prefix)/sbin"
@@ -56,8 +56,8 @@
   CONFIG["target_cpu"] = "i386"
   CONFIG["target_vendor"] = "pc"
   CONFIG["target_os"] = "mswin32"
-  CONFIG["CC"] = "cl -nologo"
-  CONFIG["CPP"] = "cl -nologo -E"
+  CONFIG["CC"] = "gcc"
+  CONFIG["CPP"] = "gcc -E"
   CONFIG["YACC"] = "byacc"
   CONFIG["RANLIB"] = ""
   CONFIG["AR"] = "lib -nologo"
@@ -67,14 +67,14 @@
   CONFIG["CP"] = "copy > nul"
   CONFIG["ALLOCA"] = ""
   CONFIG["DEFAULT_KCODE"] = ""
-  CONFIG["OBJEXT"] = "obj"
+  CONFIG["OBJEXT"] = "o"
   CONFIG["XCFLAGS"] = "-DRUBY_EXPORT -I. -I./.. -I./../missing"
   CONFIG["XLDFLAGS"] = "-stack:0x2000000"
   CONFIG["DLDFLAGS"] = "-link -incremental:no -debug -opt:ref -opt:icf -dll $(LIBPATH) -def:$(DEFFILE) -implib:$(*F:.so=)-$(arch).lib -pdb:$(*F:.so=)-$(arch).pdb"
   CONFIG["ARCH_FLAG"] = ""
   CONFIG["STATIC"] = ""
   CONFIG["CCDLFLAGS"] = ""
-  CONFIG["LDSHARED"] = "cl -nologo -LD"
+  CONFIG["LDSHARED"] = "gcc -shared"
   CONFIG["DLEXT"] = "so"
   CONFIG["DLEXT2"] = "dll"
   CONFIG["LIBEXT"] = "lib"

トラブル


scRUBYt! 0.3.4 で開発していると様々なトラブルにあうと思います。 Scrubyt::Extractor.define のブロックで問題のある記述があると無限ループに陥ったり、 windows パスを考慮してなかったり、 $KCODE を書き換えていたり、 puts url がうるさかったり、問題があります。

下はアドホックなパッチです。 puts url は grep -r scrubyt-0.3.4 で検索してコメントアウトしてください。 $KCODE は require 'scrubyt' したあとに再設定するか grep で当該箇所を見つけてコメントアウトしてください。
diff -r -u scrubyt-0.3.4.orig/lib/scrubyt/core/scraping/filters/tree_filter.rb scrubyt-0.3.4/lib/scrubyt/core/scraping/filters/tree_filter.rb
--- scrubyt-0.3.4.orig/lib/scrubyt/core/scraping/filters/tree_filter.rb    2008-06-21 00:52:39 +0900
+++ scrubyt-0.3.4/lib/scrubyt/core/scraping/filters/tree_filter.rb 2008-06-21 00:52:39 +0900
@@ -112,7 +112,7 @@
             child_pattern.generalize ? XPathUtils.generate_generalized_relative_XPath(child_pattern.filters[current_example_index].temp_sink, result) :
             XPathUtils.generate_relative_XPath(child_pattern.filters[current_example_index].temp_sink, result)
           end
-          break if @parent_pattern.children[0].filters.size == current_example_index + 1
+          break if @parent_pattern.children[0].filters.size == current_example_index + 1 || @parent_pattern.children[0].nil?
           current_example_index += 1
         end
       when EXAMPLE_TYPE_IMAGE
diff -r -u scrubyt-0.3.4.orig/lib/scrubyt/core/shared/extractor.rb scrubyt-0.3.4/lib/scrubyt/core/shared/extractor.rb
--- scrubyt-0.3.4.orig/lib/scrubyt/core/shared/extractor.rb    2008-06-21 00:52:39 +0900
+++ scrubyt-0.3.4/lib/scrubyt/core/shared/extractor.rb 2008-06-21 00:52:39 +0900
@@ -33,7 +33,7 @@
       @processed_pages = []
       
       backtrace = SharedUtils.get_backtrace
-      parts = backtrace[1].split(':')
+      parts = backtrace[1].scan(/(.+):(\d+)(?=$|:)/).first
       source_file = parts[0]
       
       Scrubyt.log :MODE, mode == :production ? 'Production' : 'Learning'

サンプル


サンプルは RubyForge のダウンロードページで提供されています。また scRUBYt! のサイト( blog )にもサンプル記事があります。ただし一部は動きません。

実例(の示唆)


実行するには次の3つのファイルをどこかから入手し、下記スクリプトと同じディレクトリに保存します。ヒントはありません。ファイル名は参考になりません。ちょっとアレかもしれないので伏せます(スクリプトを読めば想像つくと思いますが...)。
  • spl.htm
  • uni.htm
  • adj.htm


#!ruby -Ks

require 'rubygems'
require 'scrubyt'
# scrubyt.rb が $KCODE = 'u' してるので再設定する。
$KCODE = "s"

class CorporateActionSplit
    def self.parse( iPath )
        Scrubyt::Extractor.define do
            #Perform the action(s)
            fetch( iPath )
            #Construct the wrapper
            title( '//div[@class="title-text"]' )
            lastmodified( 
                '//table[@width="550"][@cellspacing="0"][@cellpadding="1"]/' + 
                'tr[1]/td[@class="mtext"][@align="right"]' )

            header( 
                '//table[@width="550"][@cellspacing="1"][@cellpadding="1"]/' + 
                'tr[1]' ) do
                name( '/td[@class="mtext-db">]' )
            end

            row( '//table[@width="550"]/tr[@*]' ) do
                date( '/td[@class="mtext"][@bgcolor="#ffffff"][1]' )
                brand( '/td[@class="mtext"][@bgcolor="#ffffff"][2]' )
                market( '/td[@class="mtext"][@bgcolor="#ffffff"][3]' )
                size( '/td[@class="mtext"][@bgcolor="#ffffff"][4]' )
            end
        end
    end

    def self.scraping( iPath, iClass = CorporateActionSplit )
        # todo
        result = iClass.parse( iPath )
        puts result.to_xml
    end
end # class CorporateActionSplit

class CorporateActionAdjust
    def self.parse( iPath )
        Scrubyt::Extractor.define do
            #Perform the action(s)
            fetch( iPath )
            #Construct the wrapper
            title( '//div[@class="title-text"]' )
            lastmodified( 
                '//table[@width="550"][@cellspacing="0"][@cellpadding="1"]/' + 
                'tr[1]/td[@class="mtext"][@align="right"]' )

            header( 
                '//table[@width="550"][@cellspacing="1"][@cellpadding="1"]/' + 
                'tr[1]' ) do
                name( '/td[@class="mtext-db">]' )
            end

            row( '//table[@width="550"]/tr[@*]' ) do
                date( '/td[@class="mtext"][@bgcolor="#ffffff"][1]' )
                brand( '/td[@class="mtext"][@bgcolor="#ffffff"][2]' )
                market( '/td[@class="mtext"][@bgcolor="#ffffff"][3]' )
                quart( '/td[@class="mtext"][@bgcolor="#ffffff"][4]' )
                prev( '/td[@class="mtext"][@bgcolor="#ffffff"][5]' )
                curr( '/td[@class="mtext"][@bgcolor="#ffffff"][6]' )
                ratio( '/td[@class="mtext"][@bgcolor="#ffffff"][7]' )
            end
        end
    end

    def self.scraping( iPath, iClass = CorporateActionAdjust )
        # todo
        result = iClass.parse( iPath )
        puts result.to_xml
    end
end # class CorporateActionAdjust

class CorporateActionUnit
    def self.parse( iPath )
        Scrubyt::Extractor.define do
            #Perform the action(s)
            fetch( iPath )
            #Construct the wrapper
            title( '//div[@class="title-text"]' )
            lastmodified( 
                '//table[@width="550"][@cellspacing="0"][@cellpadding="1"]/' + 
                'tr[1]/td[@class="mtext"][@align="right"]' )

            header( 
                '//table[@width="550"][@cellspacing="1"][@cellpadding="1"]/' + 
                'tr[1]' ) do
                name( '/td[@class="mtext-db">]' )
            end

            row( '//table[@width="550"]/tr[@*]' ) do
                date( '/td[@class="mtext"][@bgcolor="#ffffff"][1]' )
                brand( '/td[@class="mtext"][@bgcolor="#ffffff"][2]' )
                market( '/td[@class="mtext"][@bgcolor="#ffffff"][3]' )
                prev( '/td[@class="mtext"][@bgcolor="#ffffff"][4]' )
                curr( '/td[@class="mtext"][@bgcolor="#ffffff"][5]' )
            end
        end
    end

    def self.scraping( iPath, iClass = CorporateActionUnit )
        # todo
        result = iClass.parse( iPath )
        puts result.to_xml
    end
end # class CorporateActionUnit

base = File.dirname( $0 )

path = File.join( base, "spl.htm" )
result = CorporateActionSplit.scraping( path )

path = File.join( base, "uni.htm" )
result = CorporateActionUnit.scraping( path )

path = File.join( base, "adj.htm" )
result = CorporateActionAdjust.scraping( path )

他の書き方。

class CorporateActionSplit
    def self.parse( iPath )
        Scrubyt::Extractor.define do
            #Perform the action(s)
            fetch( iPath )
            #Construct the wrapper
            title( '//div[@class="title-text"]' )
            lastmodified( '//table[@width="550"][@cellspacing="0"][@cellpadding="1"/tr[1]/td[@class="mtext"][@align="right"]' )

            row( '//table[@width="550"][@cellspacing="1"][@cellpadding="1"]/tr[@*]') do
                date( "分割権利落ち日" )
                brand( "銘柄" )
                market( "市場" )
                size( "割当比率" )
            end
        end
    end

    def self.scraping( iPath, iClass = CorporateActionSplit )
        # todo
    end
end # class CorporateActionSplit

注意点


経験から得た知恵です。プログラムを読んだわけではない仕様が分からないので、対処療法かもしれません。

[@*]


XPath 式で、式の途中でタグを属性や配列の添字で修飾した場合に出力がおかしくなる場合は、最後に指定したタグに[@*]をつけると回避できます。

            row( '//table[@width="550"]/tr[@*]' ) do

:html_subtree


パターン指定と同時に :type => :html_subtree すると期待通りの値が得られないようです。

text( '//p', :type => :html_subtree )

ブロックで囲ってやるとうまくいく。

text( '//p' ) do
    html( :type => :html_subtree )
end

:script


inner_html.gsub(/<.*?>/, "") した文字列が lambda に渡されます。ブロックの戻り値は配列で戻します。戻した配列が空の場合はノードは生成されません。

 script( lambda{ |string| return array }, :type => :script )

いくつか問題があります。
  • 実態参照を元に戻さない。
  • 戻り値の重複が取り除かれる。
    • このため select_indices した場合に値がずれる。バグ?
  • 他のブロックに値を渡す場合はインスタンス変数を介して渡す。

他のブロックに値を渡すサンプル。

script( lambda{ |x|
            x.gsub!( /&nbsp;/, " " )
            x.strip!
            @values = x.split( /\s+/m )
            []
        }, :type => :script )
brand( lambda{ [@values[0]] }, :type => :script )
ticker( lambda{ [@values[1]] }, :type => :script )

lambda を並べず :type => :constant で良さそうな気がするものの、定義時に1度だけ呼ばれて繰り返し呼ばれないので、期待通りになりません。

brand( @values[0], :type => :constant )	# @values[0] は空。

こう書けるとすっきりするんだけど...

script( lambda{ |x|
            x.gsub!( /&nbsp;/, " " )
            x.strip!
            x.split( /\s+/m )
        }, :type => :script ) do
    brand( ":0" )
    ticker( ":1" )
end

戻り値


Scrubyt::Extractor.define の戻り値は ScrubytResult です。 ScrubytResult は ResultNode を継承しています。 ResultNode は Array を継承しています。 scrubytResult.inspect すると入れ子になった空の配列に見えるのはこのためです。

scrubytResult の中には resultNode が入ってます。


Tips


Spidering の注意点


サーバに負荷を掛けないよう注意します。
  • 同時接続しない
  • 1ページあたりの取得間隔を空ける( sleep( 5 ) で5秒ほど空ける)
  • スクリプトを再入可能にする
    • 既取得分はローカルにキャッシュし、キャッシュがあればそれを利用する。
  • 大量にページを取得する場合は1日あたりの実行回数を少なくする(1〜2回)

logging


役に立つかもしれないし役に立たないかもしれない。

Scrubyt.logger = Scrubyt::Logger.new

_block_name do


Scrubyt::Extractor.define 内でブロック名にアンダースコアから始まる名前を使用すると返り値からブロックが省略されます。

Scrubyt::Extractor.define do
    fetch( path )
    # 戻り値に _table ブロックは含まれない。ブロックの中身は返ってくる。
    _table( '//table' ) do
        th( "/th" )
        # 下はエラーになる。
        _td( "/td" )
    end
end

HTML Tidy


行儀の悪い html は、当然のことながら上手にパースできません。 HTML Tidy で xhtml に変換すれば、回避できるかもしれません。

HTML Tidy の ruby interface があります。このライブラリは tidy.dll を使用します。 tidy.dll をパスの通った場所に保存して gem install tidy してください。

RubyForge: Tidy: Project Info
http://rubyforge.org/projects/tidy

HTML Tidy Project Page
http://tidy.sourceforge.net/

Tidy Binaries
http://int64.sourceforge.net/tidy.html


#!ruby -Ks

require 'tidy'
Tidy.path = 'tidy.dll' # パスが通っていればこれで読み込める。

input = "<title>Foo</title><table></tr></table><p>Foo! <<b>bold</b>>"

xhtml = 
    Tidy.open( :show_warnings => true ) do |tidy|
        tidy.options.language = "jp"
        # input: shiftjis
        tidy.options.input_encoding = "shiftjis"
        # output: shiftjis, utf8, utf16, utf16be, utf16le, raw, ...
        tidy.options.output_encoding = "shiftjis"
        tidy.options.output_xhtml = true
        xhtml = tidy.clean( input )
        puts( tidy.errors )
        puts( tidy.diagnostics )
        xhtml
    end

puts xhtml

挿入する場所は、scrubyt-0.4.06 では例えば lib/scrubyt/core/scraping/pre_filter_document.rb に定義されているクラス メソッド br_to_newline が適当でしょう。このメソッドは alias できないので再定義します。 tidy_normalize を定義してこの例のコメント箇所を有効にしてください。

module Scrubyt
  class PreFilterDocument
     def self.br_to_newline(doc)
       # doc = tidy_normalize(doc)
       doc.gsub(/<br[ \/]*>/i, "\r\n")
       doc = doc.tr("\240"," ")
     end
  end
end

https


open-uri を使った https 接続は、 open-uri が証明書ファイルの設定を行う機会を用意してないため、デフォルトで設定される証明書ファイルの参照パスにアクセス先の証明書が保存されてない場合は接続できません。追加できないなら、メソッドをオーバーライドして強引に書き換えます。下の例では、実行したスクリプトと同じディレクトリに site.cer の名前で保存した証明書ファイルを設定しています。

class OpenSSL::X509::Store
    alias :set_default_paths__hogehoge :set_default_paths
    def set_default_paths
        set_default_paths__hogehoge
        path = File.dirname( $0 )
        path = File.join( path, "site.cer" )
        self.add_file( path )
    end
end

証明書は ie から取り出します。 ie で対象サイトに https で接続し、ページ内の右クリック メニューからプロパティを選択、ダイアログの証明書ボタンをクリック、表示されたダイアログの証明のパス タブをクリック、証明のパス ツリーからルートを選択、証明書の表示ボタンをクリック、詳細タブをクリック、ファイルにコピー ボタンをクリック、ウイザードから Base 64 encode X.509(CER) を選択し、ファイルに保存します。

ログイン


ログインは、連続してログインに失敗したり、ログイン/ログアウトを繰り返したり、おかしな行動をとった場合に、対象サイトがセキュリティの観点からユーザ id を一時凍結するなど、トラブルを招くおそれがあります。ログイン状態はセッション情報で管理しているでしょうから、ブラウザからクッキーを取り出し、スクリプトに与えることで、これを避けます。

ie の場合は下のスクリプトを使用し、クリップボードにクッキーをコピーします。

<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">
<!-- 京都 -->
</head>

<script language="JavaScript">
    var obj = external.menuArguments;
    var doc = obj.document;
    var ck = doc.cookie;
    alert( "cookie is \"" + ck + "\". copy to clipboard." );

    var cb = doc.parentWindow.clipboardData;
    cb.setData( "text", ck );
</script>

</html>

ie への登録は「ちょこっと 強制削除」の installer.vb を借用して登録します。

ちょこっと 強制削除 - クロノス・クラウン -
http://crocro.com/pc/soft/c_del/index.html

上記スクリプトを保存したディレクトリに installer.vb をコピーし、次の箇所を書き換えます。


    strTitle1 = "ちょこっと 強制削除"
    strFile1 = "c_del.html"

installer.vb を実行する前に、念のため全ての ie を閉じておきます。 installer.vb を実行します。 ie を再起動し、右クリックメニューに strTitle1 で指定した名前のメニューが追加されていることを確認します。

クリップボードにコピーしたクッキーは、スクリプトのオプションに指定してスクリプトに与えます。オプションに与えたあとは、クリップボードの内容を置き換えます。

スクレイピングが終わったらログアウトします。必ずログアウトしてください。

セッション情報の漏洩はセキュリティ上問題があります。取り扱いには充分注意し、スクレイピング後は必ずログアウトして、セッション情報を無効にしてください。

参考文献

[Ruby]HTTPSでサーバーに接続 - うなの日記 ( http://d.hatena.ne.jp/unageanu/20070504 )
windows 環境で ruby から https に接続する方法と証明書ファイルの取得方法を紹介されてます。

不具合

  • hpricot 0.6.161 は extern 関係で拡張ファイルがビルドできないかもしれない。ビルドできてもテストが通らない。
  • RubyInline 3.8.1 あたりから Inline::C の内部構造が変わってしまって、 RubyInlineAcceleration 0.0.1 が動かなくなっている。 @@type_map が TYPE_MAP に変更されているため...かも。
    • @@type_map = TYPE_MAP で動いてるっぽい。

もろもろ

  • 2008/12/11 に 0.4.06 が公開された。 firewatir が必須になっている( readme では Mechanize (or FireWatir) とうたっている。 gemspec には依存関係が記述されてない。が本体で require 'firewatir' している)。これは watir を使う。 firewatir が require 'watir/matches' しているがこのファイルは watir 1.6.2 に存在しない。 watir は windows-api 等を読み込んでいる。 windows 以外で使えるのだろうか?。
    • watir の一部を commonwatir に分割していたらしい。今(2009/1/23fri)は動いている。何が悪かったのだろう。
    • firewatir を入れる場合は gem install firewatir する。 scRUBYt! のインストールでは自動で入らない。
    • firewatir を入れたくない場合は scrubyt.rb の require "〜/agents/firewatir.rb" 行をコメントアウトする。

NoMethodError


Ruby 1.8.7-p160 + scRUBYt! 0.4.06 + hpricot-0.8.1-x86-mswin32 な環境で次のようなエラーが出る。
NoMethodError: undefined method `each' for nil:NilClass
    D:/ASR/lib/ruby/gems/1.8/gems/scrubyt-0.4.06/lib/scrubyt/utils/shared_utils.rb:42:in `traverse_for_match'

lib/scrubyt/utils/shared_utils.rb のバグらしい。この修正で手元のスクリプトのユニットテストが通るようになった。

Can't get scrubyt to work - scrubyt | Google Groups
http://groups.google.com/group/scrubyt/browse_thre...
From: michael <...@gmail.com>
Date: Sun, 26 Apr 2009 16:20:25 -0700 (PDT)
Subject: Re: Can't get scrubyt to work

You may be affected by a bug in shared_utils.rb try updating that file
using:

http://github.com/scrubber/scrubyt/commit/c97595d2...

Commit c97595d2ff108f801f0296b376c20d3fd67b7585 to scrubber's scrubyt - GitHub
http://github.com/scrubber/scrubyt/commit/c97595d2...
@@ -39,7 +39,7 @@ module Scrubyt
           results << node
           results.delete node.parent if node.is_a? Hpricot::Elem
         end
-        node.children.each { |child| traverse_for_match_inner.call(child, regexp) if (child.is_a? Hpricot::Elem) }
+        node.children.each { |child| traverse_for_match_inner.call(child, regexp) if (child.is_a? Hpricot::Elem) } if ! node.children.nil?
       }
       traverse_for_match_inner.call(node,regexp)
       results

リンク

scRUBYt! - a Simple to Learn and Use, yet Powerful Web Scraping Toolkit Written in Ruby ( http://scrubyt.org/ )
総本山。
RubyForge: scRUBYt!: Project Info ( http://rubyforge.org/projects/scrubyt/ )
RubyForge にあるプロジェクト ページ。
Reference - Scrubyt ( http://wiki.scrubyt.org/index.php?title=Reference )
リファレンス マニュアル。
Module: Scrubyt ( http://scrubyt.rubyforge.org/classes/Scrubyt.html )
クラス ドキュメント。
前のコードじゃフォームに対応できない - あうっちの dmesg <http://d.hatena.ne.jp/autch/20080724>
文字コードを変換してからパースする方法。

更新履歴



2011/6/13mon _block_name do を記述。HTML Tidy に追記。
2009/5/1fri もろもろに NoMethodError を追記。
2009/1/24sat もろもろに追記。
2008/12/21sun scRUBYt! 0.4.06 が公開された。
2008/12/11thu シンタックス ハイライトを使ってみた。
2008/11/14fri 更新したライブラリで問題なく動いてるぽいので、バージョン情報を更新。
2008/11/13thu 不備を補った。リンク追記。
2008/7/1tue :script 追記。構成を見直した。
2008/6/26thu :html_subtree 追記。リンク追記。
2008/6/25wed インストール関連の記述を修正。あやしい記述を修正。
2008/6/23mon もろもろ修正&追記。
2008/6/22sun vc7tk, mingw 環境で asr バイナリの拡張ライブラリをビルドするためのパッチを記述。もろもろ修正&追記。
2008/6/21sat HTML tidy について記述。Spidering の注意点を記述。もろもろ修正。
2008/6/21sat ページ作成。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Wiki内検索

編集にはIDが必要です