LoginSignup
28
26

More than 5 years have passed since last update.

CSVダウンロードを作るのに飽きたからGoogle Drive APIを使ってみた

Last updated at Posted at 2015-06-07

動機

管理画面からホゲホゲの集計データを出力するためにCSVを作ってダウンロードする処理を作ることがよくありますが、CSVって罠が多いです(Shift_JISとか改行コードとか)。
そういうノウハウを駆使してCSVダウンロード機能を作るのにちょっと飽きたので他の方法を集計データを出力できないか考えてみました。

Google Appsを使っているなら社内データもGoogleに上げてOKです。そもそもOfficeを使ってなくてAppsを使ってる場合もあるので、はじめからGoogle Driveにファイルを置いとけば仕事の流れ的に便利なんじゃないかと思います。
だからこの記事ではCSVをGoogle Driveに上げる方法を検討してみます。

言語は個人的趣味によりGo言語を使います(他の言語でも同様のはず)。

Google Drive APIを試す

公式ドキュメントのQuickStartにチュートリアルとサンプルコードがあるので、まずはこれをそのままやってみます。

ただし以下の箇所がサンプルコードと食い違ってるので注意。

getするoauthライブラリがサンプルと違う
go get google.golang.org/api/drive/v2
go get golang.org/x/oauth2/...

# サンプルではこちらを使う
go get code.google.com/p/goauth2

goauth2のページを見ると goauth2 is deprecated. Use golang.org/x/oauth2 instead. となってるのでいつかサンプルも書き換わってくれると思います(フィードバックは送信済み)。

APIをつかうための準備をする

Google Developer Consoleでプロジェクトを作ってDriveAPIの追加、認証情報の作成をします。
ここらへんの詳しいやり方は次の記事が参考になります。

googleapi - Node.jsのOAuthを使ってGoogleDriveAPIを叩いてみた - Qiita

サンプルコードを動かす

さてようやくコードを動かせる、と思いきやサンプルコードにも食い違いがありました。
importしてるgdriveのパッケージが違います。そのままだと動かないので修正したコードを貼っときます。

gdrive_sample.go
package main

import (
    "code.google.com/p/goauth2/oauth"
    "fmt"
    // "code.google.com/p/google-api-go-client/drive/v2"
    "google.golang.org/api/drive/v2" // こっちに書き換え
    "log"
    "net/http"
    "os"
)

// Settings for authorization.
var config = &oauth.Config{
    ClientId:     "YOUR_CLIENT_ID",       // ここは作った認証情報のIDに書き換える
    ClientSecret: "YOUR_CLIENT_SECRET",   // ここもSecretに書き換える
    Scope:        "https://www.googleapis.com/auth/drive",
    RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",
    AuthURL:      "https://accounts.google.com/o/oauth2/auth",
    TokenURL:     "https://accounts.google.com/o/oauth2/token",
}

// Uploads a file to Google Drive
func main() {

    // Generate a URL to visit for authorization.
    authUrl := config.AuthCodeURL("state")
    log.Printf("Go to the following link in your browser: %v\n", authUrl)
    t := &oauth.Transport{
        Config:    config,
        Transport: http.DefaultTransport,
    }

    // Read the code, and exchange it for a token.
    log.Printf("Enter verification code: ")
    var code string
    fmt.Scanln(&code)
    _, err := t.Exchange(code)
    if err != nil {
        log.Fatalf("An error occurred exchanging the code: %v\n", err)
    }

    // Create a new authorized Drive client.
    svc, err := drive.New(t.Client())
    if err != nil {
        log.Fatalf("An error occurred creating Drive client: %v\n", err)
    }

    // Define the metadata for the file we are going to create.
    f := &drive.File{
        Title:       "My Document",
        Description: "My test document",
    }

    // Read the file data that we are going to upload.
    m, err := os.Open("document.txt")
    if err != nil {
        log.Fatalf("An error occurred reading the document: %v\n", err)
    }

    // Make the API request to upload metadata and file data.
    r, err := svc.Files.Insert(f).Media(m).Do()
    if err != nil {
        log.Fatalf("An error occurred uploading the document: %v\n", err)
    }
    log.Printf("Created: ID=%v, Title=%v\n", r.Id, r.Title)
}
アップロード
$ echo "hello gdrive" > document.txt
$ go run gdrive_sample.go

ブラウザで見れば、自分のGoogleDriveのルートフォルダにアップロードされてるはずです。

トークンキャッシュを使うように改造

サンプルでは1回ごとにブラウザ経由で認証コードを取得する必要があります。何度もやってると面倒なのでファイルにキャッシュする機能を付けます。
最初に1回はブラウザ起動する必要がありますが、次回から有効期限が過ぎてたら自動で更新してくれるようになります。

TokenCache付きサンプル
package main

import (
    "code.google.com/p/goauth2/oauth"
    "fmt"
    // "code.google.com/p/google-api-go-client/drive/v2"
    "google.golang.org/api/drive/v2" // こっちに書き換え
    "log"
    "net/http"
    "os"
)

// Settings for authorization.
var config = &oauth.Config{
    ClientId:     "YOUR_CLIENT_ID",
    ClientSecret: "YOUR_CLIENT_SECRET",
    Scope:        "https://www.googleapis.com/auth/drive",
    RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",
    AuthURL:      "https://accounts.google.com/o/oauth2/auth",
    TokenURL:     "https://accounts.google.com/o/oauth2/token",
    TokenCache:   oauth.CacheFile("cache.json"), // キャッシュするファイル名を指定
}

// Uploads a file to Google Drive
func main() {
    t := &oauth.Transport{
        Config:    config,
        Transport: http.DefaultTransport,
    }

    // キャッシュがあったらブラウザ認証しない
    _, err := config.TokenCache.Token()
    if err != nil {
        // Generate a URL to visit for authorization.
        authUrl := config.AuthCodeURL("state")
        log.Printf("Go to the following link in your browser: %v\n", authUrl)

        // Read the code, and exchange it for a token.
        log.Printf("Enter verification code: ")
        var code string
        fmt.Scanln(&code)
        _, err := t.Exchange(code)
        if err != nil {
            log.Fatalf("An error occurred exchanging the code: %v\n", err)
        }
    }

    // Create a new authorized Drive client.
    svc, err := drive.New(t.Client())
    if err != nil {
        log.Fatalf("An error occurred creating Drive client: %v\n", err)
    }

    // Define the metadata for the file we are going to create.
    f := &drive.File{
        Title:       "My Document",
        Description: "My test document",
    }

    // Read the file data that we are going to upload.
    m, err := os.Open("document.txt")
    if err != nil {
        log.Fatalf("An error occurred reading the document: %v\n", err)
    }

    // Make the API request to upload metadata and file data.
    r, err := svc.Files.Insert(f).Media(m).Do()
    if err != nil {
        log.Fatalf("An error occurred uploading the document: %v\n", err)
    }
    log.Printf("Created: ID=%v, Title=%v\n", r.Id, r.Title)
}

サンプルと同じように実行すると一回目はブラウザ認証を求められますが、二回目以降は cache.json のおかげで何も入力を求められずにアップロードできます。

サーバサイドで動かす場合、いちいちブラウザ認証するのが面倒なのでこういう機能は必要ですね! そもそもバッチ処理だと無理だし。

ちなみに goauth2 にはTokenCacheがありますが、oauth2 には無いので自前で作る必要があるみたいです。

CSVを作ってアップロードする

ではいよいよCSVをGoogle Driveにアップロードしてみます。

上記のサンプルコードではファイルを開いてアップロードしています。サーバ側でいちいちファイルに落としてからアップロードするのは面倒なのでCSV文字列をCSVとしてアップロードしてみます。

使うのは bytes.Reader と MimeType です。
書き換えたコードはこちら。

gdrive_csv_upload.go
package main

import (
    "bytes" // 追加
    "code.google.com/p/goauth2/oauth"
    "fmt"
    // "code.google.com/p/google-api-go-client/drive/v2"
    "google.golang.org/api/drive/v2" // こっちに書き換え
    "log"
    "net/http"
)

// Settings for authorization.
var config = &oauth.Config{
    ClientId:     "YOUR_CLIENT_ID",
    ClientSecret: "YOUR_CLIENT_SECRET",
    Scope:        "https://www.googleapis.com/auth/drive",
    RedirectURL:  "urn:ietf:wg:oauth:2.0:oob",
    AuthURL:      "https://accounts.google.com/o/oauth2/auth",
    TokenURL:     "https://accounts.google.com/o/oauth2/token",
    TokenCache:   oauth.CacheFile("cache.json"), // キャッシュするファイル名を指定
}

// Uploads a file to Google Drive
func main() {
    t := &oauth.Transport{
        Config:    config,
        Transport: http.DefaultTransport,
    }

    // キャッシュがあったらブラウザ認証しない
    _, err := config.TokenCache.Token()
    if err != nil {
        // Generate a URL to visit for authorization.
        authUrl := config.AuthCodeURL("state")
        log.Printf("Go to the following link in your browser: %v\n", authUrl)

        // Read the code, and exchange it for a token.
        log.Printf("Enter verification code: ")
        var code string
        fmt.Scanln(&code)
        _, err := t.Exchange(code)
        if err != nil {
            log.Fatalf("An error occurred exchanging the code: %v\n", err)
        }
    }

    // Create a new authorized Drive client.
    svc, err := drive.New(t.Client())
    if err != nil {
        log.Fatalf("An error occurred creating Drive client: %v\n", err)
    }

    // Define the metadata for the file we are going to create.
    f := &drive.File{
        Title:       "My Document",
        Description: "My test document",
        MimeType:    "text/csv", // 追加
    }

    // CSV文字列
    nameList := `no, name
1, アブドゥル
2, イギー
3, 花京院
`
    csvReader := bytes.NewReader([]byte(nameList))

    // Make the API request to upload metadata and file data.
    r, err := svc.Files.Insert(f).Media(csvReader).Do()
    if err != nil {
        log.Fatalf("An error occurred uploading the document: %v\n", err)
    }
    log.Printf("Created: ID=%v, Title=%v\n", r.Id, r.Title)
}

同じように実行するとGDriveにCSVファイルがアップロードされているはずです。

「アプリで開く」からスプレッドシートを選ぶとちゃんと開けるはずです。

その他いろいろ

調査途中のこと、ハマってるとこ、情報求むなこと

公開範囲

アップロードするときに指定できるはずですが、まだ調べてません。Google Appsの場合、同じ会社のひとに公開できれば良いので最初からその公開範囲にしときたいですね。

File構造体のなかに Permission があるので指定できるはず。

drive - GoDoc

公式APIドキュメントだとこのへんか。

Share Files   |   Drive REST API   |   Google Developers

追記:googleapi - Google Drive APIでアップロードしたファイルの公開範囲を変更する - Qiita

サービスアカウント認証だとファイルが見えない

今回はふつうにOAuth2.0の認証情報を使いましたが、Developer Consoleだと「サービスアカウント」という認証のタイプがあります。こちらはプライベートキーを発行してアクセスするのでサーバサイドで完結するアプリにはちょうど良さそうに思えます。

それでいろいろ調べながらサンプルコードを改造してたんですが、アップロードは成功するのにブラウザ上からは見えないという謎の現象に遭遇しました。いろいろ検索してみたらつぎの質問を見つけました。

I can't see the files and folders created via code in my Google Drive - Stack Overflow

……よく分からないけど制限があるみたい。回避する方法はあるのかもしれないけど、OAuth2.0を使ったほうが早そうなので今回はこっちにしました。

サービスアカウント認証でもアップロードしてブラウザで見れた、そもそも認証タイプの使い方が違う、など情報知ってる方がいたらコメントくださいm(__)m

Convert(true)にするとCSVがGoogleドキュメントと紐付いてしまう

上記のコードでCSVアップロードをしたあと、Googleスプレッドシートで開くともうひとつのファイルが作られてしまいます。どうにかアップロード時に変換してくれないものかとMimeTypeをいろいろ試したのですが、そのときに遭遇した現象。

Convertはデフォルトだとfalseです。 MimeType: "text/csv" にして Convert(true) にすれば始めからスプレッドシートとしてファイルを作ってくれるかと思ったら、なぜかGoogleドキュメント(テキストファイル)として認識されてしまいました。「アプリで開く」を選ぶとスプレッドシートが出てきてくれません。

次のMimeTypeを試したんですが、全部Googleドキュメントに紐付いてしまいました。

  • text/csv
  • application/vnd.ms-excel
    • .xls
  • application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • .xlsx
  • application/vnd.google-apps.spreadsheet
    • Googleスプレッドシート

スプレッドシートで開いた画面のURLが欲しい

管理画面からこんな遷移でCSVが見れると素敵。

  • CSV出力ボタン
  • リンクが表示される or リダイレクト
  • Googleスプレッドシートの画面

ところがAPI経由で取れる情報ではGoogleスプレッドシートのURLが分からない(ダウンロードリンクは分かるが、そもそもダウンロードを無くしたいのだった)。ファイルのIDは分かるけど上記で書いた「スプレッドシートで開くと別ファイルが作られる」問題とのコンボで、そのIDからスプレッドシートのURLは分からない。

どうにも最後の一歩が欠けるかんじでイヤですね。

これも情報求む!です。

つづく(と思う)。

28
26
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
26