RubyでWebクローラっぽいのを作ってみたいと思った 続き
リダイレクトに対応したダウンロードプログラム
画像をダウンロードをするプログラムを作りました。レスポンスデータから抽出しただけのURLだとリダイレクトされる可能性があるのでそういう対応をしました。というかruby リファレンスにあるままです。
====
リダイレクトとは
ウェブサイトにおけるリダイレクト(英:redirect)とは、ウェブサイトの閲覧において、指定したウェブページから自動的に他のウェブページに転送されること。URLリダイレクト(URL redirection)とも言われる。
通常はウェブページのURLが変わったときに、元のURLから新しいURLへ誘導するときに用いられる。フィッシング詐欺サイトへの誘導などで用いられている場合もある
(by wikipedia)
ということです。
リダイレクトの例
以下のニュー速のページに画像ファイルがあります。
【2ch】ニュー速クオリティ:小学生の水着DVDに偶然映り込んだ山中知恵ちゃんのビキニが凄いことになってる
"ページのソースを表示"をすると
"<img src="http://livedoor.blogimg.jp/news4vip2/imgs/9/6/96a36e43.jpg" "
となっているところをクリックすると、画像が表示されるけど、そのURLは
"http://livedoor.4.blogimg.jp/news4vip2/imgs/9/6/96a36e43.jpg"
となっています。
よく見てみると、
- "http://livedoor.blogimg.jp.news4vip2/imgs/9/6/96a36e43.jpg"
- "http://livedoor.4.blogimg.jp/news4vip2/imgs/9/6/96a36e43.jpg"
微妙に違う!
つまりはリダイレクトなんです。そうなんです。
Rubyのプログラム
responseを見て処理をわけるようです。"Net::HTTPRedirection"がきた場合"response['location']"を見てリダイレクトされるURLへもう一度アクセスする仕組みです。"when"を増やせばほかのレスポンスにも対応できるねぇ。今はしないけど。
=begin ファイルをダウンロードするプログラム。リダイレクト対応 =end require 'net/http' require 'uri' def fetch(uri_str, save_path, limit = 10) # You should choose better exception. raise ArgumentError, 'HTTP redirect too deep' if limit == 0 response = Net::HTTP.get_response(URI.parse(uri_str)) case response when Net::HTTPSuccess puts response, " download..." size = response["Content-Length"].to_f File.open(save_path, "wb") do |file| file.write response.body end when Net::HTTPRedirection puts response, " Redirect..." fetch(response['location'], save_path, limit - 1) else puts response.value end end if __FILE__ == $0 fetch('http://livedoor.blogimg.jp/news4vip2/imgs/9/6/96a36e43.jpg', './image/foo.jpg') end
"download.rb"という名前です。
Rubyの勉強を兼ねてWebクローラっぽいのを作ってみたいと思った
最近研究が忙しいのでせっかく作ったブログを更新してない。更新する内容もないからいんだけど。というか研究が忙しくないことなんてないんですけど。
まぁそんなこんなで"Webクローラ"を作りたいと思いました(突然)。Rubyの勉強を兼ねて挑戦しようと思うので、自分の考えだけで色々と作っていきたいと思います。目標としては"目的の画像ファイルを勝手に集めてくれるやつ"を作りたいなと思いました。
====
今回rubyにおいて正規表現のマッチした部分だけを見る変数があるのを知りました。
http_img = /http.*?jpg/ if image =~ http_img puts $& end
"$&"はマッチした部分が保存される変数のようです。
そして最初はとりあえず
- http通信をしてレスポンスを受けとる
- レスポンスデータを解析して、画像タグとリンクタグを取り出す
というところから作ってみました。以下のhttp通信のところはずっと前にどこかのブログからコピペしたものです(早速他力本願)。どこかわかり次第リンクを張ります。
"response.body"でレスポンスデータを見れるので、そこから正規表現を使って"<img>"と"<a>"の2つのタグを抽出しています。その後抽出したタグ1つ1つを配列にプッシュしていきます。
# -*- coding: utf-8 -*- require "net/http" require "uri" ImgArray = Array.new LinkArray = Array.new uri = URI.parse("http://news4vip.livedoor.biz/"); Net::HTTP.start(uri.host, uri.port){|http| #ヘッダー部 header = { "user-agent" => "Ruby/#{RUBY_VERSION} MyHttpClient" } #ボディ部 body = "id=1&name=name" #送信 response = http.post(uri.path, body, header) p response # p response.body # # imgタグとaタグのみをレスポンスデータから抽出する # reImg = /<img.*?>/ str = response.body str.gsub(reImg) do |matched| ImgArray << matched end reA = /<a.*?>/ str.gsub(reA) do |matched| LinkArray << matched end }
この配列に格納されたそれぞれのタグからリンクを取り出します。"<img>"タグからは画像へのリンク、"<a>"タグからは次のページへのリンクを取り出します。
http_img = /http.*?jpg/ ImgArray.each do |image| if image =~ http_img puts $& end end http_link = /http.*?\"/ LinkArray.each do |link| if link =~ http_link puts $& end end
配列から取り出した各文字列から正規表現をつかって"http......"の部分を取り出します。上のままだとlinkのほうに"が残ってしまうけどうまい方法が見つからないので最後にとればいいと思いました。
とりあえず今はここまでです。空いた時間でちょこちょこ作っていきたいと思います。
objective-CでAES暗号化を行う その2
この前作ったのを拡張してみた。"objective-Cで継承ってどうやるんだろう?"という疑問とともに少し練習した次第です。
====
前のやつ
objective-CでAES暗号化を行う - めがねをかけて生きていこう
ファイル操作関係も入れてしまった方が楽じゃないの!ってことです。
cc_crypto.hを継承したcc_file_crypto.h
#import <Foundation/Foundation.h> #import <CommonCrypto/CommonCrypto.h> #import "cc_crypto.h" @interface cc_file_crypto : cc_crypto { } - (int)CCCryptoInPath:(NSString *)inPath OutPath:(NSString*)outPath; @end
cc_file_crypto.m
#import "cc_file_crypto.h" @implementation cc_file_crypto - (int)CCCryptoInPath:(NSString *)inPath OutPath:(NSString*)outPath { NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:inPath]; if (!fileHandle) { NSLog(@"ファイルがありません."); return 1; } // ファイルの末尾まで読み込み、暗号化を行う NSData *data = [fileHandle readDataToEndOfFile]; NSData* CryptedData = [super CCCrypto:data]; /** 復号されたデータをファイルとして書き込む **/ NSFileManager *fileManager = [NSFileManager defaultManager]; // ファイルが存在しないか? if (![fileManager fileExistsAtPath:outPath]) { // yes // 空のファイルを作成する BOOL result = [fileManager createFileAtPath:outPath contents:[NSData data] attributes:nil]; if (!result) { NSLog(@"ファイルの作成に失敗"); return 1; } } // ファイルハンドルを作成する NSFileHandle *writefileHandle = [NSFileHandle fileHandleForWritingAtPath:outPath]; // ファイルハンドルの作成に失敗したか? if (!writefileHandle) { // no NSLog(@"ファイルハンドルの作成に失敗"); return 1; } // ファイルに書き込む [writefileHandle writeData:CryptedData]; return 0; } @end
こいつは暗号化するファイルのパスと暗号化後のファイルのパスを渡せば良きに計らってくれる感じです。
テスト用のmain
#import "cc_file_crypto.h" #import <Foundation/Foundation.h> #import <CommonCrypto/CommonCrypto.h> /* * ファイルの暗号化のテスト * * */ int main(void) { unsigned char key[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; unsigned char iv[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; id cc, cc2; NSString *filePath = @"/Users/tokihiro/image/yuuyake03.jpg"; NSString *encfilePath = @"/Users/tokihiro/image/E1"; NSString *decfilePath = @"/Users/tokihiro/image/hoge.jpg"; cc = [[cc_file_crypto alloc] initWithOperation:kCCEncrypt key:key iv:iv]; cc2 = [[cc_file_crypto alloc] initWithOperation:kCCDecrypt key:key iv:iv]; int result = [cc CCCryptoInPath:filePath OutPath:encfilePath]; if(result == 1){ NSLog(@"encrypt error"); exit(EXIT_FAILURE); } result = [cc2 CCCryptoInPath:encfilePath OutPath:decfilePath]; if(result == 1){ NSLog(@"decrypt error"); exit(EXIT_FAILURE); } }
以下実行結果
bash-3.2$ cc cc_file_crypto.m cc_crypto.m main3.m -framework Foundation cc cc_file_crypto.m cc_crypto.m main3.m -framework Foundation bash-3.2$ ./a.out ./a.out 2013-11-19 22:29:14.915 a.out[62471:507] CCCryptorCreate = 0 2013-11-19 22:29:14.917 a.out[62471:507] CCCryptorUpdate = 0, resultLength = 246592 2013-11-19 22:29:14.917 a.out[62471:507] CCCryptorFinal = 0, resultLength = 16 success 2013-11-19 22:29:14.919 a.out[62471:507] CCCryptorCreate = 0 2013-11-19 22:29:14.920 a.out[62471:507] CCCryptorUpdate = 0, resultLength = 246592 2013-11-19 22:29:14.920 a.out[62471:507] CCCryptorFinal = 0, resultLength = 8 success
うまくできたー。
objective-CでAES暗号化を行う
CommonCryptoを使う
以前XcodeでopenSSLを使うとめっちゃ警告でてなんなの?って記事を書いたりしたんですけど、
openssl is deprecated in Mac OS X 10.7 - tokihiro_k's blog
apple的にはCommonCrypto使いなさいよーって言ってるみたいなのでこの休日でちょっと実装してみました。実装したのはAESを使って暗号化を行うプログラムです。思ったより簡単でした。
ステートレスにできるメソッドもあるみたいですけど(ググると出てくるCommonCrypto使ってる実装はだいたいこっち)今回は面倒だけど動きがよくわかる方法で実装しました。以下参考にしたサイト(ただのヘッダファイルだけど)と使用する暗号化用メソッド。
"CCCryptorCreate"は最初に暗号化や復号の設定を行う
CCCryptorCreate(CCOperation op, CCAlgorithm alg, CCOptions options, const void *key, size_t keyLength, const void *iv, CCCryptorRef *cryptorRef);
- op:暗号化か復号化を指定....kCCEncrypt, kCCDecrypt
- alg:使用するアルゴリズム...上記サイトに使えるのが書いてある
- options:モードを指定...デフォルトがCBCらしい
- key:鍵データ
- key Length:鍵長
- iv:初期化ベクタデータ
- cryptorRef:初期化されたオブジェクトが返ってくる
"CCCryptorUpdate"から暗号化が開始される。入力データが基底バイトより多い場合何度も呼び出され平文が基底バイト以下になるまで繰り返される
CCCryptorUpdate(CCCryptorRef cryptorRef, const void *dataIn, size_t dataInLength, void *dataOut, size_t dataOutAvailable, size_t *dataOutMoved);
- cryptorRef:上のメソッドと同じもの
- dataIn:暗号化するデータ...NSData型なら"[data bytes]"とかする
- dataInLength:暗号化するデータの大きさ
- dataOut:暗号化されたデータが入る...自分でメモリを確保して割り当てる
- dataOutAvailable:4での返り値で利用できるメモリの大きさ
- dataOutMoved:成功した場合何バイト書き込まれたかが返る
"CCCryptorFinal"で最後の16byte(aes128の場合)が暗号化される
CCCryptorFinal(CCCryptorRef cryptorRef, void *dataOut, size_t dataOutAvailable, size_t *dataOutMoved);
- cryptorRef:上と同じ
- dataOut:暗号化されたデータが入る...CCCryptorUpdateが呼ばれた場合を考慮し、"すでに書き込まれたバイト数"分の大きさを加える必要がある
- dataOutAvailable:同じ
- dataOutMoved:同じ
この3つのメソッドを順番通りに呼べば暗号化されたデータが出来上がります。上記した"dataOut"の型を見るとわかりますけど、"void *"で指定されています。つまり汎用ポインタです。NSData型で返ってきた方が使う方としてはいいと思うのでそのように実装しました。
"cc_crypto.h"
#import <Foundation/Foundation.h> #import <CommonCrypto/CommonCrypto.h> @interface cc_crypto:NSObject { CCOperation op; NSUInteger CLen; unsigned char crypto_key[16]; unsigned char crypto_iv[16]; } - (id)initWithOperation:(CCOperation)op key:(unsigned char *)key iv:(unsigned char *)iv; - (NSUInteger)CLen; - (NSData *)CCCrypto:(NSData *)data ; @end
"cc_crypto.m"
#import "cc_crypto.h" @implementation cc_crypto - (id)initWithOperation:(CCOperation)operation key:(unsigned char *)key iv:(unsigned char *)iv{ self = [super init]; if(self != nil){ op = operation; memcpy(crypto_key, key, 16); memcpy(crypto_iv, iv, 16); } return self; } - (NSUInteger)CLen { return CLen; } - (NSData *)CCCrypto:(NSData *)data { CCCryptorStatus err; CCCryptorRef ref; void *response; size_t bufferSize; size_t updateResultLength = 0; size_t finalResultLength = 0; NSUInteger length = [data length]; bufferSize = length + kCCBlockSizeAES128; err = CCCryptorCreate(op, kCCAlgorithmAES128, kCCOptionPKCS7Padding, crypto_key, kCCKeySizeAES128, crypto_iv, &ref); NSLog(@"CCCryptorCreate = %d", err); response = malloc(bufferSize*sizeof(size_t)); err = CCCryptorUpdate(ref, [data bytes], [data length], response, bufferSize, &updateResultLength); NSLog(@"CCCryptorUpdate = %d, resultLength = %zu", err, updateResultLength); err = CCCryptorFinal(ref, response + updateResultLength, bufferSize, &finalResultLength); NSLog(@"CCCryptorFinal = %d, resultLength = %zu", err, finalResultLength); if( err == 0 ) { printf("success\n"); CLen = (updateResultLength + finalResultLength); NSData* data = [NSData dataWithBytes:(const void *)response length:CLen]; CCCryptorRelease(ref); return data; } else { CCCryptorRelease(ref); return nil; } } @end
鍵とivの型はC言語から流用したので"unsigned char"になってしまいました。。。。。まぁ汎用ポインタですからなんでもいいんでしょうけど、とりあえずここは各々使いやすいように変更しましょう。
こいつの使い方はまず"initWithOperation"を読んで暗号化か復号かと鍵、ivを決定してオブジェクトを作ります。その後CCCryptoに暗号化したいデータを渡せば処理されてNSData型で値が返ってきます。あとはそのデータを好きなようにすればいいと思います。
テスト用に文字列を暗号化して復号するmainファイルを作りました。
"main.m"
#import "cc_crypto.h" #import <Foundation/Foundation.h> #import <CommonCrypto/CommonCrypto.h> int main(void){ unsigned char key[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; unsigned char iv[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; NSString* test = @"tesaaaaaaaaaaaataaaaaaaaaaaaaaaabbbbagafgarfajaowefjawpoefjao"; NSData* test_data = [test dataUsingEncoding:NSUTF8StringEncoding]; id cc, cc2; void *res, *res2; cc = [[cc_crypto alloc] initWithOperation:kCCEncrypt key:key iv:iv]; NSData* data = [cc CCCrypto:test_data]; NSLog(@"len = %lu", [cc CLen]); cc2 = [[cc_crypto alloc] initWithOperation:kCCDecrypt key:key iv:iv]; NSData* data2 = [cc2 CCCrypto:data]; NSString *str= [[NSString alloc] initWithData:data2 encoding:NSUTF8StringEncoding]; NSLog(@"%@", str); }
以下実行結果
bash-3.2$ cc main.m cc_crypto.m -framework Foundation bash-3.2$ ./a.out 2013-11-17 18:14:33.958 a.out[28566:507] CCCryptorCreate = 0 2013-11-17 18:14:33.960 a.out[28566:507] CCCryptorUpdate = 0, resultLength = 48 2013-11-17 18:14:33.960 a.out[28566:507] CCCryptorFinal = 0, resultLength = 16 success 2013-11-17 18:14:33.961 a.out[28566:507] len = 64 2013-11-17 18:14:33.961 a.out[28566:507] CCCryptorCreate = 0 2013-11-17 18:14:33.961 a.out[28566:507] CCCryptorUpdate = 0, resultLength = 48 2013-11-17 18:14:33.962 a.out[28566:507] CCCryptorFinal = 0, resultLength = 13 success 2013-11-17 18:14:33.962 a.out[28566:507] tesaaaaaaaaaaaataaaaaaaaaaaaaaaabbbbagafgarfajaowefjawpoefjao
そして、当たり前ですけどこのメソッドを使えばファイルの暗号化もできます。次にjpgファイルを暗号化して、すぐ復号するテストを行います。
"main2.m"
#import "cc_crypto.h" #import <Foundation/Foundation.h> #import <CommonCrypto/CommonCrypto.h> /* * 参考 * http://www.objectivec-iphone.com/foundation/NSFileManager/writeData.html * */ int main(void) { unsigned char key[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; unsigned char iv[16] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; id cc, cc2; NSString *filePath = @"/Users/tokihiro/image/yuuyake03.jpg"; NSString *decfilePath = @"/Users/tokihiro/image/hoge.jpg"; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; if (!fileHandle) { NSLog(@"ファイルがありません."); exit(EXIT_FAILURE); } // ファイルの末尾まで読み込み、暗号化を行う NSData *data = [fileHandle readDataToEndOfFile]; cc = [[cc_crypto alloc] initWithOperation:kCCEncrypt key:key iv:iv]; NSData* EncData = [cc CCCrypto:data]; //暗号化されたデータを復号する cc2 = [[cc_crypto alloc] initWithOperation:kCCDecrypt key:key iv:iv]; NSData* DecData = [cc2 CCCrypto:EncData ]; /** 復号されたデータをファイルとして書き込む **/ NSFileManager *fileManager = [NSFileManager defaultManager]; // ファイルが存在しないか? if (![fileManager fileExistsAtPath:decfilePath]) { // yes // 空のファイルを作成する BOOL result = [fileManager createFileAtPath:decfilePath contents:[NSData data] attributes:nil]; if (!result) { NSLog(@"ファイルの作成に失敗"); exit(EXIT_FAILURE); } } // ファイルハンドルを作成する NSFileHandle *writefileHandle = [NSFileHandle fileHandleForWritingAtPath:decfilePath]; // ファイルハンドルの作成に失敗したか? if (!writefileHandle) { // no NSLog(@"ファイルハンドルの作成に失敗"); exit(EXIT_FAILURE); } // ファイルに書き込む [writefileHandle writeData:data]; }
一応GitHubにも同じソースコードがあがっています。こっちは適宜更新する可能性があります。
そういうわけでobjective-CでCommonCryptoを使った暗号化でした。
Mac OS X Mavericksのgcc
本日このようなツイートを見かけました。
『MavericksからCコンパイラが変わりました…gccという名前のバイナリが/usr/binにできますが、コイツはgccじゃありません』 Mavericks上での古いPHPのビルドが苦行だった - hnwの日記 http://t.co/8Q6yTk5OiQ
— 徳丸 浩 (@ockeghem) November 12, 2013
普通に使う分にはそんな問題なさそうだけど、なにかしらインストールするときにこれではまったら気づかなそうですよね...
GitHubに登録しました
少し前からgitを使い始め、ちゃんと勉強してないことが原因で最近使っていなかったのでこの度勉強しながら使っていこうと思いました。ちなみに前は和製GitHubといわれるcode break;にpushしてたんですけど、久しぶりにログインしたらめっちゃ変わっててびっくりしました。
GitHubに登録してわかったんですけど、GitHubはプライベートリポジトリが有料なんですね〜。プライベートリポジトリじゃないと公開されちゃうんで、秘密のプログラム(?)はpushできないじゃないですか!これはまだまだcode break;を使う価値が出てきました。
以下のサイトを参考にさせていただきました。
初心者Git日記その五~GitHubにSSH公開鍵登録~ | SetucoCMSプロジェクト
接続に関してはsshとhttpsの2つがありましたが、ここはsshを使ってみました。ちなみに昔のcode break;はsshが使えなかったんですけど、今日ログインしたら使えるようになってました。
というわけで、ちゃんと使ってみたらとてもとても便利だと思った今日この頃ですので、これからも使っていきたい思います。
https://github.com/tokihiro000