2017/12/27


Twitter でだけで告知しててこっちで書いて無かったので。

Dropbox Paper、外出先などで連載記事のネタなどを書きとめておくのに便利で使っているのだけど、手元の Vim で途中まで書いたチラ裏の様な物を一括でアップロードしたり削除するのが面倒だったので CLI 書いた。

GitHub - mattn/dboxpaper: client for Dropbox Paper

README.md dboxpaper client for Dropbox Paper Usage NAME: dboxpaper - Dropbox Paper client USAGE: dbo...

https://github.com/mattn/dboxpaper

使い方は

dboxpaper list

で一覧表示。Dropbox Paper の API がイケてないのでタイトルを取るには2回 API を発行する必要がある。タイトルも欲しい人は dboxpaper list --title の様にフラグを付ける。JSON で欲しい人は --json で。

アップロードは標準入力から。

cat README.md | dboxpaper upload

cat も出来る。

dboxpaper cat XXXXXXXXXXX

XXXXXXXXXXX は dboxpaper list で表示されるID。

削除も。

dboxpaper delete XXXXXXXXXXX

シェル操作大好きで Dropbox Paper 使ってる人には割と便利だと思うのでお使い下さい。

Posted at by



2017/12/21


Go は簡単に軽量スレッドが起動できるのがウリなのだけど、その使い方が難しいと思われているきらいがある。

Goへの誤解について - GolangRdyJp

よくGoで誤解されるポイントについて個人的な見解を書いておきます。 今回の記事は Goアドベントカレンダー2017 その3 の20日目の記事です。 使ってないパッケージがコンパイルエラーって面倒じゃね...

http://golang.rdy.jp/2017/12/20/go-fact/

慣れていない間は、処理を並行化する際に「どうやったら並行化できるんだ」が分からない事があるのだと思う。

Big Sky :: golang の channel を使ったテクニックあれこれ

golang の channel は他の言語に見ない独特のパラダイムを開発者に提供します。 単純にスレッド間でメッセージングをするだけでもC言語で書けばそこそこの量になったり、慣れていない人であればど...

https://mattn.kaoriya.net/software/lang/go/20160706165757.htm

これに書いたワーカー等は、ある程度 Go に慣れていないと簡単には書けないかもしれない。ただ、実はそういった人達でも簡単に並行化できて、しかもリソースに制限を掛ける事もできる。例えば以下のコードを見て欲しい。

package main

import (
    "fmt"
    "time"
)

func doSomething(u string) {
    fmt.Println(u)
    time.Sleep(2 * time.Second)
}

func main() {
    urls := []string{
        "http://www.example.com",
        "http://www.example.net",
        "http://www.example.net/foo",
        "http://www.example.net/bar",
        "http://www.example.net/baz",
    }

    for _, u := range urls {
        doSomething(u)
    }
}

スライスを処理する単純なコードで、シーケンシャルに処理しているので遅い。教科書的に並行化するのであれば以下の様になる。

package main

import (
    "fmt"
    "sync"
    "time"
)

func doSomething(u string) {
    fmt.Println(u)
    time.Sleep(2 * time.Second)
}

func main() {
    urls := []string{
        "http://www.example.com",
        "http://www.example.net",
        "http://www.example.net/foo",
        "http://www.example.net/bar",
        "http://www.example.net/baz",
    }

    var wg sync.WaitGroup
    for _, u := range urls {
        wg.Add(1)
        go func() {
            defer wg.Done()
            doSomething(u)
        }()
    }
    wg.Wait()
}

ここで上記リンク先で示したワーカーとの違いを考えてみて欲しい。ワーカーの方は起動個数が決まっているのでリソース制限が出来ている。負荷を掛けない為に goroutine の起動個数を絞っている。一見、このコードからワーカーへの移行は難しい様に見えるが、実はこのコードから簡単にリソース制限できる。

package main

import (
    "fmt"
    "sync"
    "time"
)

func doSomething(u string) {
    fmt.Println(u)
    time.Sleep(2 * time.Second)
}

func main() {
    urls := []string{
        "http://www.example.com",
        "http://www.example.net",
        "http://www.example.net/foo",
        "http://www.example.net/bar",
        "http://www.example.net/baz",
    }

    limit := make(chan struct{}, 3)

    var wg sync.WaitGroup
    for _, u := range urls {
        wg.Add(1)
        go func() {
            limit <- struct{}{}
            defer wg.Done()
            doSomething(u)
            <-limit
        }()
        time.Sleep(200 * time.Millisecond)
    }
    wg.Wait()
}

分かりやすく200msのウエイトを入れてある。

limit というバッファ数を付けた channel を作って、処理の前で追加、処理の後で取り出すというコードで挟むだけ。limit は3個のバッファなのでこの doSomething は同時に3個までしか実行されない。注意する点としては goroutine は urls の個数分起動する事。その処理に注力したいが対象の処理(ダウンロードするファイルサーバなど)に負荷を掛けたくない場合に、既存のシーケンシャルな処理を簡単に並行化する際のテクニックである。

追記: タイトルが goroutine の実行個数と言いながら goroutine 自体はいっぱい起動するのはこれ如何にという指摘を頂きましたが、ごもっともでタイトルバグですね。ただ GOMAXPROCS 分しかアクティブな goroutine は回されないので、実用的ではあります。

Posted at by



2017/12/05


この記事は Go Advent Calendar 2017 の記事... ではありません

追記あり

実はあったりします。

Golang で URL から charset を取得するのを書いたのですが、他にもっとよい方法があるとおもうのです... - Qiita

他によい方法がある気がするのですが...わからないので、書いてみました。

https://qiita.com/mochizukikotaro/items/ddc0c6b1b98cd33f451e

golang.org/x/net/html/charset を使うと良いです。

package main

import (
    "bufio"
    "fmt"
    "log"
    "net/http"

    "golang.org/x/net/html/charset"
)

func main() {
    resp, err := http.Get("https://google.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    br := bufio.NewReader(resp.Body)
    if data, err := br.Peek(1024); err == nil {
        if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
            fmt.Println(name)
        }
    }
}

charset.DetermineEncoding を使うと BOM や Content-Type ヘッダ、meta タグ等といった情報から charset 名を得られます。日本のIPアドレスからであれば shift_jis と表示されると思います。

しかしながらこの charset 名を使ってどうやって HTML からテキストを得るかという問題が起きると思います。そこで go-encoding という物があります。

GitHub - mattn/go-encoding
https://github.com/mattn/go-encoding

オフィシャルが提供する golang.org/x/text/encoding には各エンコーディングに対する utf-8 へのデコード処理が書かれているのですがエンコーディング名とのマッチングがありません。そこで使うのが go-encoding です。

br := bufio.NewReader(resp.Body)
var r io.Reader = br
if enc := encoding.GetEncoding(name); enc != nil {
    r = enc.NewDecoder().Reader(br)
}

この様に名称から Encoding オブジェクトを得る事が出来ます。実装はただただ並べただけの物なので見ても面白い物はありません。あとはこれを使って HTML をパースすれば HTML からテキストのみを抽出する事が出来る様になります。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/mattn/go-encoding"
    "golang.org/x/net/html"
    "golang.org/x/net/html/charset"
)

func text(resp *http.Response) (stringerror) {
    br := bufio.NewReader(resp.Body)
    var r io.Reader = br
    if data, err := br.Peek(1024); err == nil {
        if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
            if enc := encoding.GetEncoding(name); enc != nil {
                r = enc.NewDecoder().Reader(br)
            }
        }
    }

    var buffer bytes.Buffer
    doc, err := html.Parse(r)
    if err != nil {
        return "", err
    }
    walk(doc, &buffer)
    return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
    if node.Type == html.TextNode {
        data := strings.Trim(node.Data, "\r\n ")
        if data != "" {
            buff.WriteString("\n")
            buff.WriteString(data)
        }
    }
    for c := node.FirstChild; c != nil; c = c.NextSibling {
        switch strings.ToLower(node.Data) {
        case "script""style""title":
            continue
        }
        walk(c, buff)
    }
}
func main() {
    resp, err := http.Get("http://example.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    s, err := text(resp)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(s)
}

追記

ヘッダが shift_jis を返して来ない場合は DetermineEncoding の最後の戻り値が ok を戻さない様なので修正。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/mattn/go-encoding"
    "golang.org/x/net/html"
    "golang.org/x/net/html/charset"
)

func text(resp *http.Response) (stringerror) {
    br := bufio.NewReader(resp.Body)
    var r io.Reader = br
    if data, err := br.Peek(4096); err == nil {
        enc, name, _ := charset.DetermineEncoding(data, resp.Header.Get("content-type"))
        if enc != nil {
            r = enc.NewDecoder().Reader(br)
        } else if name != "" {
            if enc := encoding.GetEncoding(name); enc != nil {
                r = enc.NewDecoder().Reader(br)
            }
        }
    }

    var buffer bytes.Buffer
    doc, err := html.Parse(r)
    if err != nil {
        return "", err
    }
    walk(doc, &buffer)
    return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
    if node.Type == html.TextNode {
        data := strings.Trim(node.Data, "\r\n ")
        if data != "" {
            buff.WriteString("\n")
            buff.WriteString(data)
        }
    }
    for c := node.FirstChild; c != nil; c = c.NextSibling {
        switch strings.ToLower(node.Data) {
        case "script""style""title":
            continue
        }
        walk(c, buff)
    }
}
func main() {
    resp, err := http.Get("http://www.itmedia.co.jp/news/articles/1710/26/news006.html")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    s, err := text(resp)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(s)
}
みんなのGo言語[現場で使える実践テクニック] みんなのGo言語[現場で使える実践テクニック]
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧大輔, 鈴木健太
技術評論社 Kindle版 / ¥2,178 (2016年09月09日)
 
発送可能時間:

Posted at by