lp6m’s blog

いろいろかきます

Yahoo!ボックス からファイルを一括ダウンロードするスクリプト

Yahoo!ボックスとかいうオンラインストレージサービスがあるらしい。
info.box.yahoo.co.jp
アプリが終了してブラウザから1つ1つダウンロードするしかないらしい。助けてくれ!と友人に頼まれた。
しばらくChromeデベロッパーツールで眺めていると、ファイル情報をJSONでやりとりしているだけなようなので、書いた。

はじめはRuby の Mechanize を使って Yahoo! JAPAN にログインする - kaosf’s diaryを参考にしてログイン処理をしていたが、途中からログインできなくなった。Mechanizeだと今どのような状態なのかがわかりにくいので、Selenium-webdriverを使って再現してみると、文字認証を求められていた。
探してみるとrubyでYahoo Japanにログインする。Cookie発行してもらう - それマグで!のような情報がでてきたので、使わせていただいた。
(微妙にCAPCHAのURLの種類が違ったのでそのへんだけ書き換えたりした)
2016/12/30 Windows環境で動作させると,txt以外がファイルが壊れるとの指摘をいただきました。訂正しました。

#参考:http://takuya-1st.hatenablog.jp/entry/20121018/1350587902
#!/usr/bin/env ruby
#coding:utf-8
require 'rubygems'
require 'mechanize'
require 'open-uri'
require 'net/http'
require 'uri'

OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE

Id       = 'XXXXXXXXXXXXXXXX'
Password = '****************'
cookie_jar_yaml_path = 'yahoo.yaml'
#Yahoo!ログイン
agent = Mechanize.new
agent.user_agent_alias = 'Windows IE 7'
agent.get('https://login.yahoo.co.jp/config/login?.src=www&.done=http://www.yahoo.co.jp')
agent.page.form_with(name: 'login_form') do |form|
	form.field_with(name: 'login').value = Id
	form.field_with(name: 'passwd').value = Password
	# agent.page.body =~ /\("\.albatross"\)\[0\]\.value = "(.*)"/
	# form.field_with(name: '.albatross').value = $1
	form.click_button
end

#CAPTHCA
str = agent.page.body.match( %r!"https://captcha.yahoo.co.jp:443/[^"]+!).to_s.gsub(/"/,"")
puts str
open(str) do |file|
  open("captcha#{Time.now.to_i}.jpg", "w+b") do |out|
    out.write(file.read)
  end
end
capthca = ''
$stdout.print 'enter captcha:'
captcha = $stdin.readline
puts "i got captcha#{captcha}"
agent.page.forms.first.fields_with(:type=>"text").first.value=captcha
agent.page.forms.first.submit

#CAPTHCA後の再ログイン
f=agent.page.forms[0]
f.fields_with( :name=>"login")[0].value=Id
f.fields_with( :name=>"passwd")[0].value=Password
f.submit


puts agent.page.body.to_s.toutf8

agent.cookie_jar.save_as(cookie_jar_yaml_path)
File.expand_path cookie_jar_yaml_path

これを実行すると、ログイン情報のクッキーがyahoo.yamlというファイルに出力される.

次に、Yahoo!ボックスからファイルを一括ダウンロードするスクリプトを実行する

#Yahoo! Box Downloader
require 'mechanize'
require 'nokogiri'
require 'kconv'
require 'scanf'
require 'date'
require 'uri'
require 'json'
require 'erb'
require 'net/http'
require 'open-uri'
include ERB::Util

cookie_jar_yaml_path = 'yahoo.yaml' #ログイン情報のクッキーを保存したファイル
filenum_of_page = 100 #一度に読み込むファイル数 20,50,100のどれか

#Yahoo!Boxへアクセス
agent = Mechanize.new
agent.user_agent_alias = 'Windows IE 7'
agent.cookie_jar.load(cookie_jar_yaml_path)
page = agent.get('https://box.yahoo.co.jp/user/viewer')	


#Javascriptの文字列からsid,uniqid,crumb,appidを取り出す
tmp_rst = page.search('script')[0]

user_parmsstr = tmp_rst.to_s.split("\n")[2].split(',')
crumb_parameter = tmp_rst.to_s.split("\n")[3].split(',')
appid_parameter = tmp_rst.to_s.split("\n")[4].split(',')

sid = user_parmsstr[0].scanf("    User  = {\'sid\':\"%s\"")[0].to_s
topuniqid = user_parmsstr[1].scanf(" \'uniqid\':\"%s\"},")[0].to_s
crumb = crumb_parameter[1].scanf("'bcrumb':\"%s")[0].to_s
appid = appid_parameter[0].scanf("\t\t'appid':\'%s")[0].to_s
puts appid
#scanfうまくいかないのでうしろの"を消す 正規表現ちゃんとかくべき^^;
sid = sid[0,topuniqid.index("\"",2)+1]
topuniqid = topuniqid[0,topuniqid.index("\"",2)]
crumb = crumb[0,crumb.index("\"",2)]
appid = appid[0,appid.index("'",2)]
puts "sid = #{sid}"
puts "uniqid = #{topuniqid}"
puts "crumb = #{crumb}"
puts "appid = #{appid}"

#ここから巡回してファイルをダウンロード	
folderList = Array.new
folderList.push(topuniqid)
#folderListが空になるまで巡回する
while folderList.size != 0 do
	#folderListから一つ取り出す
	nowuniqid = folderList.pop
	#そのフォルダ内のファイルのリストが書かれたJSONを取得する
	urlstr = "https://box.yahoo.co.jp/api/v1/filelist/" + sid + "/" + nowuniqid + "?_=" + DateTime.now.strftime('%Q').to_s + "&"
	urlstr << "results=#{filenum_of_page}&start=1&output=json&sort=%2Bname&filetype=both&meta=1&thumbnail=1&tree=1&sharemembercount=1&ownerinfo=1&boxcrumb="
	urlstr << url_encode(crumb)
	agent.get(urlstr)
	jsonstr = JSON.parse(agent.page.body.to_s)
	# 複数ページが存在する場合はまず全ページたどってファイル情報を入手
	filenum = jsonstr['ObjectList']['TotalResultsAvailable'].to_s
	unless jsonstr['ObjectList']['Object'] == nil
		jsonstr['ObjectList']['Object'].each do |object|
			type = object['Type'].to_s
			name = object['Name'].to_s
			uniqid = object['UniqId'].to_s
			dlurl = object['Url'].to_s
			path = "." + object['Path'].to_s #パスの先頭にドットをつけないとうまく相対パスにならない
			#ファイルかフォルダかで処理を分岐
			if(type == 'file') then
				dlurl << "?appid=#{appid}&error_redirect=1&done=https%3A%2F%2Fbox.yahoo.co.jp%2Ferror%2Fdownload_error&boxcrumb="
				dlurl << url_encode(crumb)
				#dlurlからリダイレクトされたURLを取得 これがダウンロードリンク
				agent.get(dlurl)
				redirect_link = agent.page.uri.to_s
				#ファイルを保存
				#File.write(path, Net::HTTP.get(URI.parse(redirect_link)))
				open(redirect_link) do |file|
					open(path, "w+b") do |out|
						out.write(file.read)
					end
				end
				puts "Download #{path}"
			elsif(type == 'dir') then
				#folderListに追加してあとで巡回
				folderList.push(uniqid)
				Dir.mkdir(path)
			end
		end
	end
end

詳しく解説してもたぶん需要なさそうなので、コードはりつけておしまい。
sleepをはさんで、負荷かけないようにしてつかいましょう。

一応gistこちらも修正しました[2016/12/30]

https://gist.github.com/lp6m/5913c1ef770f75825b00081a6ed7f671
https://gist.github.com/lp6m/a4e927963e218884e2a843e40e7a22b5