Edited at

Ruby+Mechanizeによるファイル送信方法

More than 3 years have passed since last update.


はじめに

これは【その1】ドリコム Advent Calendar 2015 の4日目です。

3日目は ogwmtnr さんのバックグラウンド時の位置情報常時取得にジオフェンスを駆使する手法を思いついたのでやってみた です。


自己紹介


  • はじめまして、JunJunHiroi です。社内で呼ばれやすいアダ名を募集しています。


    • 「じゅんじゅん」だったり、「婚活の人」だったりで呼ばれましたが、いずれも浸透が足りません。

    • ドリコムは婚活のアドバイスをしてくれる方が多い、イケイケな会社です。



  • 今年の4月に新卒入社したクライアントエンジニアです。


社内ツール作成サークルについて

弊社には主に社内業務の改善を目指してハッカソンをする「社内ツール作成サークル」というものがあり、わたしはそれに参加しています。

先輩の方たちはWebサービスを社内サーバーで実現したり、現場の問題点を吸い上げて改善したりしており、技術力・発信力が高くてすごいです。

わたしはこのサークル活動で、趣味で行なっていたスクレイピングを活用することはできないかと考えて学習をしていました。


Ruby + Mechanize のファイル送信まとめ

今回は自分が使っている Ruby + Mechanize のスクレイピングについて、体系的な資料が少なかったファイル送信方法を書いていきたいと思います。

Mechanize: https://github.com/sparklemotion/mechanize

以下の、3パターンで紹介します。


  1. 送信フォームを使ったファイル送信

  2. 送信フォームを使ったファイル送信(input type='file'がない場合)

  3. 送信フォームがないURLへのファイル送信


1. 送信フォームを使ったファイル送信

まずは取得したWebページの form タグの中に、input type="file" があるウェブサイトでのファイル送信方法です。

一般的なWebサイトで、一般的に行われているファイル送信です。

agent = Mechanize.new

page = agent.get('https://website')
form = page.forms[0] # 対象のformを選んで、
form.file_upload.file_name = 'tmp.jpg' # file_nameを参照するファイル名に変更し、
form.submit # 送信する!

対象のformを探して、file_uploadの更新をすればOKです。


2. 送信フォームを使ったファイル送信(input type='file'がない場合)

次に取得したWebページの form タグの中に、input type="file"ないウェブサイトでのファイル送信方法です。

たとえば、画像ファイルを指定するとき、URLを入力するかローカルファイルを送信するかの2パターンの指定方法があるサイトを見たことありませんか?

この場合、 javascript で動的に input type="file" を作っているため、Mechanizeからはファイルフォームが無いように見えてしまうときがあります。

そんなときはこれ!

agent = Mechanize.new

page = agent.get('https://website')
form = page.forms[0] # 対象のformを選んで、
file = Mechanize::Form::FileUpload.new(form, 'tmp.jpg') # ファイルアップロードを作成し、参照するファイル名を指定して、
form.file_uploads << file # form の file_uploads に追加して、
form.submit # 送信する!

Mechanize::Form::FileUpload.new を使って、無理やり input type="file" のようなものを作っています。


なんかいびつじゃない?

Mechanize::Form::FileUpload.new の引数に form を入れて、file を作っているのに、

その後ろで formfile を追加しています。

プログラムとして、いびつに見えます。

ここらへんは資料を読み込んで、この実装が正しいのか確かめる必要がありますが、検証する時間がなかったためこのままにしておきます。

Mechanize::Form::FileUpload.new(node, file_name)


3. 送信フォームがないURLへのファイル送信

あれれ〜このサイト、それっぽい form がないのにドラッグアンドドロップでファイル送信ができるよ〜〜?

javascript を活用しているサイトの場合、form を使わずに動的にファイル送信リクエストを送っている場合があります。

form がないんじゃ、submit できない! どうしよう!?

そんなときはまず、デベロッパーツールのネットワークタブを開きながら、ファイルの送信を試してみます。

そして、ファイルを送信しているらしい通信を見つけたら、copy as cURL!

curl 'http://website' 

-H 'Referer: ~~~'
--data-binary
〜〜〜
Content-Disposition: form-data; name="file"
〜〜〜

ヘッダーやらなんやらと一緒にファイルを送っているのがわかります。

わかってしまえば、それを真似してPOST! 単純でいいですね。

agent = Mechanize.new

content_data = {
'file': File.new('tmp.jpg') # file_nameでも、File.readでもなく、File.new なことに注意。
# サイトによって、'Content-Type', 'Token' なんかも追加する。
}
result = agent.post('http://website', content_data)

サイトにもよりますが、これでうまくいくことがあります。

あるサイトでは、通信の中にトークンを入れている場合もあったので、その場合は丁寧にトークンを探してみてください。


その他つまりどころ など



  • file_uploadfile_uploads[0] の省略のように振る舞っていた。



    • file_upload をいじってうまくいかないときは file_uploads[0] に変えて挑戦するのがいいかも。



  • Referer が入っていないと、通信に失敗するサイトがやや多い印象。

  • Mechanize デフォルトの User-Agent でも、通信に失敗することがほぼない印象。


    • スクレイピングを趣味にする前は、 User-Agent が不正な場合に通信失敗させるサービスが多いと思っていた。逆に Referer は通信を解析するためだけに使っていると思っていた。

    • スクレイピングの通信に失敗したら、細かなパラメータも実通信に似せることで解決できたことがよくある。



  • ファイルデータを無理やり送信するときは enctypemime_type を設定する。



    • file_upload には、file_data, file_name, mime_type を指定できる。通常は file_name を指定するだけでいいが、画像バイナリデータをローカルファイルに保存せずに送りたい場合などは、3つを適切に設定してやる必要がある。


    • file_upload がもともとない送信フォームの場合、enctype を適切に設定してやる必要がある。form.enctype = 'multipart/form-data'など。




結果

Ruby + Mechanizeを使って、ファイル送信ができるようになりました!

これで実現できるスクレイピングツールの幅が広がった気がします。


おわりに

社内ツール作成サークルとしてこちらを活用できるようなツールを作り、業務の改善を目指したいなと思っています。


さいごに注意

ファイル送信は重いリクエストなので、くれぐれもやりすぎ (時間を空けずに連続してアップロードするなど) と外部サービスの場合利用規約には注意してください。


明日は

kakaricho さんです。

追記:kakaricho さん 2年目エンジニアがエンジニアリーダーをやってみた話

追記:【その2】ドリコム Advent Calendar 2015 もあります。