uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

我流togetterまとめ作成方法

最近、業務でtogetterまとめを作る機会が何度かあったので 自分なりのまとめ作成方法をメモ代わりに公開しておく。

まず、ヤフーのリアルタイム検索からハッシュタグで検索してtweetを拾ってくる。
リアルタイム検索

リアルタイム検索を使うのは特に理由はなかったはずだけど、スパムっぽいツィートが弾かれてるからだった気もする。

Firefox + Firebugでコンソールを使って下のスニペットを実行してtweetのURL一覧を取得。

$("#TSm p.lt").each(function(){
  var link = $(this).children("a")[1];
  var h = link.href;
  var twURL = decodeURIComponent(h.split("*")[2])
  console.log(twURL);
});

2016/06/05 追記

twitterからぶっこ抜きするにはこんなコードでOK
もうちょっとキレイな書き方あるはずだけどとりあえずこれで。

var elements = $("a.tweet-timestamp");
var n = elements.length;
for(var i = 0 ; i < n; i++){ 
  var link = $("a.tweet-timestamp")[i].getAttribute("href");
  link = "https://twitter.com" + link;
  console.log(link);
}

2016/09/26 追記

いつの間にかフォーマット変えられてたのでちょっと変更してURLの一部を抜き出す形式に変更。

$("#TSm p.lt").each(function(){
  var link = $(this).children("a")[1];
  var h = link.href;
  var RU = h.split("/")[9].slice(3);
  // var rawURL = decodeURIComponent(RU.replace('-', '=').replace('_', "/").replace(".", "+"));
  var rawURL = RU;
  rawURL = rawURL.replace(/\-/g, '=');
  rawURL = rawURL.replace("_", '/');
  rawURL = rawURL.replace(".", '+');  
  // console.log(decodeURIComponent(rawURL));
  console.log(window.atob(decodeURIComponent(rawURL)));
});

2018/01/06 追記

twitterの方が取れなくなっていたのでちょっと修正。

var x = "";
$$(".tweet-timestamp").forEach(
  function(el, i){
    x += el.href + "<br/>\n";
    console.error(el.href);
});
document.write(x);

querySelectorAll() を使うコードに変更。

次に、togetterのまとめ作成画面から画像のY字っぽいボタンをクリック。

f:id:uokada:20160328024142p:plain

クリックして出てきたフォームに先ほど取得したURL一覧を入力して完了。 f:id:uokada:20160328024144p:plain

あとは、時間順に並べて重複、RTを削っ荒いまとめの完成。
余裕があればここからデコレーションしたり、ハッシュタグがついてないけど関係ありそうなまとめを拾ってきて完成。

デコレーションはRTが多いtweetは優先的に赤字で大きくしたいと思っているのだが今のtogetterのUIだと難しいので、 今後の課題かなと思ってる。

最近読んだ本 2016/03

10年後、生き残る理系の条件

10年後、生き残る理系の条件

著者のtwitter観てて面白い人だったので購入。
いつもtwitterで主張しておられることをまとめた1冊って印象。

対象の読者層としては就活生から入社数年目までの若手が対象ってところだと感じた。

日本企業の社員は、なぜこんなにもモチベーションが低いのか?

日本企業の社員は、なぜこんなにもモチベーションが低いのか?


あとは今月はこの辺を読んでる。

自分の写真整理するためのGoスクリプトを書いた。

みなさん大掃除は済みましたか? 自分は今パソコンの中を大掃除中です。

今年たくさん写真とったおかげでMACのディスクをかなり圧迫していてちょうどそれの整理をしています。

自分のカメラは1枚の写真に対してORFとJPGファイルを2つ生成するような設定となっています。 ただ、ORFファイルがあればJPGは要らないな〜と思ったので今回まとめて整理出来るスクリプトを書きました。

ディレクトリを引数にとってそのディレクトリ以下を探索して、
JPGとORFファイルを重複して持っているものは削除するというものです。

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "regexp"
    //    "os"
    "flag"
    "log"
    "os/exec"
)

const (
    DEBUG     = false
    EXTENSION = ".JPG"
    WALK_PATH = "/Users/hogehoge/Dropbox/Pictures/写真 Library.photoslibrary/"
    TRASH_BOX = "~/.Trash"
)

const raw = `
on run argv
  tell application "Finder"
    repeat with f in argv
      move (f as POSIX file) to trash
    end repeat
  end tell
end run
`

var trash = filepath.Join(os.Getenv("HOME"), ".Trash")

func go2Trash(f string) error {
    bin, err := exec.LookPath("osascript")
    if err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    if _, err = os.Stat(trash); err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    path, err := filepath.Abs(f)
    if err != nil {
        return err
    }

    params := []string{"-e", raw, path}
    cmd := exec.Command(bin, params...)
    if err = cmd.Run(); err != nil {
        return err
    }
    return nil
}

func WalkFunc(path string, info os.FileInfo, err error) error {
    if err != nil {
        fmt.Println(err)
        return nil
    }
    if info.IsDir() {
        return nil
    }

    if ext := filepath.Ext(path); ext == EXTENSION {
        pat, err := regexp.Compile(EXTENSION + "$")
        if err != nil {
            fmt.Println(err)
            return nil
        }
        orfpath := pat.ReplaceAllString(path, ".ORF")

        if _, berr := os.Stat(orfpath); berr == nil {
            fmt.Println("ORF File Exist: ", path)
            if dryrun {
                // if dry-run mode, file isn't deleted.
                log.Println("next file will be deleted: ", path)
                return nil
            }
            // File Remove Process
            if rename {
                derr := go2Trash(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            } else {
                derr := os.Remove(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            }
        }
    }
    return nil
}

var path string
var rename, dryrun bool

func init() {
    flag.StringVar(&path, "path", WALK_PATH, "Pictures filepath.")
    flag.BoolVar(&dryrun, "n", false, "dry run flag")
    flag.BoolVar(&rename, "trash", true, "go to trashbox")
    flag.Parse()
    debugp(fmt.Sprintf("dryrun: %s", dryrun))
    debugp(fmt.Sprintf("trash: %s", rename))

    if path != WALK_PATH {
        tmp_p, err := filepath.Abs(path)
        if err != nil {
            log.Fatalln(err)
        }
        path = tmp_p
    }
    debugp(path)
}

func debugp(debugObj interface{}) {
    if DEBUG {
        fmt.Println(debugObj)
    }
}

func main() {
    f := WalkFunc
    err := filepath.Walk(path, f)
    if err != nil {
        fmt.Println(err)
    }
}

機能は以下のとおり。

  • ドライラン
  • ゴミ箱に入れずに直接削除モード

あとはこのスクリプトをビルドしてドライランで削除されるファイルを確認。 問題がなければドライランオプションを外して削除という流れです。

$ go build -o walker
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary -n
<中略>
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary
〜〜〜〜〜〜
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200882.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200883.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200886.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200889.JPG
〜〜〜〜〜〜

こんなスクリプトでもうまく動きましたが実行してみてると1つ課題が見つかりました。 コマンド実行でゴミ箱に送ってる部分が遅いのです。
ファイル1000個ぐらいで15分程度なので耐えられない遅さではないとは思いますが、 go で並列化出来るといいんだろうなと感じました。


余談ですが、ゴミ箱に入れる方法が分からなかったのでその部分は gomi を参考にさせてもらいました。
(AppleScript使って実現してるんですね。)

Link

navigator.sendBeaconを動かしてみた!!

What is navigator.sendBeacon?

navigator.sendBeaconってなんのこっちゃって感じなのでこれを読むと良い。

Beacon
日本語版: Beacon (日本語訳)

何がうれしいの!?って感じですが、例えばリンクをクリックした時にXMLHttpRequestでサーバーに情報を送っているようなケースで
サーバーのレスポンスが悪かったりするとサーバーにデータを送る前に遷移が完了してしまって取りこぼしてしまって正確なデータの収集が出来ないようなケースがあります。 また、サーバーからのレスポンスを待って遷移させるようなケースではサーバー側のレスポンスが悪いとページ遷移の妨げになりユーザーエクスペリエンスの低下につながります。

これをBeacon APIを使うことでユーザー・アクションを妨げることなくブラウザのバックグラウンドで処理を実行して確実にサーバーにデータを送れるようになります。
そして、送ったデータは広告なりアクセス解析なりなんなりと利用すればいいと思います。

サーバーを書いてみる

まずはサーバーを書いてみます。

package main

import (
    "encoding/base64"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "net/http"
    apachelog "github.com/gorilla/handlers"
    "os"
)

const (
    body string = `
<html>
<script>
  // emit non-blocking beacon to record state update
  function reportEvent(eventData) {
    navigator.sendBeacon('/collector', eventData);
  }

  // emit non-blocking beacon to record click on a link
  function reportClick(url) {
    var clickData = JSON.stringify({
      clickTo: url,
      clickAt: performance.now()
    });
    navigator.sendBeacon('/collector', clickData);
  }

  // emit non-blocking beacon when page transitions to background state
  document.addEventListener('visibilitychange', function() {
    if (document.visiblityState === 'hidden') {
      var analyticsData = buildSessionReport();
      navigator.sendBeacon('/collector', analyticsData);
    }
  });
</script>

<body>
 <a href='http://www.w3.org/' onclick='reportClick(this)'>
 <button onclick="reportEvent('button event!')">Click me</button>
</body>
</html>
`
    base64GifPixel = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
)

type BaseHandler struct{}

func (bh *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, body)
}

type BeaconHandler struct {
    BeaconImage []byte
    RequireTLS  bool
}

func (bh *BeaconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Println("send beacon")
    //if bh.RequireTLS  r.TLS == nil {
    // log.Printf("WARNING: Please Requst with https.\n")
    //}
    if s, err := ioutil.ReadAll(r.Body); err == nil {
        log.Printf("DEBUG: %#v\n", string(s))
    } else {
        log.Printf("DEBUG: Error %#v\n", err)
    }
    output := string(bh.BeaconImage)
    w.Header().Set("Content-Type", "image/gif")
    //w.Header().Set("Content-Length", string(len(output)))
    io.WriteString(w, output)
    return
}

func main() {
    port := 8000
    mux := http.NewServeMux()

    // set demo page handler
    bh := &BaseHandler{}
    mux.Handle("/hey", bh)

    // Convert image && set handdler
    output, _ := base64.StdEncoding.DecodeString(base64GifPixel)
    beacon := &BeaconHandler{
        BeaconImage: output,
    }
    mux.Handle("/collector",
        apachelog.CombinedLoggingHandler(os.Stderr, beacon))

    // Extra
    mux.Handle("/", http.RedirectHandler("/hey", http.StatusFound))


    fmt.Printf("Listen: %d\n", port)
    addr := fmt.Sprintf(":%d", port)
    srv := http.Server{
        Addr:     addr,
        Handler:  mux,
        ErrorLog: nil,
        //ReadTimeout:  10 * time.Second,
        //WriteTimeout: 10 * time.Second,
    }
    err := srv.ListenAndServe()
    if err != nil {
        log.Fatal(err)
    }
}

検証用のサーバースクリプトを用意したのでとりあえず、サーバーを起動してみよう。

% go run server.go
Listen: 8000

ブラウザからアクセスしてみる

navigator.sendBeaconが実装されているブラウザはあまり多くない。
caniuseで確認してみるとFirefoxChromeには実装されているがそれ以外のサポート状況はあまりよろしくない。
モバイル界隈も最新のAndroidのブラウザはサポートされているがiOS safariがサポートされていない状況となっている。
個人的にiOS safariがサポートされていないのは痛い状況で日本のWEB界隈だとiOS:Androidが7:3ぐらいなので7に実装されてないとnavigator.sendBeaconを利用する側のモチベーションに影響するんじゃないかと思う。
また、Can I useのページにも日本におけるサポートブラウザの比率が掲載されているがまだ 38.87% ということで半分にも満たない。
普及にはiOS Safariで実装がされることが必須のように思う。

Can I use... Support tables for HTML5, CSS3, etc

自分はFirefox 42からこのURLにアクセス。
アクセスしたらボタンを押して遷移してW3Cのページに移動しよう。
移動したら、コンソールに戻ってみたください。 下のようなログが落ちてるはずです。

% go run server.go
Listen: 8000

2015/12/13 02:34:45 send beacon
2015/12/13 02:34:45 DEBUG: "event!"
2015/12/13 02:34:45 send beacon
2015/12/13 02:34:45 DEBUG: "{\"clickTo\":{},\"clickAt\":2601.465}"

ここには詳細は書いてませんが仕様にはPOSTでリクエストすることとなってますので比較的大きなデータでも受け渡すことが可能です。

Beacon-Age ヘッダフィールドというものが定義されています。 これはユーザーアクションからサーバーに送るまで非同期で処理されるためアクションを起こした正確な時間をサーバー側で取得するためのヘッダーとなっています。 ただ、tcpdump使ってhttpの通信みた限りChrome,Firefoxともに実装されていないので頭の片隅に入ってればいいんじゃないかなと。

Link

Goで一定時間だけ立ち上がるサーバーを書いた

一定時間だけ立ち上がって自動的に終了するサーバーをGoで書いてみた。
あんまり用途はないのかもしれないが、例えばcronで毎日0:00から5分だけ立ち上がっていてそこにアクセスしてサーバーメトリクスを収集するみたいな使い方が出来るかもしれない。

いちおうコードの解説。
main関数のところだけコードをの詳細を書いてみる。
まずはじめに、runServer() でサーバーを立ち上げる。
次に、 time.Tick t で一定時間経過後にシグナルを送るようなselectループを作成する。
time.Tick et はただのデバッグ用のコードなので削除しても構わない。
最後に、 チャネル ch で入力を待って終了する。

func main() {
    go runServer()

    t := time.Tick(time.Duration(duration) * time.Second)
    et := time.Tick(time.Duration(1) * time.Second)
    go func() {
        for {
            select {
            case <-t:
                ch <- true
            case <-et:
                // debug code
                fmt.Printf("[%d] server running!\n", time.Now().Unix())
            }
        }
    }()
    _ = <-ch
    fmt.Println("Finished.")
}

試しに、3秒で終了するように動かしてた結果がこんな感じ。
きっちり、3回デバッグ用のコードが出力されて終了している。

% go run hey_server.go -duration=3
[1446923055] server running!
[1446923056] server running!
[1446923057] server running!
Finished.

以下、コード全体。

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "time"
)

type BaseHandler struct {
    Name string
}

func (bh *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if bh.Name != "" {
        fmt.Fprint(w, "Hey "+bh.Name+"!")
    } else {
        fmt.Fprint(w, "Hey World!")
    }
}

func runServer() {
    mux := http.NewServeMux()
    bh := &BaseHandler{Name: name}
    mux.Handle("/hey", bh)

    addr := fmt.Sprintf(":%d", 8080)
    err := http.ListenAndServe(addr, mux)
    if err != nil {
        log.Fatal(err)
    }
}

var ch = make(chan bool)

var name string
var duration int

func init() {
    flag.StringVar(&name, "name", "", "your name")
    flag.IntVar(&duration, "duration", 60, "working time")
    flag.Parse()
}

func main() {
    go runServer()

    t := time.Tick(time.Duration(duration) * time.Second)
    et := time.Tick(time.Duration(1) * time.Second)
    go func() {
        for {
            select {
            case <-t:
                ch <- true
            case <-et:
                // debug code
                fmt.Printf("[%d] server running!\n", time.Now().Unix())
            }
        }
    }()
    _ = <-ch
    fmt.Println("Finished.")
}

// https://gobyexample.com/select

Link

uokada.hatenablog.jp

influxdbをCLIから利用する

influxdb ver0.9を触っているけど、いろいろ変わりすぎているので公式ドキュメントしか頼っている。。。

ver0.6ぐらい触った時はこんな挙動だった?って思うことが沢山w

とりあえず、curlからデータを挿入してみる。

Write Syntax | InfluxDB

Specify Non-nanosecond Timestamps

Use the precision=[n,u,ms,s,m,h] query string parameter to supply a precision for the timestamps.

All timestamps are assumed to be Unix nanoseconds unless otherwise specified. If you provide timestamps in any unit other than nanoseconds, you must supply the appropriate precision in the URL query string. Use n, u, ms, s, m, and h for nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.

precisionパラメータでデータの精度を決めることが出来る。今回はテストなのでそこまで精度が必要ないので秒単位でデータを登録していく。

% curl -i -X POST 'http://localhost:8086/write?db=mydb&precision=s' \
  -d 'cput,host=server01,region=us-west value=0.64 1434055566'
HTTP/1.1 204 No Content
Request-Id: 5fa0e3de-8566-11e5-802b-000000000000
X-Influxdb-Version: 0.9.4.1
Date: Sat, 07 Nov 2015 15:44:04 GMT

バッチでデータを挿入することも出来るので大量データを挿入する場合はこっちでデータを挿入するのがベター。 ファイルのフォーマットは単発でデータを挿入する場合と一緒。

Write a Batch of Points with curl

You can also pass a file using the @ flag. The file can contain a batch of points, one per line. Points > must be separated by newline characters \n. Batches should be 5000 points or fewer for best performance.

これでもダメならUDPでデータ挿入なのかな。 Go製なのでパフォーマンスはかなり出るからこれを使うのはかなり先になりそう。

2015-10-13の艦これ戦果

5-2突破。編成はこんな感じでいった。

f:id:uokada:20151014073100j:plain

旗艦に軽空母正規空母x2、低速戦艦、重巡洋艦高速戦艦の順番。 軽空母を大破しにくいように旗艦に設定して、同じく戦艦を最後尾に戦艦を配置している。

制空権はまぁまぁ重視しているけど、BOSS戦は制空優勢ぐらいしか取れない編成で昼戦のみで十分S勝利出来た。

f:id:uokada:20151014073622j:plain

あと、ゲージ破壊時に夕雲さんGET。

f:id:uokada:20151014083932j:plain