ryushare downloader 2

なんか丸一日かかってしまった。
以下、説明

〔概要〕
DLファイルリストをコントローラー.pyから開いて、ダウンロード.pyに投げる仕組み
各ファイルはchunkごと(ソースでは300MB)に区切って最後にマージしている。
リスト管理は完全に手動。
ファイル指定に使うhashみたいなのはURLにも含まれる10桁〜12桁くらいのやつ。
pythonはmechanizeが必要。

〔詳細〕
threadcontrol.py
 プロセス管理、ファイルリスト管理、メッセージ出力、インターフェース等
 基本的にこれを起動する。
 用法:python ./threadcontrol.py ファイルリストファイル 保存先

rs_downloader.py
 サイトからファイル情報取得、ファイルダウンロードを担当。
 ダウンロードはchunkごとに区切る。
 アカウント情報を追加してください。
 用法:(直接は起動しない想定)
  python .\rs_downloader.py check_size 1a1a1a1a1a1a
   ファイル名、ファイルサイズを取得する。
  python .\rs_downloader.py get 1a1a1a1a1a1a d:\
   当該ファイルを保存する。out of memoryが発生することがある。
  python .\rs_downloader.py get_range 1a1a1a1a1a1a d:\ 100 0
   当該ファイルを分割して保存する。上記では100MB区切りの1番目を取得する。


DLリストファイル記法
以下のようなテキストファイルを用意しておく
プログラムからはハッシュ部分さえあれば他は見てない。

http://ryushare.com/1a1a1a1a1a1a/xxxxxx.AVI メモメモ
http://ryushare.com/2b2b2b2b2b2b/yyyyyy.part01.rar メモメモ
http://ryushare.com/3c3c3c3c3c3c/yyyyyy.part02.rar メモメモ

threadcontrol.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import subprocess
import threading
import shutil
import locale
import os, re, sys, datetime

locale.setlocale(locale.LC_ALL, "")

def get_size(file_id , target_dir):
  cmd = 'python ./rs_downloader.py check_size ' + file_id

  print "-----------------------------------------"
  d = datetime.datetime.now()
  print d.strftime('%Y-%m-%d %H:%M:%S') , "--->" , cmd
 
  p = subprocess.Popen(cmd, shell=True, cwd='./', stdin=subprocess.PIPE,
                       stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  (stdouterr, stdin) = (p.stdout, p.stdin)
  while True:
    line = stdouterr.readline()
    if not line:
      break
    if re.match('filename:' , line):
      fname = line.strip().split(':')[1]
    if re.match('filesize:' , line):
      size = line.strip().split(':')[1]
    if re.match('status:file not found' , line):
      return('not found' , 0 )
  ret = p.wait()
  
  d = datetime.datetime.now()
  strfilesize = locale.format('%d', int(size) , grouping=True)
  print d.strftime("%Y-%m-%d %H:%M:%S") , '<---' , fname , strfilesize + '(Byte)'
  print d.strftime("%Y-%m-%d %H:%M:%S") , '<---' , "Return code: %d" % ret
  return(fname, size)

class obj_rs (threading.Thread): # inherit threading.Thread
  def __init__(self, file_id , target_dir , size , pos , chunk_num):
    threading.Thread.__init__(self)
    self.setDaemon(True)
    self.file_id = file_id
    self.target_dir = target_dir
    self.size = size
    self.pos = pos
    if chunk_num == 1:
      self.cmd = 'python ./rs_downloader.py get ' + self.file_id + ' ' + self.target_dir
    else:
      self.cmd = 'python ./rs_downloader.py get_range ' + self.file_id + ' ' + self.target_dir + ' ' + str(self.size) + ' ' + str(self.pos)
                  
    self.subproc_args = { "stdin"     : subprocess.PIPE,
                          "stdout"    : subprocess.PIPE,
                          "stderr"    : subprocess.STDOUT,
                          "cwd"       : "./",}

  def run(self):
    p = subprocess.Popen(self.cmd, shell=True, cwd='./', stdin=subprocess.PIPE,
        stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    (stdouterr, stdin) = (p.stdout, p.stdin)

    d = datetime.datetime.now()
    print d.strftime('%Y-%m-%d %H:%M:%S') , "--->" , self.cmd
    
    while True:
      line = stdouterr.readline()
      if not line:
        break
      print line.rstrip()
    ret = p.wait()
    d = datetime.datetime.now()
    print d.strftime('%Y-%m-%d %H:%M:%S') , "<--- Return code: %d" % ret

if __name__ == '__main__':
  chunk_size = 300 * 1024 * 1024 # splitted download size
  
  # get parameters
  argv = sys.argv
  if (len(argv) != 3):
    print "Usage: python %s listfile target_dir" % argv[0]
    sys.exit()
  feed = argv[1]
  target_dir = argv[2]

  #check feed list
  f= open(feed , 'r')
  r = f.read()
  f.close()
  file_ids=[]
  for i in r.split('\n'):
    m = re.search('[0-9a-z]{10,12}' , i)
    if m == None:
      print "Unmatched Line:" + i
    else:
      file_ids.append(m.group(0))
  print "we got " + str(len(file_ids)) + " file_id from list."

  # threading control
  for file_id in file_ids:
    (fname, s) = get_size(file_id, target_dir)
    
    # case file not found
    if fname == 'not found':  
      d = datetime.datetime.now()
      print d.strftime('%Y-%m-%d %H:%M:%S') , "<--- file not found."
      continue
    size = int(s)
    chunk_num = size//chunk_size + 1
    thread = [] # more threads can exectute
    for i in range(chunk_num):
      thread.append(obj_rs(file_id , target_dir , chunk_size / 1024 / 1024 , i, chunk_num))
      thread[i].run()

    # merge chunked-file and delete
    if chunk_num > 1:
      dest = open(target_dir + fname, 'wb')
      for i in range(chunk_num):
        addnum = '000' + str(i)
        shutil.copyfileobj( open(target_dir + fname + '.' + addnum[-3:] , 'rb'), dest)
    
      for i in range(chunk_num):
        addnum = '000' + str(i)
        os.remove(target_dir + fname + '.' + addnum[-3:])
        print "merged and deleted " + target_dir + fname + '.' + addnum[-3:]

rs_downloader.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import mechanize
import cookielib
import os, sys
import datetime
import locale
import logging

def make_browser():
  """
  A function to make browser object.
  """
  browser = mechanize.Browser()

  # Making Cookie Jar and bind it to browser
  cj = cookielib.LWPCookieJar()
  browser.set_cookiejar(cj)

  # Setting browser options
  browser.set_handle_equiv(True)
  browser.set_handle_gzip(False)
  browser.set_handle_redirect(True)
  browser.set_handle_referer(True)
  browser.set_handle_robots(False)
  browser.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(),
                             max_time=1)

  # Want debugging messages?
  #browser.set_debug_http(True)
  #browser.set_debug_redirects(True)
  #browser.set_debug_responses(True)

  return browser

if __name__ == '__main__':
  browser = make_browser()
  
  locale.setlocale(locale.LC_ALL, "")

  # get parameter
  argv = sys.argv
  mode = argv[1]
  if mode == 'check_size':
    file_id = argv[2]
    if len(argv) != 3:
      print "Usage: python %s check_size filehash" % argv[0]
      sys.exit()
  elif mode == 'get':
    file_id = argv[2]
    target_dir = argv[3]
    if len(argv) != 4:
      print "Usage: python %s get filehash target_dir" % argv[0]
      sys.exit()
  elif mode == 'get_range':
    file_id = argv[2]
    target_dir = argv[3]
    bytesize = int(argv[4])
    bytepos = int(argv[5])
    if len(argv) != 6:
      print "Usage: python %s get_range filehash target_dir chunk_size(MB) num_of_chunk" % argv[0]
      sys.exit()
  else:
    print "Invalid \"mode\" parameter."
    sys.exit()

  # set http headers
  if mode == 'get_range':
    start_pos = bytesize * 1024 * 1024 * bytepos
    end_pos = (bytesize * 1024 * 1024 * (bytepos + 1)) - 1
    browser.addheaders = [('User-agent',('Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3)'
                           ' Gecko/20041001 Firefox/0.10.1')),
                           ('Range','bytes=' + str(start_pos) + '-' + str(end_pos))]
  else:
    browser.addheaders = [('User-agent',('Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3)'
                           ' Gecko/20041001 Firefox/0.10.1'))]

  # Login
  browser.open('http://ryushare.com/login.python')
  browser.select_form(name="FL")
  browser.form['login'] = '///USER///'
  browser.form['password'] = '///password///'
  browser.submit()
  
  # open "create download link" url and get download link
  browser.open('http://ryushare.com/' + file_id + '/')
  
  try:
    browser.select_form(nr=0)
    browser.submit()
  except mechanize._mechanize.FormNotFoundError, e:  # case file not found
    print "status:file not found"
    sys.exit()
  
  zip_url = browser.response().read().split('">Click here to download')[0].split('a href="')[-1]
  fname = zip_url.split('/')[-1]

  if mode == 'check_size':
    filesize = browser.response().read().split('<small>(')[1].split(' bytes)</small>')[0]
    print "filename:" + fname
    print "filesize:" + filesize
    sys.exit()

  # execute download
  if mode == 'get':
    localfile = open(target_dir + fname , 'wb')
  elif mode== 'get_range':
    addnum = '000' + str(bytepos)
    localfile = open(target_dir + fname + '.' + addnum[-3:] , 'wb')

  r = browser.follow_link(text_regex='Click here to download')
  hd = r.info()
  strfilesize = locale.format('%d', int(hd.getheader('content-length')) , grouping=True)
  d = datetime.datetime.now()
  print d.strftime('%Y-%m-%d %H:%M:%S'), "<--- HTTP" , r.code , r.msg , strfilesize , "(Byte)"

  while 1:
    chunk = r.read(4096)
    if not chunk:
      break
    localfile.write(chunk)
  localfile.close()
  browser.close()

threading サンプル

すっかり忘れてる。復習中

#!/usr/bin/env python

import threading
import time

class test(threading.Thread):
  def __init__(self,i):
    threading.Thread.__init__(self)
    self.setDaemon(True)
    self.i = i

  def run(self):
    print "Start." + str(self.i)
    time.sleep(3)

if __name__ == "__main__":
  a = (test(1),test(2))

  a[0].start()
  time.sleep(1)
  a[1].start()

  while a[0].isAlive()+a[1].isAlive():
    time.sleep(0.5)
    print a[0].isAlive()
    print a[1].isAlive()
    print "=" * 10

ryushare downloader

使い方:実行ファイル名 ハッシュ文字列 保存先

premium account が必要です。

mechanizeめっちゃ便利ですねこれ。

#!/usr/bin/env python

import mechanize
import cookielib
import sys
import datetime
import locale

def make_browser():
  browser = mechanize.Browser()

  # Making Cookie Jar and bind it to browser
  cj = cookielib.LWPCookieJar()
  browser.set_cookiejar(cj)

  # Setting browser options
  browser.set_handle_equiv(True)
  browser.set_handle_gzip(False)
  browser.set_handle_redirect(True)
  browser.set_handle_referer(True)
  browser.set_handle_robots(False)
  browser.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(),
                             max_time=1)

  # Want debugging messages?
  #browser.set_debug_http(True)
  #browser.set_debug_redirects(True)
  #browser.set_debug_responses(True)
  
  browser.addheaders = [('User-agent',('Mozilla/5.0 (Windows; U; Windows NT 5.1; rv:1.7.3)'
                         ' Gecko/20041001 Firefox/0.10.1'))]
  return browser

if __name__ == '__main__':
  browser = make_browser()
  
  locale.setlocale(locale.LC_ALL, "")
  d = datetime.datetime.today()
  
  # get file_id
  argv = sys.argv
  if (len(argv) != 3):
    print 'Usage: # python %s file_id target_directory' % argv[0]
    quit()
  file_id = argv[1]
  target_dir = argv[2]
  
  # Login
  browser.open('http://ryushare.com/login.python')
  browser.select_form(name="FL")
  browser.form['login'] = '///username///'
  browser.form['password'] = '///password///'
  browser.submit()
  
  # open "create download link" url and get download link
  browser.open('http://ryushare.com/' + file_id + '/')
  browser.select_form(nr=0)
  rp = browser.submit()
  
  zip_url = rp.read().split('">Click here to download')[0].split('a href="')[-1]
  fname = zip_url.split('/')[-1]
  
  # execute download 
  r = browser.follow_link(text_regex='Click here to download')
  hd = r.info()
  filesize = locale.format('%d', int(hd.getheader("content-length")), grouping=True)
  print d.strftime("%Y-%m-%d %H:%M:%S") , r.code , r.msg , fname , filesize
    
  localfile = open(target_dir + fname, 'wb')
  while 1:
    chunk = r.read(4096)
    if not chunk:
      break
    localfile.write(chunk)
  loalfile.close()

追記:大容量ファイルだとout of memory するのでhttp header で range指定して分割DLする必要があると考える