作ったクローラ
備忘録として残しておく。
Ruby習いたてで書いたので色々修正するところもありそう。
#! ruby -Ks
# ○○○サイトクローラー
#
# 利用方法:コマンドラインより実施
# ruby site_crawler.rb 引数1
#
# 引数1:なし →Typeのすべてを取得
# 引数1:種別名称 →Typeに一致するものだけを取得
# 例) ruby site_crawler.rb 投資用マンション ##投資用マンションのみ取得
# 例) ruby site_crawler.rb ##すべて取得
#
# 出力されるCSVは、Shift-JIS タブ区切り 改行コード:CRFL
require 'rubygems'
require 'mechanize'
require 'kconv'
require 'hpricot'
# サイト毎の基本変更点は以下のとおり
#
# ①データ一覧のトップページ
# ②物件詳細URLに含まれるべき文字列(正規表現可能)
# ③物件リストの次ページリンク文字列(正規表現可能)
# ④次へページの挙動
# ⑤CSV出力ファイル名
# ⑥データ項目
# ⑦無効データ
# および、データ項目でHTMLを解析して取得すべきデータのプログラミング
# ①データ一覧のトップページ
Type = {
"投資用マンション"=>"http://www.xxxxxx.com/list/t=1",
"売りアパート"=>"http://www.xxxxxx.com/list/t=2",
"売りマンション"=>"http://www.xxxxxx.com/list/t=3",
"戸建賃貸"=>"http://www.xxxxxx.com/list/t=8"
}
if ARGV[0]
Type.each{|tkey,tvalue|
if tkey!=ARGV[0]
Type.delete(tkey)
end
}
end
# ②物件詳細URLに含まれるべき文字列(正規表現可能)
url_match_str = %r!/realestate/!
# ③物件リストの次ページリンク文字列(正規表現可能)
next_page_link_match_str = %r!次の[0-9]+件!
# ④次へページの挙動
def nextPage_click(agent, link, nextPageNum)
# 次へページがURLベースである場合は下記を利用
nextPage = link.click
return nextPage
# # 次へページがjavascriptなど、URLベースで無い場合はここを変更して利用する
# # 内部HTMLを解析し、パラメータの状態を確認のこと
# form = agent.page.form_with("page_jump")
# form["start_index"]=(nextPageNum-1)*20
# form.submit
# nextPage = agent.page
# return nextPage
end
# ⑤CSV出力ファイル名
CsvFileName = "site_crawler.csv"
tmpCsvFileName = CsvFileName
# ⑥データ項目
csv_item_ary=[
#
# ここは、データ一意性のための番号が振られます
"取得番号", #各取得タイプ内での連番(プログラムで振ります)
"取得タイプ", #Type の名称が入ります
"サイトURL", #
"取得年月日", #
#
# ここからはTRデータ項目(HTML取得出来る名称で設定してください)
#
"価格", #
"満室時利回り", #
"築年月", #
"満室時年収", #
"物件名", #
"建物構造/階数", #
"建物構造", #
"交通", #
"住所", #
"土地面積", #
"土地権利", #
"専有面積", #
"間取り", #
"建物面積", #
"建ペイ率/容積率", #
"接道状況", #
"用途地域", #
"防火/国土法", #
"管理費/修繕積立", #
"管理会社名/管理方式", #
"取引態様", #
"問い合わせ", #
"現況", #
"引渡", #
"更新予定日物件登録日", #
"管理ID", #
"営業時間", #
"定休日", #
"備考" #
]
# ⑦無効データ
del_item_ary = [
]
# 1項目内の改行文字を置き換えたもの
Line_Separator_In_Item = "@改行@"
# 開始時刻
s_time = Time.now
# ページ遷移待ち時間
Wait_Time = 6
# リトライタイム
Retry_Time = 5
# CSVデータのセパレータ
Separator_In_Data = "\t"
# CSV書き出し中フラグ
flg_w = false
# 実データ(1レコード分)格納領域
# csv_item_aryと同インデックスに値が入る
csv_data_ary = Array.new(csv_item_ary.length)
# ログ吐き出しメソッドの定義
def puts_log(log)
puts Time.now.strftime("%H:%M:%S") + ' ' + log.tosjis
end
# 改行コード類を固定文字列に変換する
def my_gsub_kaigyo(tmp_line, del_item_ary)
tmp_line.gsub!(/\r\n/,Line_Separator_In_Item)
tmp_line.gsub!(/\n/,Line_Separator_In_Item)
tmp_line.gsub!(/\r/,Line_Separator_In_Item)
tmp_line.gsub!(/\t/,'') #タブコードは、CSVセパレータなので無効にしておく
del_item_ary.each do |ditem|
tmp_line.gsub!(ditem,'')
end
return tmp_line
end
#
# ここからはじまり
#
# ブラウザオブジェクトの生成
WWW::Mechanize.html_parser = Hpricot
agent = WWW::Mechanize.new
agent.max_history = 1 # 履歴記録は現ページのみ(メモリ浪費させないため)
agent.user_agent_alias = 'Windows IE 7' # 重要:IE7のふりをする
#
# メイン処理ループ
# tkey : 取得サイトタイプ名
# tvalue : 取得サイトトップURL
Type.each {|tkey,tvalue|
puts_log('---' + tkey + 'の取得開始---')
site_top_url = tvalue
#取得サイトトップURLからURIオブジェクトを生成する(相対パスが出てきたときに絶対パス変換で必要)
parent_uri = URI.parse(site_top_url)
# 物件詳細URL 記録用配列
url_ary = Array.new
# トップページにアクセス
page_num = 1 # ページ番号
begin
page = agent.get(site_top_url) #pageオブジェクトを取得
rescue
puts_log(tkey + ':1ページ目 アクセス失敗。リトライします。')
sleep(Retry_Time)
retry
end
# 一覧リストから、物件ページURLを記録(開始)
begin
sleep(Wait_Time)
# 現在のページに対して、リンク一覧を取得し、未記録の物件ページURLを記録する
page.links.each do |link|
begin
url = parent_uri.merge(link.uri.to_s).to_s
rescue
# linkオブジェクトにはURL変換不能なもの(javascript)なども含まれるため
# 例外処理としてこのブロックはある。(とくになにもしない)
end
# 指定リンク文字列(url_match_str)があるか
if url!=nil and url.match(url_match_str)
# そのURLは格納済みか?
if not(url_ary.include? url)
# 未記録URLであれば格納する
url_ary.push url
end
end
end
puts_log(tkey + ':' + page_num.to_s + 'ページ目 物件リストURL取得完了')
# 次ページをめくる処理
if next_page_link = page.link_with(:text => next_page_link_match_str) and next_page_link.uri!=nil
page_num += 1 # ページ番号インクリメント
begin
page = nextPage_click(agent, next_page_link, page_num)
rescue
puts_log(tkey + ':' + page_num.to_s + 'ページ目 アクセス失敗。リトライします。')
sleep(Retry_Time)
retry
end
else
break # クリックすべきリンクがない場合は、ループを抜ける
end
end while true
# 一覧リストから、物件ページURLを記録(終了)
# 物件数(詳細ページURLの格納数)取得
bukken_count = url_ary.length
# 最初の書き出しであれば、CSVファイルにヘッダを書き出す
tmpCsvFileName = CsvFileName
if flg_w==false
if ARGV[0]!=nil
tmpCsvFileName = CsvFileName.gsub(%r!\.!,'_'+tkey+'.')
end
open(tmpCsvFileName,"w") do |f|
flg_w = true
f.write csv_item_ary.join(Separator_In_Data).tosjis + "\n"
end
end
# 記録済みの物件詳細URLからそのページにアクセスする
url_ary.each_with_index do |url, idx|
# データ記録先配列をクリア
csv_data_ary.clear
begin
sleep(Wait_Time)
# 物件詳細URLページにアクセス
page = agent.get(url)
rescue TimeoutError
puts_log(tkey + ':' + page_num.to_s + '(タイムアウト)物件詳細ページアクセス失敗。リトライします。 ' + url)
sleep(Retry_Time)
retry
rescue WWW::Mechanize::ResponseCodeError => ex
case ex.response_code
when '404' then
puts_log(tkey + ':' + page_num.to_s + '(404エラー)物件詳細ページアクセス失敗。Jump ' + url)
else
puts_log(tkey + ':' + page_num.to_s + '(' + ex.response_code.to_s + 'エラー)物件詳細ページアクセス失敗。リトライします。 ' + url)
sleep(Retry_Time)
retry
end
end
# 正常にレスポンスがあったページについてデータを取得(開始)
if page.code=="200"
# Hpricotでinner_textでは正しく変換されない文字列を置換しておく
# 文字化けがある場合は、ここで変換しておく
page.body.gsub!(%r!^ +!,'') # 行頭の空白を削除しておく
page.body.gsub!(%r! +$!,'') # 行末の空白を削除しておく
page.body.gsub!(%r!\t!,'') # タブを削除しておく
page.body.gsub!(%r! !,' ') # HTML特殊コードの半角空白が正常にinner_textできないので空白に置換しておく
page.body.gsub!(%r! !,' ') # HTML特殊コードの半角空白が正常にinner_textできないので空白に置換しておく
page.body.gsub!(%r! !,' ') # HTML特殊コードの半角空白が正常にinner_textできないので空白に置換しておく
page.body.gsub!(%r! !,' ') # HTML特殊コードの半角空白が正常にinner_textできないので空白に置換しておく
page.body.gsub!(%r! !,' ') # HTML特殊コードの半角空白が正常にinner_textできないので空白に置換しておく
page.body.gsub!(%r!"!,'') # CSV出力に影響があるため、ダブルコーテーションを無効にしておく
# HTML上改行テキストは特殊文字に置換しておく
page.body.gsub!(%r!\r\n!,Line_Separator_In_Item)
page.body.gsub!(%r!\n!,Line_Separator_In_Item)
page.body.gsub!(%r!\r!,Line_Separator_In_Item)
doc = page.root # 現ページからドキュメントを取得する
trs = doc/:tr # trタグ一覧を取得
tr_data_ary = Array.new # 一時データ格納用
trs.each do |trs_data|
tmp_line = trs_data.inner_html
tmp_line.gsub!(%r!(
tr_data = tmp_line.split(Line_Separator_In_Item)
tr_data.delete("") # 配列要素の無駄な空白は削除
del_item_ary.each do |ditem|
tr_data.delete(ditem) # 無効にすべきデータを削除する
end
tr_data_ary.push tr_data # 一時データ格納用へ追加
end
tr_data_ary.each do |tr|
tr.each do |tr_data|
tr_data.gsub!(%r!^ +!,'') # 行頭の空白を削除しておく
tr_data.gsub!(%r! +$!,'') # 行末の空白を削除しておく
end
end
prev_item_idx = -1 # 項目番号(-1 は対応する項目番号なし)
flg_d = false # データ登録
tr_data_ary.each do |tr|
if flg_d==true
prev_item_idx = -1
flg_d = false
end
tr.each do |tr_data|
#データが項目名と一致するか
if now_item_idx = csv_item_ary.index(tr_data)
# 項目名と一致するので、項目番号をセットする
prev_item_idx = now_item_idx
flg_d = false
else
now_item_idx = -1 #-1は非項目を表す(すなわち記録する可能性の高いデータ)
# prev_item_idx>=0 すなわち、項目内であることがわかっている場合
if prev_item_idx>=0
# データを記録する(初めての場合はそのまま、何か入っている場合は Line_Separator_In_Item をはさんで記録)
if not csv_data_ary[prev_item_idx]
csv_data_ary[prev_item_idx] = tr_data
else
csv_data_ary[prev_item_idx] = csv_data_ary[prev_item_idx] + Line_Separator_In_Item + tr_data
end
flg_d = true
end
end
end
end
#
# 基本的にここは変更しない(ここから)
#
# 取得番号
csv_data_ary[csv_item_ary.index("取得番号")] = (idx+1)
# 取得タイプ
csv_data_ary[csv_item_ary.index("取得タイプ")] = tkey
# サイトURL
csv_data_ary[csv_item_ary.index("サイトURL")] = url
# 取得年月日 YYYYmmdd形式
csv_data_ary[csv_item_ary.index("取得年月日")] = s_time.strftime("%Y%m%d")
#
# 基本的にここは変更しない(ここまで)
#
#
# CSVとしてデータを書き出す
#
open(tmpCsvFileName,"a") do |f|
f.write csv_data_ary.join(Separator_In_Data).tosjis + "\n"
end
puts_log(tkey + ':' + 'CSV書出し ' + (idx+1).to_s + "/" + bukken_count.to_s)
else
puts_log(tkey + ':' + '404エラー:データ取得不能 次へJUMP' + url)
end
# 正常にレスポンスがあったページについてデータを取得(終了)
end
puts_log('---' + tkey + 'の取得終了---')
}
puts_log(tmpCsvFileName + ' 処理時間 '+(Time.now-s_time).to_s + 's')
0 件のコメント:
コメントを投稿