digdagを使う際に覚えておくべき事メモ
タスクの状態が以下のいずれかになったときにタスクが開始される。
- 依存するタスクが無い
- 依存するタスクが全て完了している
実行のしかたは3つ
ローカル
digdag run piyo.dig
コマンドを叩いて実行
サーバ
digdag server -o digdag-server
サーバーが立つ http://127.0.0.1:65432 で管理画面
on Docker
docker: image: ubuntu:14.04
digファイルに↑のように記述されているとdockerで動く
バージョン管理
- プロジェクトという単位でスクリプトやコンフィグを管理している。
- リビジョンという単位でバージョンを管理している
- サーバはアップロードされた過去のリビジョンを保持している
タスクとオペレータ
+piyoとかはタスク
yaki>: tori
とかでアクションが実行できる
AWS上にサーバレスな汎用クローラを展開するぞ。
前回はAPIGatewayとLambdaで、指定したURLの指定した位置にあるデータを抜き出すAPIを作った。
今回はサイト内探索をするようなシステムをAWS上に構築しようと思う
注意:クローラは用法用量を守って、相手方のサイトに迷惑がかからないように十分な配慮を徹底しましょう。
今回作るもの
こんな感じの構成をイメージしてる。
DynamoDBの1レコードがサイトへの1リクエストに常に対応するものとし、内部リンクのURLを新たにDynamoDBのテーブルに追加していく。
得られたデータは対応レコードに格納される。
URLがテーブルに追加されると、DynamoDB Streamsに流され、Lambdaで実行される。
というような繰り返し。
DynamoDBのデータスキーム
1.Request先URL :target_url
例) https://example.com/items/1
2.巡回対象サイトのrootURL :root_url
3.対象データのセレクタ :selector
例) .score
4.対象データのスクレイピング結果 :result
例) 4.2
プライマリキーをtarget_urlに設定。
ストリームに流れるのは"新しいイメージ"のみで良いはず。
SERVERLESS FRAMEWORKの用意
SERVERLESS FRAMEWORKが最高すぎる - あのにのに SERVERLESS FRAMEWORKを使う。 DynemoDBへのレコード追加時に、Lambdaに渡されるJSONのサンプルがほしいので一旦デプロイして動かしてみる。
functions: spide: handler: handler.spide events: - stream: arn: #### batchSize: 1 startingPosition: LATEST
とりあえずデータを取得(スクレイピング)
console.log(event);
ってやるとCloudWatchの方に吐き出されるので、そのデータをevent.jsonに入れてローカルで動かしてみる。
'use strict'; var client = require('cheerio-httpcli'); module.exports.spide = (event, context, callback) => { var url = event.Records[0].dynamodb.NewImage.target_url['S']; var selector = event.Records[0].dynamodb.NewImage.selector['S']; client.fetch(url, {}, function(err, $, res) { console.log($(selector).text()); }); };
これで目的のデータは得られた。
結果をDynamoDBに格納
DynamoDBに入れておく。 DynamoDBを操作するために
GitHub - noradaiko/vogels: DynamoDB data mapper for node.js
をいれる。
var vogels = require('vogels'), Joi = require('joi'); var BasicSpider = vogels.define('BasicSpider', { hashKey: 'target_url', timestamps: true, schema: { target_url: Joi.string(), root_url: Joi.string(), selector: Joi.string(), result: Joi.string(), }, tableName: 'BasicSpider' });
BasicSpider.update({ target_url: url, result: $(selector).text() }, function(err, acc) { console.log('update account', acc); });
でresultが追加されるはず。 ただ、今の状態だとUPDATEでもリクエストが飛んでしまうため、レコード追加時のみスクレイピング処理が走るようにする。
if (!newImage.target_url || !newImage.selector || !newImage.root_url || newImage.result || newImage.updatedAt) return;
一旦デプロイして動かしてみる。
レコードを追加してちょっと待つと...
入った!!!
DynamoDBに対象URLと対象要素のセレクタを入れると、resultにデータを保存する仕組み
が出来たことにななる。
これはこれでなんか良さそう。
内部リンクを巡る
過去に訪問したページ → UPDATE処理になる為実行されない。(無限ループはない)
対象データが存在しないページ → resultのないレコードとして残る。
とりあえず内部リンクを拾うって訪問したことのないページをレコード追加する。
$('a').each(function(i, e) { var link = ($(e).attr('href') || '').replace('rootURL',''); if (link[0] == '/') { var target_url = rootURL + link; BasicSpider .query( target_url) .exec(function(err, acc) { if (acc.Count) return; BasicSpider.create({ target_url: target_url, root_url: rootURL, selector: selector }, function(err, post) { console.log("create:" + target_url); }); }); } });
後は諸々調整、timeoutとかmemorysizeとか。
実行
取れてる。
ただ並列数がどうなるのかが気になる。 あまり同時に叩きすぎると対象サイトに迷惑がかかるのでコントロールしたい。
つづく
React.js + firebaseでリアルタイムチャットアプリ作った。
でーきた。 特徴としては↓
- リアルタイムチャット(勝手に更新が反映される)
- チャンネルの作成が自由にできる。
- 無限にふぁぼれる
- 完全匿名
です。
ふぁぼり放題です。
作り方の解説
こっちでも雑に書いてはいたんですが、もうちょっと丁寧に記述します。
react-boilerplateを使う。
github.com 開発環境を整えるのが大変なので、ボイラープレートを使いました。 詳細はリンク先みていただくとわかるかと思うんですが、
- React.js
- Redux
- ES6
- webpack
- ServiceWorker
- Mocha
- enzyme
- eslint
- CLI
などなど、モダンなフロントエンド開発に使うものがいっぱい入ってます。
ただ、Angularと比べると非公式なものなので、結構バギー。 私の場合webpackのcss生成の記述がおかしくなってて手動で対応したりしてました。 あとeslintがめちゃめちゃ厳しいです... サンプルプログラムの時点で結構eslint:disabledの記述があったので、付き合い方が難しそう。
Material UIを使う
UIframeworkをなににしようか悩んだんですが、やっぱり王道のMaterial-UIにしました。理由としては
という感じです。
グリッドシステムも特に使わず、できるだけ純粋なMaterial-UIで書きました。
firebaseのリアルタイムDBを使う(reactfire)
これを使ってみたくてこのチャットを作り始めたみたいなところあります。 "データベース"のくせにリアルタイム更新と、プッシュ機能も持ってるのですごいです。
チャンネル内の情報なんかは
const ref = firebase.database().ref('channels/default'); this.bindAsArray(ref, 'channel');
と記述するだけで、そのコンポーネントのstate.channelにバインドされます。
バインドされた状態のstateにsetState()
で変更を加えるだけで、DBに更新が走り、
バインドしている全クライアントに対してプッシュされます。
すごい!
普通にjavascriptで使う場合は
firebase.database().ref('channels/default').on('eventname', function(snapshot) { var mes = $('<span>',{class:'message'}).text(snapshot.val().message); $('#message-box').append(mes); });
のような記述で動きます。
ちなみに、このリアルタイムDBはユーザ認証を用いたパーミッション機能があるのですが、 今回は認証なしで動いているため、誰でも自由に書き換えができます。
一応サニタイズは行われるぽいのですが、バリデーションのかけようがないのが気になってる。
雑感(やってみて)
- やっぱりSPAは夢があって楽しい
- サーバ側はどうしようかもうちょっと考えたい
- GraphQLとかちょっと手を出してみる予定
- firebaseDB面白いけど使う場面がかなり限られる
- とはいえfirebaseのホスティングはhttpsカスタムドメイン対応しているのですごく良い!
あとServiceWorkerについてなんですが、キャッシュとしての効力が強すぎるのと、扱い方が難しそうだったのでオフにしてます。また別途勉強したい。
1日でリアルタイムチャットを作る。
作ります。後から加筆していく。
10:30 Start!
まずは大まかな要件
- チャンネルの概念がある(slackのような)
- 1チャンネル1000ポストまで(2chのような)
- 匿名で投稿
- 無限ふぁぼ( 超重要!)
11:00
スーパーでネギトロを買ってネギトロ丼を食べる。 アニメを見る。 "ユーリ!!! on ICE"と"ガーリッシュナンバー"。よい。
11:30
少し細かく
firebaseのリアルタイムDB使う
- channel_indexes:Array
- (name):String
- channels:Array
- (channel):Object
- id
- message:Text
- posted_at:Datetime
- fabo:Integer こんなもんで良さそう
- (channel):Object
ルーティングとか
/chennel_name#messageID
UIコンポーネント
これ使う。
12:00
プロジェクトを作り始める
プロジェクト名はもちろんtimes-nakajima
Nekojimaで。
やるぞ。 firebaseのほうのプロジェクトも作る。
DBを軽くつくっておく。
{ "rules": { ".read": "true", ".write": "true" } }
ルールは一旦これでいいや。
DBもこんなもんで。
frameworkもいれる
これつかう。
12:30
とりあえず何かしらが公開されている状態にする。 FirebaseのHostingを使う。 react-boilerplateでビルドした後の静的ファイルを公開する形にする想定。 でもいったんてきとーに内容書いてデプロイ。 あとはカスタムドメインを通す。
もちっと時間がかかる。
boilerplateの初期設定
$ npm run setup $ npm run clean
これで初期化される。
あとは試しにビルドしてみる
$ npm run build
出来たけど/build
に吐き出された。
firebaseのデプロイ対象ディレクトリは/public
なので、これを変える。
{ ... "hosting": { "public": "build" } }
サイドdeployする。
よしよし。 そんなこんなでSSL通ったぞ。 DNSレコード書き換えて... レコード設定ミスっていたのでもう少し時間がかかりそう。30分ぐらい?
13:00
UIframeworkと、firabaseを差し込んでいく。
<MuiThemeProvider>
を全体に適応させる。
はいった。
$ npm install reactfire firebase react-mixin@2 --save
firebaseとreactfireをいれる。 react-mixinはReactコンポーネントにfirebaseリアルタイムDBとのバインディング機能をアドオンするために使う。
つながってるか確認するためのデータをRealtime Databaseに入れる。
フロント側も適当な場所で
var ref = firebase.database().ref("home_message"); this.bindAsObject(ref, "homeMessage");
とかする。 ほいきた!
とかやってたらドメイン設定完了したっぽい ビルド&デプロイしよう。
できt.. てない!反映されない!Nannde!
14:00
デプロイで躓く わからん!反映に時間かかるのかな。 ちょっと休憩しよう。
16:00
この時点でもう結構色んな問題が起こっていることに気づいた。 少しづつ直していく。
17:45
materialUIがうまく扱えなくてどうしよう。一旦寝るという選択肢。
19:00
起きた。 github.com
寝たらすぐに解決した。よかった。
20:30
再開 チャンネル切り替えと追加の機能をつける。
だめでしたー!!!!
思いの外バグ踏んだのと、firebaseの扱いがうまくいかなかったので未完。 とりあえずURLだけ貼っておきます。
サーバレスな汎用スクレイパーを作った。
以前SERVERLESS FRAMEWORKを紹介したんですが、 今回はSERVERLESS FRAMEWORKを使ってスクレイパーを作りました。
使い方
serverless deploy --stage production
でAWSにdeployして使います。
例: githubリポジトリのページからStar数を取ってくる。
/scrape? url=https://github.com/anoChick/basicscraper& query=.social-count.js-social-count& datatype=int
url
で対象ページを指定し、query
で対象のhtml要素を指定します。
datatype
をつかうとトリムされたり小数点を丸めたり出来ます。
response:
{ "datetime": "2016-11-27 05:56:14", "url": "https://github.com/anoChick/basicscraper", "result": { "query": ".social-count.js-social-count", "value": 0 } }
同一サイトに対して複数の要素を取得したい場合は、パラメータを配列にして渡すことも出来ます。 また、labelをつけることも可能です。
response:
{ "datetime": "2016-11-27 05:54:51", "url": "https://github.com/anoChick/basicscraper", "result": [ { "index": "0", "query": ".social-count.js-social-count", "value": 0, "label": "Star数" }, { "index": "1", "query": ".author", "value": "anoChick", "label": "Author" } ] } ## 次にやること ```/scrape```でスクレイピングの即時実行する機能が提供できました。 ```/schedule```などでジョブのスケジューリングなんかもやれたら良いなって思ってます。
ご利用は自己責任でお願いします。
パーリンノイズでそれっぽいダミーチャートを作る
「折れ線グラフとかのダミーデータをどうやって用意しよう」と思い、 パーリンノイズを使ったらそれっぽくなったので紹介します。
パーリンノイズってなんだ
主にコンピュータグラフィックスの世界でよく使われるんですが、すごく直感的な説明をすると自然に連続な乱数です。 ゲームだとテクスチャや、地形の凹凸をそれっぽくするときなんかによく使われます。 3次元のパーリンノイズが↑のようなものです。 ボコボコ感がそれっぽくなってると思います。このうち高さ情報を白黒に変換すると↓ のようになります。
このような、火山地帯っぽいテクスチャが自動生成出来るというわけです。
ラインチャートのデータとして使う
↑で紹介したのは3次元、2次元のパーリンノイズですが、さらに1次元落としたものを実際に作ってみようと思います。
gem install perlin_noise
require 'perlin_noise' n1d = Perlin::Noise.new 1 result = [] 0.step(10, 0.3) do |x| result.push((n1d[x] * 100).floor) end p result
$ ruby pn.rb => [50, 31, 26, 44, 61, 75, 61, 44, 26, 31, 50, 56, 45, 45, 61, 75, 61, 45, 45, 56, 50, 43, 54, 54, 42, 50, 57, 45, 45, 56, 50, 43, 54, 54]
単純に1次元で生成したものがこちら。 そこまで不規則的ではなくてイマイチ。 次は2次元で生成したものをスライシング(ある面で切り取って次数を下げる)するという方法でやってみます。
require 'perlin_noise' n2d = Perlin::Noise.new 2 result = [] 0.step(10, 0.3) do |x| result.push((n2d[0,x] * 100).floor) end p result
$ ruby pn.rb => [50, 37, 41, 49, 51, 61, 58, 45, 33, 40, 50, 51, 46, 47, 54, 56, 50, 50, 51, 51, 50, 47, 45, 48, 53, 56, 52, 48, 37, 36, 50, 67, 72, 55]
それっぽくなりました。 パーリンノイズのアルゴリズムによっては、一次元でそれっぽく生成出来るらしいのですが、使用したgemの実装がシンプルだったことと、比較グラフも合わせて表示させることを考えるとこの方が良さそうだったので、二次元をスライシングするやりかたを採用しました。というのも、
require 'perlin_noise' n2d = Perlin::Noise.new 2,seed:12345 result1 = [] result2 = [] 0.step(10, 0.3) do |x| result1.push((n2d[0,x] * 100).floor) result2.push((n2d[0.2,x] * 100).floor) end p result1,result2
$ ruby pn.rb => [50, 56, 46, 45, 57, 52, 45, 53, 59, 53, 50, 47, 42, 47, 57, 67, 58, 46, 44, 51, 50, 45, 49, 51, 48, 53, 54, 47, 39, 43, 50, 48, 42, 46] [51, 56, 45, 43, 53, 48, 40, 48, 58, 60, 59, 53, 39, 38, 48, 62, 60, 50, 50, 61, 60, 55, 57, 58, 55, 54, 49, 40, 31, 32, 39, 40, 36, 41]
このように、近くにあるデータも併せてとることができます。 いい感じに比較グラフっぽくなってますよね。
パーリンノイズのしくみとか
私にはちょっと説明できそうにないので参考文献だけ貼っておきます。
引用・参考文献
パーリンノイズを理解する http://postd.cc/understanding-perlin-noise/
ランダム地形生成 Part1~パーリンノイズ http://qiita.com/y_li/items/e058bfc2ff8051008679
ランダム地形生成 Part2~フラクタルブラウン運動 http://qiita.com/y_li/items/290754b9c3ba18e9fb2b
junegunn/perlin_noise https://github.com/junegunn/perlin_noise
React.jsにthree.jsぶっこむぞ
「背景でWebGLがぬるぬる動くWebサイト作りたいぞ!」
って思ったときに、 ページ遷移する度に背景再描画されるのすっごくイケてなさみがある。 結局SPAっぽく使うことになるんだとおもった。 ということでReact.jsでthree.jsを使うパッケージつかう。
import React from 'react'; import React3 from 'react-three-renderer'; import THREE from 'three'; export default class HomePage extends React.Component { constructor(props, context) { super(props, context); this.cameraPosition = new THREE.Vector3(0, 0, 100); this.state = { cubeRotation: new THREE.Euler(), }; this._onAnimate = () => { this.setState({ cubeRotation: new THREE.Euler( this.state.cubeRotation.x + 0.004, this.state.cubeRotation.y + 0.002, 0 ), }); }; } render() { const width = window.innerWidth; const height = window.innerHeight; return (<React3 mainCamera="camera" width={width} height={height} onAnimate={this._onAnimate} > <scene> <perspectiveCamera name="camera" fov={75} aspect={width / height} near={0.1} far={1000} position={this.cameraPosition} /> <mesh rotation={this.state.cubeRotation} > <torusGeometry radius = {50} tube = {10} radialSegments = {10} tubularSegments = {20} arc = {Math.PI * 2} /> <meshNormalMaterial wireframe = {true} /> </mesh> </scene> </React3>); } }
できたできた。 ちなみに私はReactアプリケーションを作るときはとりあえず
これ使ってます。サクッと環境が整うので良い。