Created
February 24, 2016 02:32
-
-
Save mogya/ec02020b8af8cbeec750 to your computer and use it in GitHub Desktop.
Wgetをラップして使うスクレイパー
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding: utf-8 -*- | |
require 'lib/scraper.rb' | |
require "uri" | |
require "kconv" | |
#WEBページを取得する際、キャッシュやアクセス時間の間隔などを配慮するためのクラス | |
class WgetScraper < Scraper | |
include DebugLog | |
CACHE_DIR = '/tmp/WgetScraper/' | |
COOKIE_FILE = '/tmp/WgetScraper/cookie.txt' | |
WGET = '/usr/bin/wget' | |
CHMOD = '/bin/chmod' | |
MKDIR = '/bin/mkdir' | |
def initialize(params=nil,log=nil) | |
debugLog_init(log) | |
# wgetでアクセスする時のアクセス間隔 | |
default_interval = 5 | |
@interval = params['interval'] if (params && params['interval']) | |
@interval = @interval || default_interval | |
@keep_cache_seconds = params['keep_cache_seconds'] if (params && params['keep_cache_seconds']) | |
@keep_cache_seconds = @keep_cache_seconds || 60*60*24*7*3 # 3week:キャッシュしたファイルを返す期間 | |
@userAgent = params['userAgent'] if (params && params['userAgent']) | |
@userAgent = @userAgent||"mogya scraper. contact me at [email protected] if any problem." | |
@lastAccess = Time.at(0) | |
@forceFileName = params['forceFileName']||false #ファイル名をwgetに任せずにWgetScraperで決定する。ファイル末のmemo参照。 | |
@clearlyFileAndDirectory = params['clearlyFileAndDirectory']||false #ファイル名の末尾に_をつけてディレクトリと明確に分ける。ファイル末のmemo参照。 | |
@use_MD5_for_query = params['use_MD5_for_query']||false #query部分をファイル名にするとき、無理にデコードせずにmd5を使う | |
#キャッシュディレクトリがあることを確認 | |
if (!File.exist?(CACHE_DIR)) | |
Dir::mkdir(CACHE_DIR) | |
end | |
#WGETが使えることを確認 | |
test = `#{WGET} -V` | |
raise "#{WGET} not found. need to set WGET constance?" if (test.length <= 0) | |
end | |
def get(uri,param=nil) | |
filename = get_cache_path(uri) | |
wget_get(uri) if (!cache_valid?(filename)) | |
return read_cache(uri) | |
end | |
#アクセス間隔を保証するための関数 | |
#前回アクセスから@interval 以上たっていなければ、残り時間分sleepする。 | |
#todo:出来ればドメイン別に記録を取っておいて、違うドメインならそのままアクセスしたい | |
def sleep_between_access(uri) | |
now = Time.now | |
# 朝6時以降はアクセス頻度を大幅に落とす | |
interval = (now.hour<6)?@interval:@interval*10 | |
left_interval = interval - (now - @lastAccess) | |
if ( left_interval>0 ) | |
debug("wgetScraper: sleep #{sleep left_interval}s..") | |
sleep left_interval | |
end | |
@lastAccess = Time.now | |
end | |
def wget_get(uri) | |
sleep_between_access(uri) | |
debug('doing accual network access to '+uri) | |
wget_cmd = %Q(#{WGET} -x -N -P "#{CACHE_DIR}" "#{uri}" --user-agent="#{@userAgent}" --load-cookies "#{COOKIE_FILE}" --save-cookies "#{COOKIE_FILE}" --keep-session-cookies --no-check-certificate 2>&1) | |
if(@forceFileName) | |
cachepath = get_cache_path(uri) | |
cachedir = (cachepath+' ').split('/')[0..-2].join('/') | |
`#{MKDIR} -p #{cachedir}` #フォルダは作っておいてあげないといけない。 | |
wget_cmd = %Q(#{WGET} -x -O "#{cachepath}" "#{uri}" --user-agent="#{@userAgent}" --load-cookies "#{COOKIE_FILE}" --save-cookies "#{COOKIE_FILE}" --keep-session-cookies --no-check-certificate 2>&1) | |
end | |
status = `#{wget_cmd}` | |
# debug("wget_cmd:#{wget_cmd}") | |
# debug("status:#{status}") | |
if ($?.exitstatus!=0) | |
response_code = status.scan(/HTTP request sent, awaiting response\.\.\. ([0-9]{3})/)[0][0] rescue nil | |
raise ScraperException.new("#{$?.exitstatus}. #{status}", response_code) | |
end | |
#普通にwgetすると、アクセス権が775になる。この場合、Apache経由のCGIでは同じプログラムがキャッシュを書けなくなってしまう。 | |
#(なんだかちょとまずいような気もするけど)ここで777にしてしまおう。 | |
`#{CHMOD} -R 777 #{CACHE_DIR} > /dev/null 2>&1` | |
end | |
def get_cache_path(uri) | |
uri_obj = URI.parse(uri) | |
filename = "#{uri_obj.host}#{uri_obj.path}" | |
if (uri_obj.query) | |
if (@use_MD5_for_query) | |
filename = filename+"?md5_"+ Digest::MD5.hexdigest(uri_obj.query) | |
else | |
#wgetの挙動として、':'以外はエンコードするみたいなので真似る。 | |
filename = filename+"?"+ URI.encode(URI.decode(uri_obj.query), /[^-_.!~*'()a-zA-Z\d;?:@&=+$,\[\]]/) | |
end | |
end | |
# wgetがindex.htmlを自動補完してくれるので、ファイル名も対応しておく。 | |
if(@clearlyFileAndDirectory) | |
filename.sub!(/([^\/]$)/, '\1_') | |
end | |
filename.sub!(/\/$/, '/index.html') | |
filename.sub!(/\/\?/, '/index.html?') | |
cache_path = "#{CACHE_DIR}#{filename}" | |
return cache_path | |
rescue ArgumentError=>e | |
# URI.encode/URI.decodeあたりで落ちることがあるので調査中 | |
info e.to_s | |
info "uri:%s"%uri | |
raise e | |
end | |
#キャッシュが存在して有効期間内である時TRUEを返す | |
def cache_valid?(filename) | |
return false if (!File.readable?(filename)) | |
return false if (File.size(filename)==0) #ファイルサイズが0ならキャッシュを破棄して読み直す | |
return ( Time.now - File.ctime(filename) < @keep_cache_seconds ) | |
end | |
def read_cache(uri) | |
file_path = get_cache_path(uri) | |
return File.read(file_path).toutf8 | |
end | |
def destruct_cache(uri) | |
file_path = get_cache_path(uri) | |
debug("wgetScraper: destruct #{file_path}") | |
return File.delete(file_path) | |
end | |
end | |
=begin | |
irb -Ku | |
load 'lib/wgetScraper.rb' | |
userAgent = 'test scraper. [email protected]' | |
scraper = WgetScraper.new( {'userAgent'=>userAgent}, $log ) | |
url = 'http://oasis.mogya.com/test.txt?a=b&c=d' | |
scraper.get_cache_path(url) | |
scraper.wget_get(url) | |
scraper.read_cache(url) | |
scraper.get(url) | |
require 'lib/wgetScraper.rb' | |
userAgent = 'test scraper. [email protected]' | |
scraper = WgetScraper.new( {'userAgent'=>userAgent}, Logger.new(STDERR) ) | |
scraper.get('http://oasis.mogya.com/test.txt?a=b&c=d') | |
scraper.get('http://oasis.mogya.com/test.txt?a=b&c=d') | |
=end | |
=begin | |
forceFileName と clearlyFileAndDirectory について | |
いずれも、wordpressで作成されたサイトのスクレイピングで引っかかる問題を回避するための実装。 | |
とはいえ、問題自体は一般的に起こりうるものなので普通のオプションとして実装した。 | |
URLエンコードされた日本語ファイル名 | |
たとえば、 | |
'http://dengen-cafe.com/archives/tag/%e3%82%a2%e3%83%b3%e3%83%86%e3%82%a3%e3%83%bb%e3%82%a2%e3%83%b3%e3%82%ba' | |
というURLをwgetで普通にスクレイプすると、一部の制御文字を無視するもんだから、なんだかわからないファイル名になってしまう。 | |
この現象をWgetScraperで再現するのは難しいので、キャッシュを取得できない問題になる。 | |
forceFileNameをつけると、キャッシュファイル名をwgetに任せずにWgetScraperで決めたファイル名を強制する。 | |
これにより上記現象を回避できるけど、リダイレクトされたら意図しないファイル名になってしまったりする可能性が残るので | |
不必要には使わないほうがいいと思う。 | |
ディレクトリと区別のつかないファイル名 | |
たとえば | |
http://dengen-cafe.com/archives/category/tokyo/station/ | |
にアクセスすると、/のつかない station にリダイレクトされる。 | |
ところが、 | |
http://dengen-cafe.com/archives/category/tokyo/station/nikotamaeki | |
というページも存在する。 | |
スクリプトでURIをパースしていて実ファイルと結びつかないから、こういう実装をしても気にならないらしい。 | |
キャッシュする方は、ディレクトリなのかファイル名なのかわからないから泣きそうなんだけど。 | |
仕方ないので、 clearlyFileAndDirectory オプションを指定すると、/ で終わらないURIの最後に_をつけて保存する。 | |
もし本当にそんな名前のURIがあったら困るけど、まあ普通無いだろう。 | |
wgetの挙動と異なってしまうので、forceFileNameとセットで指定することが前提。 | |
=end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment