週刊(月刊?)プレカリアート
RubyでSBIランキングの売り越し額を調べる
- 2010-05-05 (水)
- Ruby
SBI証券は個人投資家が多く使っている証券会社です。通常は個人投資家は機関投資家に搾取されるようにできています。株価が暴落すると個人投資家は飛びつき、下げ止まらないとどんどんナンピン買いをして損を膨らませていきます。投資のスタイルは人それぞれですが、個人が買い越している間は手を出さずに売り越しに転じた辺りで買うというのもよいと思っています。
これを毎日、前場と後場にランキングを見て、買い代金と売り代金を比較してソートするのは面倒ですので、これを自動でやらせます。
方針
いつものようにMechanizeで該当のページを読み、xpathで必要な部分だけ取り出して解析します。
売りと買いを配列に入れて1つずつ照合、同じコードを持つもので買い代金と売り代金の差を取って新しい配列に入れます。これをソートして出力すればOK。
実装例
#!/usr/bin/ruby -Ku
require 'rubygems'
require 'nokogiri'
require 'mechanize'
require 'logger'
require 'kconv'
SBI_RANKING = "https://trading1.sbisec.co.jp/ETGate/?_ControlID=WPLETmgR001Control&_DataStoreID=DSWPLETmgR001Control&burl=search_market&dir=ranking%2F&file=et_trading_value.html&cat1=market&cat2=ranking&getFlg=on"
BUY_XPATH = "/html/body/div[@id='middleArea']/div[@id='middleAreaM']/table/tr/td[1]/div[@id='main']/table[2]/tr/td[2]/table[4]/tr/td[1]/table"
SELL_XPATH = "/html/body/div[@id='middleArea']/div[@id='middleAreaM']/table/tr/td[1]/div[@id='main']/table[2]/tr[1]/td[2]/table[4]/tr/td[3]/table"
DATE_XPATH = "/html/body/div[@id='middleArea']/div[@id='middleAreaM']/table/tr/td[1]/div[@id='main']/table[2]/tr[1]/td[2]/table[3]/tr/td[1]/b"
TIME_XPATH = "/html/body/div[@id='middleArea']/div[@id='middleAreaM']/table/tr/td[1]/div[@id='main']/table[2]/tr[1]/td[2]/table[3]/tr/td[2]"
#---------------------------------------------------------------------------
# main
#---------------------------------------------------------------------------
def main
agent = Mechanize.new
log = Logger.new(STDOUT)
# log = Logger.new("sbi_ranking.log")
log.level = Logger::INFO
agent.get(SBI_RANKING)
agent.page.encoding = "Shift_JIS"
html = agent.page.body
parser = Nokogiri::HTML.parse(html, nil)
# 時刻
ranking_time = parser.xpath(TIME_XPATH)
/(\d{2,2}):(\d{2,2})<\/td>$/ =~ ranking_time.to_html.toutf8
sbi_hour = $1
sbi_min = $2
# 日付
ranking_date = parser.xpath(DATE_XPATH)
/.*?(\d{1,2})\/(\d{1,2})/ =~ ranking_date.to_html.toutf8
sbi_month = $1
sbi_day = $2
buy_code = []
buy_name = []
buy_trading_value = []
sell_code = []
sell_name = []
sell_trading_value = []
for i in 3..22 do
buy = parser.xpath(BUY_XPATH + "/tr[#{i}]")
e = buy.to_html.toutf8.gsub("\n", "")
/stock_sec_code_mul=(\d{4,4}).*?exchange_code.*?>(.*?)<.*?([\d,]+?)<\/div>/ =~ e
buy_code << $1
buy_name << $2
buy_trading_value << $3.gsub(",", "")
end
for i in 3..22 do
sell = parser.xpath(SELL_XPATH + "/tr[#{i}]")
e = sell.to_html.toutf8.gsub("\n", "")
/stock_sec_code_mul=(\d{4,4}).*?exchange_code.*?>(.*?)<.*?([\d,]+?)<\/div>/ =~ e
sell_code << $1
sell_name << $2
sell_trading_value << $3.gsub(",", "")
end
result = []
for i in 0..buy_code.size - 1 do
for j in 0..sell_code.size - 1 do
if buy_code[i] == sell_code[j] then
result.push( [buy_code[i], buy_name[i], ( buy_trading_value[i].to_i - sell_trading_value[j].to_i )] )
end
end
end
result = result.sort{ |a, b| a[2] <=> b[2] }
for i in 0..result.size - 1 do
puts "#{result[i][0]}, #{result[i][1]}, #{result[i][2]}"
end
end
#---------------------------------------------------------------------------
# エントリポイント
# 単にmainを呼ぶだけ
#---------------------------------------------------------------------------
main
RubyでNHKラジオ講座のストリーミングを保存する
- 2010-05-05 (水)
- Ruby
NHKのラジオ講座は安価に語学を学ぶことができる。ラジオを持っていない人でもストリーミングで聴くことができる。ただしロシア語などいくつかの講座は提供されていない。
ストリーミングは電車の中でiPodに入れて聴きたいと思ったときには不便である。ここではストリーミングファイルを自動的にダウンロードしてmp3に変換する。
ツールを手に入れる
ストリーミングファイルをダウンロードするにはRTMPDumpを使う。ビルド版もあるし、portやyumなどでインストールしてもいい。
flvをmp3に変換するにはFFmpegを使う。これは多種のコーデックをサポートするツールだが、そのぶんビルドが面倒臭い。既にビルドされているものを使うか、やはりportやyumを使って入手するとよい。
準備
まず語学講座のURLを連想配列に入れる。また、flvの情報が記録されているXMLファイルlistdataflv.xmlやストリーミングファイル置き場のURLも定数に入れておく。
#!/usr/local/bin/ruby -Ku
require 'rubygems'
require 'mechanize'
require 'nokogiri'
require 'kconv'
require 'logger'
require "fileutils"
RTMPDUMP = "rtmpdump "
FFMPEG = "ffmpeg "
STREAMING_BASE = "rtmp://flv9.nhk.or.jp/flv9/_definst_/gogaku/streaming/flv/"
COURSE_XML = "listdataflv.xml"
COURSE = {
"基礎英語1" => "http://www.nhk.or.jp/gogaku/english/basic1/",
"基礎英語2" => "http://www.nhk.or.jp/gogaku/english/basic2/",
"基礎英語3" => "http://www.nhk.or.jp/gogaku/english/basic3/",
"英語5分間トレーニング" => "http://www.nhk.or.jp/gogaku/english/training/",
"ラジオ英会話" => "http://www.nhk.or.jp/gogaku/english/kaiwa/",
"入門ビジネス英語" => "http://www.nhk.or.jp/gogaku/english/business1/",
"実践ビジネス英語" => "http://www.nhk.or.jp/gogaku/english/business2/",
"まいにち中国語" => "http://www.nhk.or.jp/gogaku/chinese/kouza/",
"まいにちフランス語" => "http://www.nhk.or.jp/gogaku/french/kouza/",
"まいにちイタリア語" => "http://www.nhk.or.jp/gogaku/italian/kouza/",
"まいにちハングル講座" => "http://www.nhk.or.jp/gogaku/hangeul/kouza/",
"まいにちドイツ語" => "http://www.nhk.or.jp/gogaku/german/kouza/",
"まいにちスペイン語" => "http://www.nhk.or.jp/gogaku/spanish/kouza/"
}
ダウンロードメソッドを作る
まず連想配列の名前を与えてdownloadメソッドを呼ぶ。存在しない講座ならreturnしておく。
ファイルが乱雑に置かれると面倒なので講座ごとにフォルダを作る。まだフォルダがなければ作成しておく。
MechanizeでXMLファイルをgetして、NokogiriのXMLパーサで解析する。解析にはxpathを使う。ファイル名はfile属性にある。講座名や日付もあるので利用する。
まだファイルがないときには、RTMPDumpを用いてflvファイルをダウンロードする。mp3ファイルがまだないときは続けてFFmpegを使って変換する。
最後に連続してアクセスしないように待ちを入れる。
#---------------------------------------------------------------------------
# ラジオ講座のダウンロード
#---------------------------------------------------------------------------
def download(course)
if COURSE[course] == nil then
return
end
unless( FileTest.exist?(course) ) then
FileUtils.mkdir_p course
end
agent = Mechanize.new
url = COURSE[course] + COURSE_XML
agent.get(url)
parser = Nokogiri::XML.parse(agent.page.body, nil)
parser.xpath("musicdata/music").each do |element|
flvname = element.attr("file")
filename = course + "/" + element.attr("kouza") + "_" + element.attr("hdate")
unless( FileTest.exist?("#{filename}.flv") ) then
command = RTMPDUMP + "-o #{filename}.flv -r #{STREAMING_BASE+flvname}"
system(command)
end
unless( FileTest.exist?("#{filename}.mp3") ) then
command = FFMPEG + "-i #{filename}.flv #{filename}.mp3"
system(command)
end
end
sleep(1)
end
聴きたい講座をダウンロードする
#---------------------------------------------------------------------------
# エントリポイント
#---------------------------------------------------------------------------
download("基礎英語1")
聴きたい講座を指定してdownloadメソッドを呼ぶ。
実行結果
RTMPDump v2.2 (c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL Connecting ... Starting download at: 0.000 kB Metadata: duration 900.76 audiodatarate 64.00 audiodelay 0.04 audiocodecid 2.00 canSeekToEnd TRUE 7577.419 kB / 900.81 sec (100.0%) Download complete FFmpeg version 0.5.1, Copyright (c) 2000-2009 Fabrice Bellard, et al. configuration: --prefix=/opt/local --disable-vhook --enable-gpl --enable-postproc --enable-swscale --enable-avfilter --enable-avfilter-lavf --enable-libmp3lame --enable-libvorbis --enable-libtheora --enable-libdirac --enable-libschroedinger --enable-libfaac --enable-libfaad --enable-libxvid --enable-libx264 --enable-nonfree --mandir=/opt/local/share/man --enable-shared --enable-pthreads --cc=/usr/bin/gcc-4.2 --arch=x86_64 libavutil 49.15. 0 / 49.15. 0 libavcodec 52.20. 1 / 52.20. 1 libavformat 52.31. 0 / 52.31. 0 libavdevice 52. 1. 0 / 52. 1. 0 libavfilter 1. 4. 0 / 1. 4. 0 libswscale 1. 7. 1 / 1. 7. 1 libpostproc 51. 2. 0 / 51. 2. 0 built on Apr 11 2010 12:49:33, gcc: 4.2.1 (Apple Inc. build 5646) (dot 1) Input #0, flv, from '基礎英語1/基礎英語14月30日放送分.flv': Duration: 00:15:00.75, start: 0.000000, bitrate: 64 kb/s Stream #0.0: Audio: mp3, 44100 Hz, mono, s16, 64 kb/s Output #0, mp3, to '基礎英語1/基礎英語14月30日放送分.mp3': Stream #0.0: Audio: libmp3lame, 44100 Hz, mono, s16, 64 kb/s Stream mapping: Stream #0.0 -> #0.0 Press [q] to stop encoding [libmp3lame @ 0x101812800]lame: output buffer too small (buffer index: 9404, free bytes: 388) Audio encoding failed
FFmpegのバージョンによってはAudio encoding failedになる。解決策がここにある。
[libmp3lame @ 0x101812800]lame: output buffer too small (buffer index: 9404, free bytes: 388) Audio encoding failed
Rubyで2chの市況1板のニュースを集めTweetする
- 2010-05-05 (水)
- Ruby
2chの市況1板には多くの人(個人投資家)が集まって、株価に一喜一憂している。そのため、何かよい材料がないか常に探し求めている状況にある。もしよいニュースを見つけたら「キタ━━━━━━(゜∀゜)━━━━━━」とすぐに書き込んでくれる。これは場合によると自分の見ているニュースサイトに掲載されるより早く情報を手に入れられるかも知れない。
しかし、スレッドは多く全部見ているのは大変である。雑談も多いし、必要な情報だけいち早く報告したらトレードに有利なのではないかと考えた。信頼性の高い情報はソースを載せることが多いので、ニュースサイトのURL付きの書き込みを有用度が高いと判断することにする。
スレッド一覧を取得する
スレッドの一覧は前回書いたようにsubject.txtを見れば手に入る。これをmechanizeでgetする。
BOARD = "http://anchorage.2ch.net/livemarket1/" DAT = "dat/" SUBJECT = "subject.txt"
このように定数を用意しておく。
株価コードで絞り込む
市況1板では慣例である銘柄についてのスレッドに株価コードを入れることになっている。自分にとって関心のある銘柄のコードで探せば上手く見つけられる。これを事前に用意して配列に入れておく。
STOCK_CODE = [ "2497", "5401", "5405", "5411", "5713", "6301", "6752", "6758", "7203", "7974", "8002", "8031", "8058", "8306", "8316", "8411", "8802", "9132", "9433", "9984", "日経" ]
ここでは出来高の大きそうな銘柄と「日経」に関するスレッドを読むことにする。1行ずつSTOCK_CODEの配列にある文字列を含むか見ていき、該当するものだけをdatsという配列に格納する。
#--------------------------------------------------------------------------- # getThreads #--------------------------------------------------------------------------- def getThreads(list) # 読むべきdatリスト dats = [] # 1行ずつ判定 list.each_line do |line| d = line.split( /<>/ ) STOCK_CODE.each do |code| if d[1].include?(code) then dats << d[0] end end end #重複があれば除去 dats.uniq! return dats end
不要なDATファイルを削除する
過去に取得したものの、既に板から消えてしまったDATファイルは不要だから削除する。
#--------------------------------------------------------------------------- # deleteThreads #--------------------------------------------------------------------------- def deleteThreads(dats) # カレントディレクトリのファイル一覧を消去候補リストに入れる fileList = Dir::entries(Dir::pwd) # datファイル以外は消去リストから取り除く fileList.delete_if do |x| /\.dat$/ !~ x end # カレントディレクトリの.datファイルでdatsにないものを消す fileList.each do |name| # flagがtrueのものは消す flag = true dats.each do |dat| if name == dat then # 取得したいdatファイルは消さないでおく flag = false end end # datファイルで不要なものは削除する if flag == true then File.delete(name) $log.info(name + "を消去しました") end end end
必要なスレッドを読む
dats配列に格納されたスレッドを読む。ただし、丸ごと読むとサーバに負担をかけるので差分ダウンロードをする。差分ダウンロードはwgetでもできるが、差分だけ解析する処理が面倒臭いので自力でやることにする。
差分ダウンロードはHTTPのリクエストヘッダにRangeヘッダを追加してリクエストすればよい。まずローカルに保存されたDATファイルがあるなら容量を計算して、それをRangeにセットすればよい。転送量を減らすためにaccept-encodingにgzipを入れておく。
これでリクエストしたものは差分になる。簡易あぼーん対策として、最初に改行コードが来ていなければ、あぼーんによってスレッドの構成が変わっていると判断する。市況1板ではそれほど個人情報さらしなどの悪質な書き込みがないので、簡易対策だけに留めるが、必要なら丸ごと再取得する。
連続してリクエストするとサーバに負荷をかけて迷惑になるのでsleepを適当に入れる。
#---------------------------------------------------------------------------
# 必要なスレッドをクロールする
#---------------------------------------------------------------------------
def crawl( name )
daturl = BOARD + DAT + name
$log.info( daturl + "を取得中")
header = {}
header['User-Agent']= 'Monazilla/1.00 (super-hikky/1.0)'
length = 0
if File.exist?( name )
length = File.stat( name ).size
datfile = open( name, 'a' )
else
datfile = open( name, 'w' )
end
if length > 0
header['Range'] = "bytes=#{length-1}-"
header['accept-encoding'] = "gzip"
end
url = URI.parse(daturl)
res = Net::HTTP.start(url.host, url.port) do |http|
http.get(url.path,header)
end
#簡易あぼーん対策
if res.body[0] != '\n' then
datfile.print res.body
analyze(res.body)
end
datfile.close
sleep(10)
end
ニュースを取り出す
書き込まれたURLが全てニュースとは限らない。そこで、ニュースサイトのドメインを用意しておく。
NEWS_SITE = [ "www.bloomberg.co.jp", "jp.reuters.com/article", "www.nikkei.co.jp/news", "www3.nhk.or.jp/news", "www.yomiuri.co.jp", "www.asahi.com", "www.business-i.jp", "sankei.jp.msn.com", "news.livedoor.com/article", "headlines.yahoo.co.jp", "www.tokyo-np.co.jp/article", "news.searchina.ne.jp", "www.jiji.com", "www.worldtimes.co.jp", ]
これに該当するものだけをニュースサイトであると判断する。過不足があればこの配列に足してもよい。2chではhttpをttpと書くこともあるので、ドメイン名のみにしておく。これを用いて判断する。
重複したニュースを読むのはコストの無駄なので、recent_news.txtに既に読んだニュースのURLを記録しておく。
新しく取得した差分から1行ずつURLが含まれているかscanする。URLが含まれるときはrecent_news.txtに含まれていないのを確認して、news配列に入れていく。また、recent_news.txtにも記録する。もしかするとrecent_news.txtに記録するタイミングはTwitterにつぶやいた後の方がいいかもしれない。
#------------------------------------------------------------------------------------------
# ニュースサイトを抽出
#------------------------------------------------------------------------------------------
def analyze( body )
body.each_line do |each_res|
# URLのみ取り出す
r = each_res.scan(/ttp\:\/\/([\w\+\$\;\?\.\%\,\!\#\~\*\/\:\@\&\\\=\_\-]+)/)
# URLが入っている場合
if r.size > 0 then
# 全てのURLを精査
r.each do |url|
NEWS_SITE.each do |news|
if url[0].include?( news ) then
# 過去に読んでいるならtrue
flag = false
news_url = "http://" + url[0]
recent_news = open("recent_news.txt", "a+")
recent_news.each do |line|
if line.include?(news_url) then
flag = true
break
end
end
if flag == false then
$news << news_url
recent_news.puts(news_url)
end
recent_news.close
end
end # each
end # each
end # if
end #each
end
ニュースを読む
ニュースを読み、Twitterにつぶやく。まず、前回取得したOAuthのキー類を用意する。
CONSUMER_KEY = "GDzTag(略)" CONSUMER_SECRET = "7TbeOj9waUkqVCOiP(略)" ACCESS_TOKEN = "91938679-(略)" ACCESS_TOKEN_SECRET = "hvwhVudtK8QOD(略)"
これを用いて
#------------------------------------------------------------------------------------------ # ニュースサイトを抽出 #------------------------------------------------------------------------------------------ def readNews $news.each do |url| begin $agent.get(url) puts "getting " + url title = $agent.page.title.toutf8.chomp $log.info(title) text = title + " " + url #OAuth # http://d.hatena.ne.jp/shibason/20090802/1249204953を参考にしました consumer = OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET, :site => "http://twitter.com") oauth = Twitter::OAuth.new(CONSUMER_KEY, CONSUMER_SECRET) oauth.authorize_from_access(ACCESS_TOKEN, ACCESS_TOKEN_SECRET) twitter_client = Twitter::Base.new(oauth) twitter_client.update(text) rescue rescue Timeout::Error ensure end end end
実行する
#---------------------------------------------------------------------------
# main
#---------------------------------------------------------------------------
def main
# 板一覧を取得
$agent.get(BOARD + SUBJECT)
board = $agent.page.body.toutf8
puts board
# 読むべきスレッド名を取得
dats = getThreads(board)
# 不要なDATファイルを消去する
deleteThreads(dats)
# 必要なスレッドを読む
dats.each do |name|
crawl(name)
end
# ニュースをげっと
if $news.size > 0 then
$log.info("ニュースあり")
readNews
else
$log.info("ニュースがありません><")
end
end
#---------------------------------------------------------------------------
# エントリポイント
#---------------------------------------------------------------------------
#$log = Logger.new("log.txt")
$log = Logger.new(STDOUT)
$log.level = Logger::INFO
$agent = Mechanize.new
#かき集めたニュースURLが入る配列
$news = []
#mainを呼ぶ
main
実行結果
1272986450.dat<>■■■明日の日経平均を予想するスレ14685■■■ (54) 1272984242.dat<>日経225先物オプション実況スレ6264 (907) 9241004012.dat<>【2ちゃんねる十周年】携帯からお試し●を使ってみよう! 【規制?】 (30) (中略) I, [2010-05-05T03:10:00.196231 #858] INFO -- : 1272744747.datを消去しました (中略) I, [2010-05-05T03:10:00.198558 #858] INFO -- : 1272876392.datを消去しました I, [2010-05-05T03:10:00.198603 #858] INFO -- : http://anchorage.2ch.net/livemarket1/dat/1272986450.datを取得中 I, [2010-05-05T03:10:01.709770 #858] INFO -- : http://anchorage.2ch.net/livemarket1/dat/1272984242.datを取得中 (中略) I, [2010-05-05T03:11:03.133612 #858] INFO -- : http://anchorage.2ch.net/livemarket1/dat/1266856385.datを取得中 I, [2010-05-05T03:11:04.344469 #858] INFO -- : http://anchorage.2ch.net/livemarket1/dat/1270186921.datを取得中 I, [2010-05-05T03:33:28.767688 #1070] INFO -- : ニュースあり getting http://www.asahi.com/photonews/images/TKY201004290227.jpg getting http://jp.reuters.com/article/topNews/idJPJAPAN-15081720100430 I, [2010-05-05T03:33:30.066649 #1070] INFO -- : ギリシャは厳しい緊縮財政措置の構え、市場は安心感で回復 | Reuters getting http://www.bloomberg.co.jp/apps/news?pid=90920008&sid=au._wm7SnJDQ I, [2010-05-05T03:33:33.845826 #1070] INFO -- : ギリシャでデモ活動が激化、遺跡アクロポリス占拠も−緊縮財政に抗議 - Bloomberg.co.jp
なお、ここでつぶやいたニュースはボットをフォローすれば読むことができる。
参考になる本
著者/訳者:Array
出版社:オライリージャパン( 2008-07-25 )
定価:¥ 3,570
大型本 ( 392 ページ )
ISBN-10 : 4873113644
ISBN-13 : 9784873113647
どちらかというと機械学習の本で、サポートベクターマシン(SVM)とか面白い話題にも踏み込んである。
Javaスパイダーツールサンプル&クックブック―自動アクセス&収集・加工プログラム
著者/訳者:Array
出版社:秀和システム( 2005-05 )
定価:¥ 2,730
単行本 ( 377 ページ )
ISBN-10 : 4798010618
ISBN-13 : 9784798010618
好きな本だけど新品で手に入れることは難しいようだ。
Spidering hacks―ウェブ情報ラクラク取得テクニック101選
著者/訳者:Kevin Hemenway Tara Calishain
出版社:オライリー・ジャパン( 2004-05 )
定価:¥ 3,675
単行本 ( 516 ページ )
ISBN-10 : 4873111870
ISBN-13 : 9784873111872
やや古いけどいい本。Perlで書いてあるけど内容は問題ない。マナーについてなど単に技術的な話だけに留まらないので是非読むべき。オライリーなのでGoogle Booksで読むことができる。
- Search
- Feeds
- Meta

















