17197


kienuというファイルアップローダーで，パスワードを入力しダウンロードする作業を自動化したいと思っています．  
web上ではフォームにパスワードを入力し，送信すると自動的にダウンロードが開始されます．  
例えば以下のリンクでは「full123」と入力し，DOWNLOADボタンをクリックします．  
<https://kie.nu/22W5>

pythonのmechanizeでフォームに入力しsubmit()すれば，その時点でダウンロードが開始されるだろうと思ったのですが，できませんでした．

[code]

    import mechanize
    
    url = "https://kie.nu/22W5"
    
    def kienu(password):
        br = mechanize.Browser()
        br.open(url)
        br.select_form(nr=0)
        br["passwd"] = password
        br.submit()
    
    kienu("full123")
    
[/code]

なにか良い方法はありませんか？  
mechanizeでなくても，目的が達成できればどのような方法でも構いません．


CC BY-SA 3.0
2015-10-01T12:59:12.960
17145
2015-10-03T11:23:41.137
9744
question
python mechanize
「パスワードを入力しダウンロード」を自動化


よく引っかかるのは次の2パターンではないでしょうか。

## javascriptが有効でないと利用できない

ブラウザのjavascriptを無効にして確認することが出来ます。

mechanizeはjavascriptが解釈できないはずなので、javascriptが解釈できるツールを使うか、自力でjavascriptの動作をエミュレーションする必要があります。

埋め込みのjavascriptソースで確認できる簡単な処理であれば自力で処理した方が早いかもしれません。ただし、相手側でそこを変更されるとそれに追随しなければなりません。

## 送信する内容が不足または相違している

コメントでも指摘がありますが、ブラウザで表示される入力欄だけでなく、hidden要素やCSSで非表示にされている要素の内容も送信しなければならない場合があります。

Referer:、User-Agent:、Cookieなどがチェックされていることもあります。

ブラウザで正しく操作した場合に送信されている内容を確認し、それに極力合わせるのがよいでしょう。


CC BY-SA 3.0
2015-10-02T09:20:36.110
17186
2015-10-02T09:20:36.110
5793
17145
answer


おそらく他の方も書かれている通り、クッキー・隠しフォーム・リファラあたりが必須なのでしょう。  
**追記:
TugrebIcさんの回答で気付きましたが、mechanizeはここらへんを自動でやってくれるはずでしたね。動かない理由は、TugrebIcさんの回答の通りだと思います。**

下記は Python3 の例ですが、いずれスクレイピングも JavaScript の解釈なしでは厳しくなるかと思っています。PhantomJS
などを使う方がよいかもしれません。

[code]

    #!/usr/bin/python3
    import urllib.request
    import http.cookiejar
    import re
    import sys
    
    url = "https://kie.nu/22W5"
    
    # クッキーを扱う
    cookiejar = http.cookiejar.CookieJar()
    opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookiejar))
    
    # 1回目の読み込み(GET)
    req = urllib.request.Request(url)
    res = opener.open(req)
    html = res.readall().decode('utf-8')
    
    # HTML から POST に必要な値を探す
    hidden_name = 'csrfmiddlewaretoken'
    hidden_input_regex = re.compile(
            "<input[^>]*name='" + hidden_name + "'[^>]*value='([^']*)'[^>]*>")
    hidden_value = hidden_input_regex.search(html).group(1)
    
    
    # 2回目の読み込み(POST)
    req.add_header('Referer', url) # たぶん必要
    data = {
            'passwd': 'full123',
            hidden_name: hidden_value,
            }
    post_data = urllib.parse.urlencode(data).encode(encoding='utf-8')
    res = opener.open(req, post_data)
    
    # 標準出力にバイナリのまま出力
    sys.stdout.buffer.write(res.readall())
    
[/code]


CC BY-SA 3.0
2015-10-02T11:37:35.570
17188
2015-10-03T01:50:16.833
2015-10-03T01:50:16.833
3054
3054
17145
answer


> 目的が達成できればどのような方法でも構いません

ということですので、コマンドライン上で処理を行う方法を一つ。具体的には curl コマンドを使います。

まず、HTTP レスポンスヘッダだけをダンプしてみます。

[code]

    $ curl -I https://kie.nu/22W5
            : 
    Set-Cookie: csrftoken=fba73bc15b49182e7e9be2afe9d6f002; expires=...
    
[/code]

実は cookie 内の `csrftoken` の値は HTML ファイル内の `csrfmiddlewaretoken`(hidden data)
の値と同じです(同じ値で埋め込んでいます)。なので、以下の様にするとダウンロードができます。

[code]

    $ token="$(curl -s -I https://kie.nu/22W5 | grep -Po '(?<=Set-Cookie: csrftoken=).+?(?=;)')"
    $ curl -e https://kie.nu/ -b "csrftoken=$token" -d "passwd=full123&csrfmiddlewaretoken=$token" https://kie.nu/22W5 -o output.dat
    
[/code]

**追記**

実行環境は以下です。

[code]

    $ uname -rvm
    3.19.0-30-generic #33-Ubuntu SMP i686
    $ curl --version
    curl 7.38.0 (i686-pc-linux-gnu)
    $ grep --version
    grep (GNU grep) 2.20
    
[/code]

ダウンロードしたファイル

[code]

    $ ls -l output.dat 
    -rw-r--r-- 1 nemo nemo 6073 Oct 3 20:20 output.dat
    
    $ od -tx1 output.dat | head
    0000000 1b 00 54 00 75 00 78 00 47 00 75 00 69 00 74 00
    0000020 61 00 72 00 20 00 46 00 69 00 6c 00 65 00 20 00
                            :
    
[/code]


CC BY-SA 3.0
2015-10-02T13:43:45.240
17192
2015-10-03T11:23:41.137
2015-10-03T11:23:41.137
17145
answer


`.submit()`はファイル内容やヘッダを`urllib2.urlopen()`のようにオブジェクトで返します。  
中身さえ得られれば良いのならば、関数の最後を

[code]

        return br.submit().read()
    
[/code]

などとすれば良いかと思います。  
ファイル名が必要であればヘッダの`Content-
Disposition`をパースする必要がありますが、それに`cgi.parse_header`が使えるようです。

[code]

        r = br.submit()
        return (cgi.parse_header(r.info().getheader("content-disposition"))[1]["filename"], r.read())
    
[/code]

ファイルサイズが小さければこんな所でしょうか。  
<https://stackoverflow.com/a/27840405>  
しかし、大きい場合は一気に読まず、ファイルに書き出した方が良いかもしれません。  
＃`read()`はファイルなどと同じく読み込みサイズを指定できます  
なお蛇足ながら、得られたファイル名にはそのまま使わない方がよい文字種などが含まれる可能性がありますので、場合によっては何らかの変換など行った方が良いかもしれません。


CC BY-SA 3.0
2015-10-02T16:20:15.063
17197
2015-10-02T16:20:15.063
2017-05-23T12:38:55.250
-1
12301
17145
answer