AWS Lambdaを利用しスクレイピングしたい
- なんとか EC2 にインスタンス立てたりすることなく楽に安く済ませたい
- 適度な速度でHTMLをダウンロードしてきたい
HTML 保存した後はまあ適当にやれば良いと思っていて、ゆっくりダウンロードしてくるところが面倒。
今動いてるやつは 200-300個ぐらいしか URL がないのもあって Timeout を5分に設定した lambda:crawl で setTimeout しながら lambda:download_html を invoke してるんだけど増えてきて5分で終わらなくなったら困るなというのがあって考えたやつ
1つめの方法
- 適当な URL が DynamoDB とかに入ってるとして lambda:crawl で Table を Scan して SQS へ
{s3_key: "", url: ""}
のようなメッセージを送っていく - lambda:consume_download_html_queue を Scheduled Event で5分おきに実行し、適度な速度で5分間メッセージを消費/lambda:download_html の invoke を行う
- lambda:download_html で HTML をダウンロードし、S3 に Put する
2つめの方法
SQS について調べるの面倒だなと思っていたら浮かんだやつ。
{tableName: "ダイナモ"} みたいなのを渡されてそっから URL とってきて動きはじめて、いくらか処理してから残ってたら自分に {urls: [処理してないURL]} を渡してinvoke する。
var AWS = require("aws-sdk"); var lambda = new AWS.Lambda(); var dynamoDB = new AWS.DynamoDB(); var s3 = new AWS.S3(); var Promise = require('bluebird'); Promise.promisifyAll(Object.getPrototypeOf(dynamoDB)); Promise.promisifyAll(Object.getPrototypeOf(lambda)); var MAX_FETCH_COUNT = 300; var FETCH_INTERVAL = 1000; var DOWNLOAD_HTML_LAMBDA_FUNCTION_NAME = "download-html"; exports.handler = function crawl(event, context) { console.log("start to crawl"); var fetchUrls = event.urls ? Promise.resolve(event.urls) : fetchUrlsFromTable(event.tableName); fetchUrls .then(function(urls) { var fetchCount = Math.min(MAX_FETCH_COUNT, urls.length); var urlsToFetch = []; for (var i = 0; i < fetchCount; i++) { urlsToFetch.push(urls.pop()); } return Promise .all(urlsToFetch.map(function(item, index) { // downloaded_html/site_a/ 以下にズラっとファイルを並べたいので / を適当に置換しておく var s3_key = "downloaded_html/site_a/" + url.replace(/\//g, "$"); var params = buildLambdaParams(DOWNLOAD_HTML_LAMBDA_FUNCTION_NAME, { url: url, s3_key: s3_key }); return delayedLambdaInvoke(params, FETCH_INTERVAL * index); })) .then(function(_) { console.log("finish to crawl"); if (urls.length > 0) { var params = buildLambdaParams(process.env.AWS_LAMBDA_FUNCTION_NAME, { urls: urls }); return lambda.invokeAsync(params); } }); }) .then(function(_) { context.done(null, "OK"); }) .catch(function(err) { context.done(err); }); }; function fetchUrlsDynamoDB(tableName) { return dynamoDB .scanAsync({ TableName: tableName }) .then(function(data) { var urls = []; data.Items.forEach(function(item) { urls.push(item.url.S); }); return Promise.resolve(urls); }); } function delayedLambdaInvoke(params, delay) { return new Promise(function(resolve, reject) { setTimeout(function() { console.log("Invoke lambda with " + JSON.stringify(params, null, 2)); lambda.invokeAsync(params, function(err, data) { if (err) { reject(err); } else { resolve(data); } }); }, delay); }); } function lambdaInvoke(params) { return new Promise(function(resolve, reject) { console.log("Invoke lambda with " + JSON.stringify(params, null, 2)); lambda.invokeAsync(params, function(err, data) { if (err) { reject(err); } else { resolve(data); } }); }); } function buildLambdaParams(funcName, payloadObject) { return { FunctionName: funcName, InvocationType: 'Event', LogType: 'None', Payload: JSON.stringify(payloadObject, null, 2), }; }
API Gateway+LambdaでWebサイトを作っている時に404ページを出す最悪な方法
*で/含めてなんでも受けれるみたいな機能があったならもう少し嬉しいんだけど、特にそれっぽいのも見付からなかったので。 /a/b/c/d まではこれでなんとなく動いてるので、このまま放置して忘れたい。
TexturePackerのフリーライセンスを申請した
自分のブログのURLを書く欄があるんだけど、それが自分のだと証明するためにコード(TP:A1EC9A8E)をどっか見えるところに置いてくれって書いてあったので。ついでになんで使ってるかというと、
- sketchtoolでスライス書き出し -> 2. onion_ringで9patch -> 3. TexturePackerでTextureAtlas作る -> 4. Unity上で2, 3の情報を持たせる
ということをするために使っている。c.f. http://qiita.com/kyubuns/items/cb01f926966b51a5501c
Tools/sketchimport
#!/bin/sh ROOT=$(dirname $0)/.. sketchtool export layers $ROOT/Sketches/Dark.sketch --output=$ROOT/Images/Export # 9patch find $ROOT/Images/Export/UI -depth 1 -type f | grep ".9.png" | $ROOT/Tools/9patch > $ROOT/Assets/Images/UI_border.txt # pack TexturePacker --max-size 2048 --opt RGBA4444 --format unity-texture2d --sheet Assets/Images/UI.png --data Assets/Images/UI.tpsheet Images/Export/UI
Tools/9patch
#!/usr/bin/env bundle exec ruby require_relative "../Deps/onion_ring/onion_ring.rb" borders = [] ARGF.each do |before| before = File.expand_path(before.chomp) name = File.basename(before).gsub(".png", "") borders << [name, OnionRing::run(before, before)] end borders_text = borders.map { |line| line.flatten.join(',') }.join("\n") STDOUT.puts borders_text # vim:set ft=ruby:
みたいなのを書いといて、Tools/sketchimport 実行するやつと http://qiita.com/kyubuns/items/cb01f926966b51a5501c の AtlasBorderSetter.cs をEditor以下置いて、TexturePacker ImporterってのをAsset Storeからインポートおく。そうすると.sketchいじった後はUnity開いたら上手いことなっている。
初めてメールでパッチを送った
プログラムを書き始めた時にはもうgithubがあったので、どういう風な作法で送るのか全然分からなくて困った。
あたりを読んで、git format-patch でパッチを作って git send-email で送るのが楽っぽいことが分かった。
上記のLinuxのパッチの送り方だと添付ファイルで送るなって書いてあったんだけど、送ろうとしているtmuxのメーリングリストを見ると添付してるのもインラインで書いてるのも両方あった。 git format-patch も --attach っていうの指定すると添付ファイルとして送るように保存してくれる。悩んだあげくどっちでも受け入れられているなら見るための敷居が低くて検索/引用しやすいようにインラインのほうにした。
http://sourceforge.net/p/tmux/mailman/message/33500685/
たいしたことないものとはいえ、githubでpull request出すよりもかなり緊張感があった。
■
KeyRemap4MacBook から名前が変わって結構立つ気がするけど未だに綴りを間違えるkarabinerと、seilだけども
- /Applications/Karabiner.app/Contents/Library/bin/karabiner
- /Applications/Seil.app/Contents/Library/bin/seil
が入ってて、コマンドラインで色々出来る。
で今の状態まで karabiner set
使って持ってこう。ちょっと面倒だなあ。と思ったんだけど、karabiner export
とかってやると求めているものがまるっとexportされて便利だった。
goコマンドで予約されている(特別な意味を表わしてる)インポートパス
go get -u all
とか go get -u std
って出来るの知った。めちゃくちゃ便利。
- https://github.com/golang/go/blob/master/src/cmd/go/main.go
$ go help packages
(mainもっぽいけどそれに対しての何か実装はまだ見つけてない)
testing.Mが追加されテスト実行前後に何かを書けるようになった
- commit cmd/go, testing: add TestMain support · 182d131 · golang/go · GitHub
- issue testing: allow tests to control main execution · Issue #8202 · golang/go · GitHub
例えば
// main_test.go var exitCode = 0 func TestMain(m *testing.M) { defer os.Exit(exitCode) beforeAll() defer afterAll() exitCode = m.Run() } func beforeAll() {} func afterAll() {}
みたいにして、mainパッケージのようにテスト実行の前後に適当処理入れたりとかが出来るようになった(1.4rc2から)。 mysqldとかテストの間ずーっと立ちあげといて最後に止めたいもの(テスト毎やるとコストがかかるもの)があったりすると重宝すると思う。
以前からそういう願望があった。
たしか1.3かその直前ぐらいだったと思うけど、なんとかできないかなと思ってそのへんのコード読んでいると「TestGoFilesのASTを順番見てってfunc Test*(*testing.T)
をひろってテンプレートに渡して、それを実行する。」な風になっていてそれを利用してこういうことをしていた。
testing.Mainも自由にやりたい、もしくはtesting.after書きかえたい
testing.afterを自由にしたかった。テストの最後になんかしたかった。golang - コンドルが飛んでいる。 2014/01/14
久々にGo書いていて、ふと他にやり方ないかもう一回見とこうと cmd/go/test.go
とか testing
あたりを見ていたら、testing.M
とか TestMain(*testing.M)
とかってのがあって、どうやらこれは目的のものだってことで喜んでいた。
net/http
の z_last_test.go
もこれを使った形に書き直されている。 net/http: replace z_last_test hack with testing.Main · 93e5cc2 · golang/go · GitHub。
とりあえずここに落ち着いたGoの即時実行環境 -2014年冬-
goで同一ディレクトリに、複数main.mainをおきたい - コンドルが飛んでいる。
の続編。上記の方法だと色々と不満があったので、適当なtempdirにmain.go置いて$EDITOR立ちあげて編集終えると実行するやつみたいなの作った。
一回で上手くかけるとは限らないのでさっきの続きから書きはじめるのどうしようかなーと思って放置してたんだけど、 実行後にもっかい編集する? って聞いて同意した場合前回のやつから書けるようにした。
■
近所の医者に行ったら、オイ、デングということでデカめ病院のとこに行かされて検査してきたんだけど特に何もなくて単なる風邪っぽいねってなった。 まあ良かったんだけど、とにかくこの一連の行動に半日ぐらいかかってメチャクチャ疲れた。 後日おたふくワクチンの効果が切れかけだから今度打っとけってのと、サイトメガロウイルスとやらの抗体がないからそのうちそれ感染するかもだから覚えといてってのも言われた。
Internal Server Error
GoでWeb API書いてる時に、システム内部のエラーだったりで詳細をそのままユーザーに返さないもの、例えば(*sql.DB).Execとかでもいいんだけどそういうのをどう扱うか悩んでる。
- どうせhttp.Handlerはpanic起こったらInternal server error返すようみたいな作りにするだろうし、モデルっぽいところでpanicしてしまう。
- http.Handler内でエラーを比較してって必要なものだけ詳細を返す、マッチしなかったらログ吐いて、serveInternalServerError(http.ResponseWriter, *http.Response)みたいなのに渡す。
gopwt(PowerAssert for golang)がだいたいできた
goのpower assert用パッケージ、だいたいできた https://t.co/pzRuhoHVC5
— ピヨちゃんです (@ToQoz) July 14, 2014
この時は、「Assert内で副作用のある関数を呼んでいるとそれがコケた場合に、出力の時に再度呼ばれて実際の値と違うものが表示されたり、それ以降のテストに影響がある」みたいな問題があった。それを解決するには適当に関数の呼び出しをキャッシュしてやる必要があって、型のチェックが実行時にしかない言語なら、a() == b() とかってのを memorized(a) == memorized(b) とかってできると思うけど、わりかし大変だった。
reflect.ValueOf(f).Call(reflect.ValueOf(arg1), reflect.ValueOf(arg2))[0].Interface()
というように変換して変換後のコンパイル時の型のチェックを捨てる- そうすると取れる型が決まってる二項演算子が使えない
Opなんとか(x, y interface{})
を実装してその中で型をチェックしながら適当に適用していく感じにする - 組み込みの関数は、
reflect.ValueOf(f).Call
で呼べないから普通に呼ぶがそうすると型がハマらないので Op*と一緒のようなものを実装する - このへんまではまあ別にいいかなと思ってたんだけど、型の変換(
string('r')
) で上手くいかないことに気付いてかなり辛くなる。 - こざかしいことをして*ast.CallExprが普通の関数呼び出しか型の変換かを調べて、後者なら
reflect.ValueOf(x).Convert(reflect.ValueOf(new(t)).Elem().Type())
に変換する。
みたいなことをやった。こざかしいことは、code.google.com/p/go.tools/go
のgcimporterとtypesのおかげで、楽に出来たんだけど一気にブラックボックス感が高まった感じがした。
そもそもこういうアプローチはGoっぽくないと思う(作る前もgo test
に乗せれないかとかなんとか色々悩んだ)けど、TDDでdebugの代わりのテストみたいなのを書いて開発してく時とか、CIでテストコケた時の情報量の多さは結構メリットがあるかなと思う。自分以外が関わるところでこれに依存したテストをコミットするかどうかは今のところ自分自身悩ましい感じはある。まずはv0.0.1を打てるようにやっていく予定
(仕事する気力が無さすぎてエントリ書き出したものの気力がないから雑になった。)
go-unitypackageっての作った
https://github.com/ToQoz/go-unitypackage
- unity == http://unity3d.com
.unitypackage
に含まれてるファイルリスト取ったり、コマンドラインからインストール/アンインストールできるようにする
.unitypackage
に初めて触れてみてて、クリックしてインストールしたのは良いものの、アンインストールできなくて辛すぎて作った。(Pro版は知らん)
あと、
- ファイルリスト取って全部
.gitignore
にぶっこみたい(色々ぶちまけられるので手で設定するのは無理) - 使用する .unitypackage のファイルリストにバッティングするものがないかどうか知りたい
- 依存unitypackageを一括インストール、その後にゴニョゴニョ みたいなことをするスクリプトとか書きたい(「インストール後ここ書き変えて」ってpackageがあったから)
みたいな気持ちがあった。
まあ、とりあえずはおおまかにそのようなことが出来るようになったけど、一般的にこのへんの管理マジでどうやってるのか知りたい。 (unitypackageが吐き出すファイルをignoreせずにimportするごとにcommitしておいてアンインストールはrevertみたいなのはあんまりやりたくない...)
gog 紹介
静的サイト作るやつみたいな感じ。一般的なものにくらべて、機能が著しく少ない。_layout.html書いといてヘッダーフッター共通化しか機能がない。
ただ各ディレクトリに、GOG_BUILD.go
ってやつ置けて、それはgo run
される。置いておくと、例えば、build時にどっかからjson取ってきてHTML生成するみたいなことができる。
別のAPI経由でDB更新していって、jenkinsとかに3分に一回HTMLをbuildさせててたりすると、動的サイトみたいになれる。ユーザーからリクエストが来た時に計算はしなくても、動けるのかということを考えたりした。やらないけど。
それはそうと、なんでこういう機能を付けたかというと、http://github.com/ToQoz/toqoz.github.io/tree/master/_src/go/pkg で使いたかったというのもあるけど、
今のテンション的にコンドル飛んでないし、テンションによってブログのタイトル変わって欲しい
— ピヨちゃんです (@ToQoz) December 25, 2012
みたいな想いを持っていたことを思い出したことが大きい。
golangでパッケージ作る時の名前をこうやって付けてます。という話
これが最高とかって話ではない。
gemとかにおけるキラキラネームの良さは、被らないことだと思う。しかし、goにおいてはimport pathが別の人が作ったのと被るのを気にしなくて良いから、良さは特にないと思っているけど単に好みで割と付けようとしてしまう。
大体 1 -> 2 -> 3 って感じで考えてるけど、その順番も毎回必ずそうかというとそうでもない。
1. しっくりくるキラキラネーム、ダジャレを思いついた時
それを使う。子供に奇抜な名前付けると、名前だけで識別できる。
- package: キラキラネーム
- repository: 同上
2. 1に失敗して、golangに関係した(golangで書いたではない)パッケージを作る時
つまりpackage名にgoを含めたいようなものを作る時(e.g. godoc)
- package:
go
prefix を付けて、ひねりのない名前を付ける。 - repository: 同上
3. それ以外(これはgolangで書いたやつですみたいなの)
go-
prefix を付けて、ひねりのない名前を付ける。packageには付けない。あくまでリポジトリの識別性を上げるためにgo-
って付けてる。(e.g. go-formspec)。近所に同じ名前の子がいたら名字一緒に呼んであげると良いと思う。
- package:
go-
prefix を付けずに、ひねりのない名前を付ける。 - repository:
go-
prefix を付けて、ひねりのない名前を付ける。
ただ2分前ぐらいに思ったのは、https://github.com/google/go-github みたいに、リポジトリに go-
prefix付けて、そこにprefixなしgithub
のディレクトリ掘ってそこに package github
とか置くやつのほうが良いかなと思った。
package名とimport pathの最後が一致してるのはやっぱり良いなーと。