2010年01月31日
Go言語で2ちゃんねるのdatファイルを圧縮してダウンロードする
bbspink.comのdatを取得するために、勉強も兼ねてGo言語で2ちゃんねるのdatファイルをダウンロードするプログラムを作成しました。
2ちゃんねるの「バーボン」はサーバ別に判定していることを利用して、対象とするサーバの数だけ並列にdatをダウンロードする作りにしました。githubを借りたため、これからはなるべくgithubにソースコードを載せるようにしたいと思います。
ソースはこちら→http://github.com/tanaton/get2ch-go
ちなみに、実行するとbbspink.comの全datファイルをフォルダ分けをしながらダウンロードします。2回目以降の実行では、以前との差分のみを取得するようになっています。
get2ch.goがライブラリなので、適当にコンパイルして使ってみてください。動作は保証しませんが。
解凍部分のソース
2ちゃんねるは圧縮転送に対応していて、ヘッダーに「Accept-Encoding: gzip」と書くと、gzipで圧縮されたデータが送られてきます。差分取得には使えないのですが、転送速度が大幅に早くなり、しかも回線に優しいので導入するべきです。
以下のソースはgzipで送られてきたデータを解凍します。普通のファイルにも使えるはずですが、試したことはありません。
import "compress/gzip" // 解凍 func gzipInflater(reader io.Reader) (string, os.Error){ bufsize := 520 * 1024 buf := make([]byte, bufsize) size, n := 0, 0 zip, err := gzip.NewInflater(reader) defer zip.Close() if err != nil { return "", err } for { // 1024byte毎 n, err = zip.Read(buf[size:size+1024]) size += n if err != nil { if str := err.String(); str == "EOF" { break } else { return "", &Get2chError{"Not EOF error", err} } } if size > bufsize { return "", &Get2chError{"Data size error", err} } } return string(buf[0:size]), nil }
bufio.NewReader()等で生成されたio.Reader型を引数にとり、そこからデータを引っ張り出して解凍します。
コピペする際はエラーの部分を書き換えてご使用ください。
上記のプログラムとほぼ同じなんですが、微妙に動きの違うものも貼っておきます。
上記は最大サイズを決めてダウンロードしますが、その制限を外したものになります。ちまちまメモリを確保するので上記よりも遅いかもしれません。
import "compress/gzip" // 解凍 func gzipInflater(reader io.Reader) (string, os.Error){ data := "" buf := make([]byte, 1024) zip, err := gzip.NewInflater(reader) defer zip.Close() if err != nil { return "", err } for { // 1024byte毎 n, err := zip.Read(buf) if err != nil { if str := err.String(); str == "EOF" { break } else { return "", &Get2chError{"Not EOF error", err} } } // 文字列バッファに結合 data += string(buf[0:n]) } return data, nil }
2010年01月06日
unkarが大量のアクセスにより落ちました
どうやら、どこぞのボットの大漁アクセス攻撃を浴びてサーバが落ちました。
今から復旧作業に入りますが、肝心のログが2009年12月後半くらいから消滅していると思います。バックアップをサボってごめんなさい。アホなことに環境のバックアップを取ってないので、アパッチ君のコンパイルからやり直しです。めんどくさすぎる…。
明後日くらいまでには、なんとか復旧するつもり。
追記(原因)
データベース(MySQL)のデータが壊れた(?)のか、インサート命令を発行するとデータベースから応答が無くなる状態になっていました(なぜかタイムアウトもしない)。
それによりデータベースの接続がスレッドの新規取得と共に増えて、さらにPHP君がデータベースからの応答を待ち続けるため、Apacheの接続数も増えて何も処理をできなくなっていたようです。PHP側のMySQLタイムアウトやPHP自体のタイムアウトも設定しているのですが動作しませんでした。こわい。
2009年12月23日
GoでWiresharkのログ(csv)の必要な部分を単純に抜き出してみた
Wiresharkのログを確認する際に、Excel2003では65536行を超えるcsvが省略されてしまうので少しばかり不便です。
そこで、ログの中から必要な部分を抜き出すGoを書いてみました。
PHPと同じような感覚で作れましたが、実行時間は体感的にはPHPと同じくらい(遅い?)でした。ファイルのIOが影響しているのか私の書き方が悪いのか分かりませんが、ちょっとがっかりです。
※最初にPHPで作って、後からGoで作りました。
Wiresharkのログのcsvは大まかに以下のようになっています。ログの内容は適当です。
"No.","Time","Source","Destination","Protocol","Info" "1","0","192.168.0.3","192.168.0.11","TCP","nfs > 7014 [ACK] Seq=1000 Ack=10 Win=5678 Len=1460" "2","0.1","192.168.0.4","192.168.0.11","TCP","nfs > 7015 [ACK] Seq=2000 Ack=20 Win=6789 Len=1460" "3","0.2","192.168.0.99","192.168.0.11","TCP","nfs > 7016 [ACK] Seq=3000 Ack=30 Win=7890 Len=1460"
作りとしては、"Source","Destination","Protocol","Info"を比較したりして抜き出します。
作るにあたり、Go標準の正規表現や動的配列のライブラリを使用しました。すごく簡単に使えて感動です。
ソースコード
ライブラリの使い方の参考にどうぞ。
ちなみに、Windowsではコンパイルできません。
Windowsでコンパイルしたい方はfp.Stat()を消してファイルサイズを手入力してください。
package main import ( "os"; "bytes"; "strings"; "regexp"; "container/vector"; ) const ( // 自身のIPアドレス IP = "192.168.0.11"; // ログのファイル名 PATH = "aaa.csv"; ) func get_ip_list() (*vector.StringVector){ // 動的配列を用意 ip_list := new(vector.StringVector); // 通信先IPアドレス ip_list.Push("192.168.0.3"); ip_list.Push("192.168.0.4"); ip_list.Push("192.168.0.5"); ip_list.Push("192.168.0.6"); ip_list.Push("192.168.0.7"); ip_list.Push("192.168.0.8"); ip_list.Push("192.168.0.9"); ip_list.Push("192.168.0.10"); return ip_list; } func main(){ fp, _ := os.Open(PATH, os.O_RDONLY, 0); // 読み込みで開く dir, _ := fp.Stat(); // ファイル情報取得 ip_list := get_ip_list(); // IPのリスト fp_list := make(map[string] *os.File, 10); // ハッシュ buf := make([]byte, dir.Size); // ファイルサイズ分のバッファを用意 fp.Read(buf); // ファイルを読み込む for ip := range ip_list.Iter() { // ファイルポインタを格納 // 書き込みモードでファイルを作成 fp, _ := os.Open(ip + ".txt", os.O_WRONLY | os.O_CREATE, 500); fp_list[ip] = fp; } str := bytes.NewBuffer(buf).String(); // ファイルのバイト配列を文字列に変換 list := strings.Split(str, "\n", 0); // 文字列を改行で分割 length := len(list); // 配列の要素数 // 正規表現を用意 regs, _ := regexp.Compile(" Len=(1460|0)"); for i := 0; i < length; i++ { line := strings.Split(list[i], "\",\"", 0); // 分割 if len(line) < 5 { continue; } if line[4] != "TCP" { continue; } // TCP通信であること if line[2] == IP || line[3] == IP { // 自身のIPと通信していること if regs.MatchString(line[5]) { // 一致した for ip := range ip_list.Iter() { if line[2] == ip || line[3] == ip { // 送信、または受信が指定のIPアドレスだった場合 // ファイルに書き出す fp_list[ip].WriteString(strings.Join(line, "\",\"") + "\n"); } } } } } // ファイルを閉じる for ip := range ip_list.Iter() { fp_list[ip].Close(); } }
2009年12月21日
unkar.jpをwww.unkar.orgに移転しました
なるべく人様に迷惑を掛けないように(?)制限の大きいccTLDから、なんとなくゆるく感じるgTLDに逃げました。
非営利団体用の.orgってところが、なんか微妙で、また問題を起こしそうな気がします。とりあえずは大丈夫なのかな。
移転後のドメインがGoogle先生に上手くインデックスされるか分かりませんが、www.unkar.orgでやっていこうと思います。
何かあるたびに逃げ続けて、ついに果てまで来てしまったような気がします。実はサーバをお借りしているAmazon.comからも警告が着ていたりして、そろそろ逃げ場がありません。
unkarを続けるためにはウェブサイトという枠組みから外れた新しい手段を考えないといけないかも。
最後に
問題を起こしそうなサイトを運営する場合、ccTLDはやめましょう。
2009年12月20日
golangでリバーシを作った
golangで動くリバーシ(オセロ)を作成しました。
GUIはGoに標準(?)で付いてるX11のライブラリを使っています。
置ける石は白固定です。
AIが対戦してくれるので一人で遊べます。AIが強すぎて私は勝てたことがありません。作成者なのに情けない・・・。

