動機
管理画面からホゲホゲの集計データを出力するためにCSVを作ってダウンロードする処理を作ることがよくありますが、CSVって罠が多いです(Shift_JISとか改行コードとか)。
そういうノウハウを駆使してCSVダウンロード機能を作るのにちょっと飽きたので他の方法を集計データを出力できないか考えてみました。
Google Appsを使っているなら社内データもGoogleに上げてOKです。そもそもOfficeを使ってなくてAppsを使ってる場合もあるので、はじめからGoogle Driveにファイルを置いとけば仕事の流れ的に便利なんじゃないかと思います。
だからこの記事ではCSVをGoogle Driveに上げる方法を検討してみます。
言語は個人的趣味によりGo言語を使います(他の言語でも同様のはず)。
Google Drive APIを試す
公式ドキュメントのQuickStartにチュートリアルとサンプルコードがあるので、まずはこれをそのままやってみます。
ただし以下の箇所がサンプルコードと食い違ってるので注意。
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のパッケージが違います。そのままだと動かないので修正したコードを貼っときます。
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回はブラウザ起動する必要がありますが、次回から有効期限が過ぎてたら自動で更新してくれるようになります。
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 です。
書き換えたコードはこちら。
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
があるので指定できるはず。
公式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は分からない。
どうにも最後の一歩が欠けるかんじでイヤですね。
これも情報求む!です。
つづく(と思う)。