Rubyでhttpとスクレイピング

前置き

仕事で、Webサイトを自動巡回するプログラムを書くことがあり、rubyでのhttp通信やスクレイピングについて調べました。そのイロハをメモっておきます。

本文

ruby

Ruby 1.9.3を使います。

C:\WINDOWS\system32>ruby --version
ruby 1.9.3p194 (2012-04-20) [i386-mingw32]

Net::HTTPとURIライブラリ

http通信には、標準ライブラリのNet::HTTPを使います。このライブラリのインターフェイスは盛り沢山で、GETやPOSTリクエストを送る方法が何通りもあります。

  • クラスメソッドのget
  • クラスメソッドのget_response
  • クラスメソッドのpost_form
  • インスタンスメソッドのget
  • インスタンスメソッドのpost
  • インスタンスメソッドのrequest
  • インスタンスメソッドのrequest_get
  • インスタンスメソッドのrequest_post
  • インスタンスメソッドのsend_request

一長一短あるんでしょうね。私の場合、最初に見つけたサンプルコードが、newしてget/postするスタイルだったので、それを真似してます(ただ、そのせいでちょっと落とし穴にハマリましたが…)。

同じく標準ライブラリのURIには、文字通り、URIを管理する機能が詰まっています。例えば、"https://google.co.jp/"のような文字列をパースして、スキームやホスト名を抽出することができます。

Nokogiri

Nokogiriは、スクレイピング用のgemの一つです。フォーム処理機能を強化したMechanizeというgemもありますが、ちょっとencoding関連の問題があったので使えませんでした。Mechanizeは内部でNokogiriを使うようです。

C:\WINDOWS\system32>gem list nokogiri

*** LOCAL GEMS ***

nokogiri (1.5.3 x86-mingw32)

基本のGET

newしてgetします。newの引数にホスト名を、getの引数にパスを、それぞれ指定します。

# encoding: utf-8

require 'net/http'
require 'kconv'

con = Net::HTTP.new 'gpsoft.dip.jp'   # ホスト名。
res = con.get '/'                     # パス。

puts res.body.split($/)[0..9].join($/).tosjis

getが返すのはNet::HTTPResponse(か、その派生クラス)のインスタンスです。ここでは、ボディ部の最初の10行を表示しています。

D:\etude\rwalker>ruby main.rb
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C/DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="ja" xml:lang="ja">

<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <title>TOPページ | GPソフト</title>
  <link rel="INDEX" href="./index.html" />

リクエストパスには、クエリ(?para1=val1&para2=val2とか)やフラグメント(#index1とか)を含めても構いません。

またSSL(https)の場合は、以下の手順が増えます。

  • ポート番号を指定
  • SSLを有効化
  • 認証方法を指定
  • (古いrubyの場合のみ)requireするライブラリをnet/httpからnet/httpsへ変更
# encoding: utf-8

require 'net/http'
require 'kconv'

con = Net::HTTP.new 'www.google.co.jp', URI::HTTPS::DEFAULT_PORT
                             # ホスト名とポート番号。
con.use_ssl = true           # SSLを有効化。
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
                             # 認証方法に「認証しない」を設定。
res = con.get '/search?q=ruby'
                             # クエリを含むパス。
res.body =~ /<title>(.+)<\/title>/
puts $1.tosjis

正規表現を使って、ページのタイトルを抽出しています(まだ鋸は仕舞っておきます)。

D:\etude\rwalker>ruby main.rb
ruby - Google 検索

上記のコードではSSL認証をスキップしてますが、ちゃんとSSL認証する場合は以下のようになります。

con.use_ssl = true
con.verify_mode = OpenSSL::SSL::VERIFY_PEER
                             # サーバ認証する。
con.ca_file = './BuiltinObjectToken-EquifaxSecureCA.crt'
                             # ルートCA証明書。
con.verify_depth = 3         # 証明書チェーンの最大長?
res = con.get '/search?q=ruby'

ca_fileには必ずルートCA証明書を指定します(中間CA証明書ではダメです)。ルートCA証明書ファイルは、適当なブラウザからPEM形式でエクスポートすれば入手できます。

リダイレクト

自動巡回するならリダイレクトに対応する必要があります。Net::HTTPResponseクラスの派生クラスはレスポンスのステータスコードに応じて階層化されているので、getが返したオブジェクトのクラスを調べれば、リダイレクトされたかどうかを判定できます。

リダイレクトされていた場合は、ヘッダ部のlocationフィールドを見ればリダイレクト先が分かります。ヘッダ部のフィールドを検索するにはfetchメソッドか[]メソッドを使います(どちらも引数はフィールド名)。

# encoding: utf-8

require 'net/http'
require 'kconv'

con = Net::HTTP.new 'mail.google.com', URI::HTTPS::DEFAULT_PORT
con.use_ssl = true
con.verify_mode = OpenSSL::SSL::VERIFY_PEER
con.ca_file = './VerisignClass3Public-Primary.crt'
con.verify_depth = 3
res = con.get '/mail'

case res
  when Net::HTTPSuccess then
    puts "success"                          # 200レスポンス。
  when Net::HTTPRedirection then
    loc = res.fetch 'location'              # res['location']でも可。
    puts "redirected to #{loc}"
  else
    puts "unexpected response: #{res.code}" # レスポンスコードを表示。
end

ここではgmailのトップページをgetしてますが、リクエストヘッダにcookie情報を指定してないので、ログインページへリダイレクトされるはずです。

D:\etude\rwalker>ruby main.rb
redirected to https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1&ltmpl=default&ltmplcache=2

で、次はリダイレクト先をgetするわけですが、ここに、いろんな落とし穴があります。

  • リダイレクト先をgetすると、さらに別のページへリダイレクトされるかもしれない
  • リダイレクト先はパスだけで指定されるかもしれない(スキームやホスト名を含まないかもしれない)
  • リダイレクト先は別のホストかもしれない
  • リダイレクトが永遠に循環するかもしれない

この辺に注意してコードを書きましょう(最後のコードを参考に)。

2点目については、URIライブラリを使うと簡単に対処できます。

あと、私は3点目でハマリました。ホスト名はNet::HTTP.newの引数で決まるので、別のホストにリダイレクトされた場合はNet::HTTPオブジェクトを作り直す必要があるんですね。これに気付かず、404や302を食らってました。今にして思えば、newしてgetするスタイルの採用が失敗だったのかも…。

URIライブラリを使う

リダイレクトを処理するには、少々面倒くさい文字列加工が必要です。例えば、絶対URIとパスを合成するとか、URI文字列からポート番号を判別するとか。こういった処理にはURIライブラリが便利です。

# encoding: utf-8

require 'uri'

uri = URI.parse 'https://www.google.co.jp/search?q=ruby'
puts uri.scheme         #=> https
puts uri.host           #=> www.google.co.jp
puts uri.path           #=> /search
puts uri.query          #=> q=ruby
puts uri.fragment       #=> 
puts uri.port           #=> 443
puts uri.request_uri    #=> /search?q=ruby
puts uri.to_s           #=> https://www.google.co.jp/search?q=ruby

uri = URI.join uri, '/search?q=perl'
puts uri.request_uri    #=> /search?q=perl
puts uri.to_s           #=> https://www.google.co.jp/search?q=perl

uri = URI.join uri, 'http://www.foo.com/bar/buz/index.html'
puts uri.to_s           #=> http://www.foo.com/bar/buz/index.html

uri = URI.join uri, './chap1.html'
puts uri.to_s           #=> http://www.foo.com/bar/buz/chap1.html

request_uriはパスとクエリとフラグメントを合成してくれます。これは、Net::HTTP.getの引数に使うのに打ってつけですね。

またjoinは、2つのURIを合成してくれます。2つのURIがどちらも絶対URIの場合は、2つ目が採用されます。この挙動も、リダイレクト先を得るときに好都合です(単純に、元URIとリダイレクト先をjoinすればOK)。

クエリとフラグメントが無い場合はrequest_uriでもpathでも同じ、と思ったら大間違いです。下記のようなケースでは、request_uriとpathが異なります。

# encoding: utf-8

require 'uri'

uri = URI.parse 'http://www.google.co.jp/'
puts uri.path           #=> /
puts uri.request_uri    #=> /
puts uri.to_s           #=> https://www.google.co.jp/

uri = URI.parse 'http://www.google.co.jp'   # 最後の/を忘れた場合。
puts uri.path           #=> 
puts uri.request_uri    #=> /
puts uri.to_s           #=> https://www.google.co.jp

rubyの文字コード管理

そろそろ力尽きてきたのでスクレイピングに移りたいのですが、その前に、どうしても文字コードの話しをしないわけにはいかないようです。

これまで一貫してソースファイルはUTF-8で書いてきました。そして、実行環境はWindowsです。起動オプションは付けてません。それを踏まえると、以下の挙動はリーズナブルです。

# encoding: utf-8

require 'kconv'

puts Encoding.default_external       #=> Windows-31J  ←OSがWindowsだから
puts __ENCODING__                    #=> UTF-8        ←マジックコメント(1行目)のせい

text = '検索'
puts text.encoding                   #=> UTF-8
puts text                            #=> 検索         ←自動的にWindows-31Jへ変換される
puts text.split(//).map { |c| "%04x" % [c.ord]}.join ':'
                                     #=> 691c:7d22    ←Unicodeコードポイントで「検索」

次に、googleの検索結果ページを見てみましょう。このページ(HTML)はUTF-8で書かれており、<meta>タグにも「charsetはUTF-8」と記述されています。

# encoding: utf-8

require 'net/http'
require 'kconv'

con = Net::HTTP.new 'www.google.co.jp', URI::HTTPS::DEFAULT_PORT
con.use_ssl = true
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = con.get '/search?q=ruby'

body = res.body
puts body.encoding                          #=> ASCII-8BIT
open('foo.html', 'w') { |f| f.write body }  # foo.htmlはSJISになる。

body =~ /<title>(.+)<\/title>/
title = $1

puts title.encoding          #=> ASCII-8BIT
puts title                   #=> ruby - Google ■■ ←化ける
puts title.split(//).map { |c| "%04x" % [c.ord]}.join ':'
         #=> 0072:0075:0062:0079:0020:002d:0020:0047:006f:006f:0067:006c:0065:0020:008c:009f:008d:00f5

最後の8c9f8df5は、SJISの「検索」と一致します。UTF-8からSJISへ変換しておきながら、title.encodingはASCII-8BITですか? しかも、f.writeで出力されたファイルは化けないのに、titleをコンソールへputsすると化ける…。なぜ??

よく分かりませんが、encodingがASCII-8BITになってしまうのがマズい気がします。まともにtitleを表示するには、force_encodingメソッドを使ってStringオブジェクトが内部に保持しているencoding情報を上書き(訂正)してやるか、kconvで別の文字コードに変換してやります。何に変換しても、putsすればSJIS(と言うかWindows-31J)に変換されるはず…。

title_s = title.tosjis
puts title_s                 #=> ruby - Google 検索

title_u = title.toutf8
puts title_u                 #=> ruby - Google 検索

title_e = title.toeuc
puts title_e                 #=> ruby - Google 検索

title.force_encoding 'Windows-31J'
puts title                   #=> ruby - Google 検索

GET/POSTのレスポンスをスクレイピングするときも、鋸で切る前に、これをやっておいた方が良いみたいです。

スクレイピング

ようやくスクレイピングです。まずは試し切りとして、Nokogiriを使ってgoogleの検索結果ページからタイトルを抽出してみます。

# encoding: utf-8

require 'net/http'
require 'kconv'
require 'nokogiri'

con = Net::HTTP.new 'www.google.co.jp', URI::HTTPS::DEFAULT_PORT
con.use_ssl = true
con.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = con.get '/search?q=ruby'

root = Nokogiri::HTML res.body.toutf8
puts root.xpath('/html/head/title').first.inner_text  #=> ruby - Google 検索

ポイントは最後の2行です。

前節で説明した通り、レスポンスのボディ部はkconvで変換して使います。今回のページ(HTML)は元々UTF-8で書かれていたので、toutf8するのが自然でしょう。

Nokogiri::HTMLメソッドは、Nokogiri::XML::Nodeオブジェクトを返します。これは、HTML文章のルートノードに相当します。xpathメソッドの引数にXPath形式のノードパスを指定すると、それにマッチするノードの配列(Nokogiri::XML::NodeSet)が返ってきます。firstは配列の最初のノードを返し、inner_textはノードが持つテキストコンテントを返します。

NokogiriはHTMLの文字コードを類推しますが、それは100%正確とは限りません。明示的に指定するには、HTMLメソッドの第3引数を使います。

root = Nokogiri::HTML res.body.toutf8, nil, 'utf-8'

Nokogiri::XML::Nodeクラスには、その属性や子要素にアクセスするための様々なインターフェイスが用意されています。これらのインターフェイスとXPathの表現力を使って、自在にHTML内を探索することが可能です。以下のHTMLファイル(bar.html)を使って試してみましょう。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=euc-jp">
    <title>ログイン | GPソフト</title>
  </head>
  <body>
    <h2>1.rubyでHTTP</h2>
    <h2>2.rubyでスクレイピング</h2>
    <form method="post" action="/login.cgi">
    <table border="0">
      <tr>
        <td class="label">UserID</td>
        <td>
          <input name="userid" />
        </td>
      </tr>
      <tr>
        <td class="label"><font color="#3b14ff">Password</fong></td>
        <td>
          <input type="password" name="password" />
        </td>
      </tr>
    </table>
    <input type="submit" value="Login" />
    </form>
  </body>
</html>

サンプルページ

様々な要素にXPathでアクセスしてみます。

# encoding: utf-8

require 'nokogiri'
require 'kconv'

open('./bar.html', 'r') { |f|
  body = f.read

  root = Nokogiri::HTML body

  puts root.xpath('/html/head/title').first.inner_text  #=> ログイン | GPソフト
  puts root.xpath('/html/body/h2[2]').first.inner_text  #=> 2.rubyでスクレイピング
  td = root.xpath("//td[@class='label'][./font]").first
  puts td.inner_html                     #=> <font color="#3b14ff">Password</font>
  puts td.inner_text                     #=> Password

  # ※1
  root.xpath('//form').first.xpath('.//input').each { |i|
    puts "input name=#{i['name']}, type=#{i['type']}"
                                         #=> input name=userid, type=
                                         #=> input name=password, type=password
                                         #=> input name=, type=submit
  }
}

ここで使っているXPath表現について簡単に補足します。

  • [2]は、「2番目」の意味(1から開始)
  • //は、「途中に何が入ってもいい」
  • [@class='label']は、「class属性を持ち、かつ、その値が'label'」
  • [./font]は、「子要素に<font>を持つ」

また※1の意図は、以下の通りです。

  • 途中はどうでもいいからとにかく<form>を探し、
  • 最初の<form>の子か孫要素から<input>を探し、
  • 各<input>要素の、name属性とtype属性を表示する

Cookie

スクレイピングできるようになったので、次はフォームのsubmitに進んでいきたいところですが、その前にcookieを片付けておきましょう。正しいcookieをサーバに送らないと、submitが失敗します。やるべきことは、以下の2点です。

  • レスポンスのヘッダ部にset-cookieフィールドがあったら、それを退避
  • 退避しておいたcookieをリクエストのヘッダ部にcookieフィールドとして指定

set-cookieフィールドの例を示します。

Set-Cookie: SessionID=12345; Domain=mail.google.com; Expires=Thu, 10-Jan-2013 13:35:42 GMT; Path=/mail; Secure; HttpOnly

一番大事なのは、先頭にある、cookie名と値です(SessionIDと12345)。他の情報も大事と言えば大事ですが、ここは手抜きして、cookie名と値をハッシュに退避することにします。set-cookieフィールドを得るにはget_fieldsメソッドを使います(1つとは限らない)。

cookies = {}
res = con.get '/login'
if res.key? 'set-cookie'           # set-cookieフィールドがある?
  res.get_fields('set-cookie').each { |c|
    c =~ /(.+?)=(.+?)[;\s$]/
    cookies[$1] = $2 if $1
  }
end

リクエストには、それまでに受け取ったcookie情報を全て付けるようにします(これも手抜き)。

h = {}
if cookies
  h['cookie'] = cookies.map { |k, v|
    "#{k}=#{v}"
  }.join('; ')
end
res = con.get '/login', h

cookieフィールドに限らず、リクエストのヘッダをカスタマイズしたい場合は、ヘッダ情報をハッシュにしてgetメソッドの第2引数に渡せばOKです。

POST(フォームのsubmit)

最後にPOSTリクエストを送って、フォームをsubmitしてみましょう。これには以下のような処理が含まれます。

  • HTMLページからPOSTのパラメータ情報(<input>とか<select>とか)を収集
  • パラメータ情報をリクエストのボディ部へエンコード
  • POSTリクエストを送信

順に見て行きましょう。

まず、HTMLページをスクレイピングして、パラメータ情報を収集します。具体的に言うと、submitしたい<form>要素の子孫要素から、<input>、<select>、<textarea>を検索し、それぞれのname属性値とvalue属性値を集めてハッシュに格納します。例えば<input>なら、こんな感じ。

params = {}
root = Nokogiri::HTML res.body.toutf8, nil, 'utf-8'
f = root.xpath('//form').first    # フォームの見つけ方はケースバイケース。
f.xpath('.//input').each { |i|
  n = i['name']
  t = i['type']
  v = i['value']
  params[n] = v unless t == 'checkbox' or t == 'submit'
}

これも、かなり手抜きしてます。

  • type属性が'checkbox'や'radio'の場合、checked属性を持つならvalue属性値を採用すべき
  • type属性が'submit'や'reset'や'button'の場合、value属性値を採用するかどうかはケースバイケースで決める
  • 同一フォーム内に同じnameが重複するケースもあるので、paramsの値は配列にすべき

また、<select>と<textarea>については、以下の点に注意して下さい。

  • <select>の場合、子孫要素からselected属性を持つ<option>を探し、そのvalue属性値を採用
  • <textarea>の場合、value属性値ではなく、inner_textを使う

では次に、リクエストのボディ部を作りましょう。書式(つまりContent-Type)は2種類あります。

  • application/x-www-form-urlencoded
  • multipart/form-data

デフォルトは前者で、変更する場合は<form>要素のenctype属性で指定します。後者はファイルのアップロードなんかに使います(面倒くさいので割愛)。

前者(application/x-www-form-urlencoded)の場合、等号と&で単純に連結した、param1=value1&param2=value2&...のような文字列になります。ただし、等号の両辺をURLエンコードしなければなりません。そこで、URIライブラリの登場です。encode_www_formメソッドが面倒を見てくれます。

b = URI.encode_www_form params

encode_www_formメソッドの引数の構造は柔軟で、以下の4つの形式が許されています(3番目と4番目は実質的には同じですが…)。返された文字列がボディ部になります。

  • {param1 => value1, param2 => value2, ...}
  • {param1 => [value1, value11, ...], param2 => [value2, value22, ...], ...}
  • [[param1, value1], [param2, value2], ...]
  • [[param1, value1], [param1, value11], [param2, value2], ...]

cookiesとparamsが決まり、そこからヘッダ部とボディ部が作れたので、あとはPOSTリクエストを送信するだけです。GETのときと同様に、newしてから、postします。

cookies = {}
params = {}

# cookiesにcookie情報を蓄積。
...

# paramsにPOSTパラメータ情報を収集。
...

# cookiesをヘッダ部にセット。
h = {}
if cookies
  h['cookie'] = cookies.map { |k, v|
    "#{k}=#{v}"
  }.join('; ')
end

# ボディ部をエンコードして、
# ヘッダ部にContent-Typeをセット。
b = URI.encode_www_form params
h['content-type'] = 'application/x-www-form-urlencoded'

# POST送信。
con = Net::HTTP.new 'gpsoft.dip.jp'
res = con.post '/login.cgi', b, h

最後に、gmailにログインするところまでを一気に実行してみましょう。gmailの場合、JavaScriptを動かさないことにはどうにもならないので、実用性は無いですけど…。

# encoding: utf-8

require 'net/http'
require 'kconv'
require 'nokogiri'

# GET送信。
def get(uristr, cookies)
  send_req(URI.parse(uristr), cookies, 5) { |con, path, h|
    con.get path, h
  }
end

# POST送信。
def post(uristr, cookies, params)
  send_req(URI.parse(uristr), cookies, 5) { |con, path, h|
    b = URI.encode_www_form params
    h['content-type'] = 'application/x-www-form-urlencoded'
    con.post path, b, h
  }
end

# GET/POSTに共通の処理。
def send_req(uri, cookies, chance)
  con = Net::HTTP.new uri.host, uri.port
  if uri.port == URI::HTTPS::DEFAULT_PORT
    con.use_ssl = true
    con.verify_mode = OpenSSL::SSL::VERIFY_PEER
    con.ca_file = './google_ca.crt'  # 必要なルートCA証明書を全て1つにまとめた。
    con.verify_depth = 3
  end

  res = yield con, uri.request_uri, make_header(cookies)

  # cookie情報を退避。
  if res.key? 'set-cookie'
    res.get_fields('set-cookie').each { |c|
      c =~ /(.+?)=(.+?)[;\s$]/
      cookies[$1] = $2 if $1
    }
  end

  # リダイレクト。
  case res
    when Net::HTTPSuccess then
      res
    when Net::HTTPRedirection then
      raise "No more chance to redirect" unless chance > 0
      loc = res['location']
      send_req(URI.join(uri.to_s, loc), cookies, chance - 1) { |con, path, h|
        con.get path, h
      }
    else
      raise "Unexpected response: #{res.code}"
  end
end

# cookiesを元にヘッダ部を生成。
def make_header cookies
  h = {}
  h['cookie'] = cookies.map { |k, v|
    "#{k}=#{v}"
  }.join('; ') if cookies
  h
end

# スクレイピング準備。
def scrape(res)
  root = Nokogiri::HTML res.body.toutf8, nil, 'utf-8'
  yield root
end

# ここからがメイン。
cookies = {}
params = {}

# gmailのトップページをGET。
# リダイレクトされるはず。
res = get 'https://mail.google.com/mail', cookies

# POSTパラメータを収集。
# <input>のみをケア。
# <form>のaction属性も退避。
action = ''
scrape(res) { |root|
  root.xpath('//form[@action]').each { |f|
    action = f['action']
    f.xpath('.//input').each { |i|
      n = i['name']
      t = i['type']
      v = i['value']
      params[n] = v unless t == 'checkbox' or t == 'submit'
    }
  }
}

# メアドとパスワードを入力。
params['Email'] = 'my_addr@gmail.com'
params['Passwd'] = 'my_passwd'

# ログインフォームをsubmit。
res = post action, cookies, params

# メッセージのみ抽出。
scrape(res) { |root|
  puts root.xpath("//div[@class='msg']").first.inner_text
                           #=>  my_addr@gmail.com を読み込んでいます…
}

ca_fileで指定した証明書ファイルの内容は以下の通りです。ログインページとgmailトップとで使われているルートCAが異なるので、2つの証明書を1つのファイルに格納しました。

-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----

以上で、自動巡回に必要な要素は全て説明したと思います(JavaScriptは除く)。個人的には、これらを応用して、TV番組の検索や、天気予報の収集、図書館の貸出予約状況調査などを自動化しています。サイトによっては、ページに埋め込まれた画像ファイルをGETしないとcookieがもらえないとか、cookieの値がJavaScriptで計算されるとか、onClick属性値のJavaScriptを解読しないとリンク先URIが分からないとかで、難儀する場合もあります。

Last modified:2012/07/12 01:02:05
Keyword(s):
References:[言語Tips]
This page is frozen.