C#からGoogleスプレッドシートを操作してみる

# はじめに
本文書はC#からGoogleスプレッドシートを操作するための手順をまとめることを目的としています。

# Googleスプレッドシートを操作してみる

## やりたいこと
以下のようなウェブページの閲覧ログデータにおいて、ページごとのユニークユーザー数を算出するプログラムをC#で作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/042527c0-c027-3983-25be-1a2f58ca14e0.png)
以降で必要な手順を記載していきます。

## C#コードからGoogleスプレッドシートにアクセスするための準備
GoogleスプレッドシートにアクセスするためにはGoogleが提供するAPIを利用する必要があります。
ただ、このAPIアクセスを有効にするために事前にいくつが設定が必要です。
事前準備の詳細は[こちら](https://developers.google.com/workspace/guides/get-started?hl=ja)を参考にしてください。
以下では、実際に筆者が実施した手順を記載しています。

### Google Cloudでプロジェクトを作成する
まずは[こちら](https://console.cloud.google.com/projectcreate?hl=ja)からGoogle Cloud上にプロジェクトを作成します。
APIを利用するためにはプロジェクトを作成し、必要なAPIへのアクセスを有効にする必要があるため、この作業が必要になります。
以下の赤枠内でプロジェクト名を指定して、プロジェクトを作成します。
![u_00_プロジェクト作成画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/57bf7f42-eb5a-30ea-cc16-dda4743072af.png)

### Googleスプレッドシート用のAPIアクセスを有効にする
プロジェクト作成後は画面左上のナビゲーションメニューから[APIとサービス]->[有効なAPIとサービス]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/e79fee1e-e27a-7641-8a0b-14221f43f09b.png)
[APIとサービスの有効化]をクリックし、APIライブラリの検索画面を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/6abcf134-5f2e-cb47-b212-1058cbb578ee.png)
APIの検索テキストボックスに「Google Sheets」と入力し、検索し、「Google Sheets API」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/887acc50-716f-575d-5157-f572d122a3de.png)
「有効にする」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/9cae854d-d0c9-c7e2-b7ef-f4843bb87d6d.png)
画面左上のナビゲーションメニューから[APIとサービス]->[有効なAPIとサービス]し、API一覧から「Google Sheets API」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/0b892416-39f4-e49f-e778-6ffce6d2b4d7.png)
[認証情報]タブをクリックし、[認証情報を作成]->[サービスアカウント]を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/c4032a26-a880-6a08-33ad-d35bfdc13a49.png)
[サービスアカウント名]、[サービスアカウントID]、[サービスアカウントの説明]を入力し、[作成して続行]をクリックします。
![09_サービスアカウント作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/e21fd9d7-390e-1d61-efaf-9cbbbab78337.png)

ロールを「参照者」を設定して[続行]をクリックし、その後[完了]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/b3d56bf1-f664-7a9c-4523-28e1b05e7276.png)
サービスアカウント一覧で作成したサービスアカウントをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/0f454357-462a-9daf-e5e3-cf2182dae394.png)
[キー]タブをクリックし、[鍵を追加]->[新しい鍵を作成]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/96aaf66b-f22b-8891-6e29-a85d8c6bbc5d.png)
キーのタイプを「JSON」にして、[作成]をクリックします。
ローカルにjson形式のキーが保存されます。(このファイル内のキーは後々利用します)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/b02021a1-cf5f-ad90-0763-cbc7c68079fa.png)

### 操作したいスプレッドシートに権限を付与する
APIアクセスを有効にしたので、次に操作したいスプレッドシートにAPI経由でアクセスできる権限を設定します。
対象のスプレッドシートをWebブラウザで開き、[共有]をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/0fa84b0a-3870-1132-38cd-e26caa4f8045.png)
作成したサービスアカウントのメールアドレスを「ユーザーやグループを追加」テキストボックスに入力し、ロールを編集者にして「送信」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/71f0da63-d617-5fb1-1aad-f0fdaa2bc34b.png)

## C#でプログラムを作成する

### 必要なNuGetパッケージのインストール
C#でスプレッドシートを操作するためにはNuGetパッケージ「Google.Apis.Sheets.v4」を利用します。
VisualStudioでプロジェクト作成後に作成したプロジェクトをソリューションエクスプローラー上で右クリックし、[NuGetパッケージの管理]メニューを実行します。
表示されたNuGetパッケージマネージャーウィンドウから「Google.Apis.Sheets.v4」をインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/63031f88-5b83-1368-7fbe-3b8b562ea17e.png)

### プログラム作成
ようやく準備ができたので、以下のようなウェブページの閲覧ログデータにおいて、ページごとのユニークユーザー数を算出するためのプログラムを作成していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/f40fedba-6497-f1e0-d041-56d2f6b6d020.png)
以下がコードです。
```csharp
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Sheets.v4;

// [Googleスプレッドシート用のAPIアクセスを有効にする]の章でエクスポートしたJSON形式のAPIキーを使って認証を実行する
// 上記のJSONファイルを開き、スプレッドシートにアクセスするための認証情報を作成
var fileStream = new FileStream("エクスポートしたJSON形式のAPIキーへのファイルパス", FileMode.Open, FileAccess.Read);
var credential = GoogleCredential.FromStream(fileStream).CreateScoped(SheetsService.Scope.SpreadsheetsReadonly);
// 上記の認証情報を使って、スプレッドシートにアクセスするためのサービスを作成
var service = new SheetsService(new BaseClientService.Initializer(){HttpClientInitializer = credential});

// 対象とするシートのID
var sheetId = "対象とするシートのID";

// ページ名列の範囲
var pageNameColumnRange = "対象のシート名!B2:B21";

// ユーザーID列の範囲
var userIdColumnRange = "対象のシート名!C2:C21";

// ページ名一覧を取得
var pageNameValues = service.Spreadsheets.Values.Get(sheetId, pageNameColumnRange).Execute().Values;
var pageNames = pageNameValues.Select(pageNameValue => pageNameValue.First().ToString());

// ユーザーID一覧を取得
var userIdValues = service.Spreadsheets.Values.Get(sheetId, userIdColumnRange).Execute().Values;
var userIds = userIdValues.Select(userIdValue => userIdValue.First().ToString());

// ページごとのユニークID一覧を記録するディクショナリ
var uniqueUserIdsPerPage = new Dictionary<string, HashSet<string>>();

// ページごとのユニークIDを取得するためにページ名一覧とユーザー名一覧を1つのEnumerableにまとめる
var pageNameAndUserIds = pageNames.Zip(userIds, (pageName, userId) => new { pageName, userId });

// ページ名とユーザーIDの組み合わせ一覧に対して反復処理をして、ページ名とユニークIDを集計する
foreach (var pageNameAndUserId in pageNameAndUserIds)
{
	if (!uniqueUserIdsPerPage.ContainsKey(pageNameAndUserId.pageName))
	{
		// 該当のページに対応するユーザーIDが一件も追加されていない場合はユーザーIDリストを初期化
		uniqueUserIdsPerPage.Add(pageNameAndUserId.pageName, new HashSet<string>() { pageNameAndUserId.userId });
	}
	else
	{
		// 該当のページに対応するユーザーIDを追加
		// HashSetへの追加なので重複追加はされない
		uniqueUserIdsPerPage[pageNameAndUserId.pageName].Add(pageNameAndUserId.userId);
	}
}

// ページごとのユニークユーザー数を出力
foreach (var (pageName, users) in uniqueUserIdsPerPage)
{
	Console.WriteLine($"{pageName}:{users.Count}");
}
```
コードの説明をしていきます。
[Googleスプレッドシート用のAPIアクセスを有効にする]の章でエクスポートしたJSON形式のAPIキー(JSONファイル)を開き、そのファイルを使ってスプレッドシートにアクセスするための認証情報を作成します。
```csharp
// [Googleスプレッドシート用のAPIアクセスを有効にする]の章でエクスポートしたJSON形式のAPIキーを使って認証を実行する
// 上記のJSONファイルを開き、スプレッドシートにアクセスするための認証情報を作成
var fileStream = new FileStream("エクスポートしたJSON形式のAPIキーへのファイルパス", FileMode.Open, FileAccess.Read);
var credential = GoogleCredential.FromStream(fileStream).CreateScoped(SheetsService.Scope.SpreadsheetsReadonly);
```
上記の認証情報を利用してスプレッドシートにアクセスするためのサービスを作成します。
```csharp
// 上記の認証情報を使って、スプレッドシートにアクセスするためのサービスを作成
var service = new SheetsService(new BaseClientService.Initializer(){HttpClientInitializer = credential});
```
以下のスプレッドシートのIDは、スプレッドシートをWebブラウザで表示した際のURLの以下の赤枠内の部分の値を記入します。
```csharp
// 対象とするシートのID
var sheetId = "対象とするシートのID";
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/452292/507ff139-41e5-195e-3f10-9c8d345fb5bf.png)
スプレッドシート内のシート名とセルの範囲を文字列で指定して、作成済みのサービスオブジェクト経由でページ名とユーザーID一覧を取得します。
``` csharp
// ページ名列の範囲
var pageNameColumnRange = "対象のシート名!B2:B21";

// ユーザーID列の範囲
var userIdColumnRange = "対象のシート名!C2:C21";

// ページ名一覧を取得
var pageNameValues = service.Spreadsheets.Values.Get(sheetId, pageNameColumnRange).Execute().Values;
var pageNames = pageNameValues.Select(pageNameValue => pageNameValue.First().ToString());

// ユーザーID一覧を取得
var userIdValues = service.Spreadsheets.Values.Get(sheetId, userIdColumnRange).Execute().Values;
var userIds = userIdValues.Select(userIdValue => userIdValue.First().ToString());
```
以降の部分では、上記までで取得したページ名一覧とユーザーID一覧を使ってページ名とユーザーIDを組み合わせて反復処理します。(スプレッドシートの各行ごとに処理をするイメージです。)
ページごとのユーザーIDの一覧をユーザーIDが重複しないように作成することで、最終的にページ名ごとのユニークなユーザー数(重複しないユーザーIDの数)を出力しています。
```csharp
// ページごとのユニークID一覧を記録するディクショナリ
var uniqueUserIdsPerPage = new Dictionary<string, HashSet<string>>();

// ページごとのユニークIDを取得するためにページ名一覧とユーザー名一覧を1つのEnumerableにまとめる
var pageNameAndUserIds = pageNames.Zip(userIds, (pageName, userId) => new { pageName, userId });

// ページ名とユーザーIDの組み合わせ一覧に対して反復処理をして、ページ名とユニークIDを集計する
foreach (var pageNameAndUserId in pageNameAndUserIds)
{
	if (!uniqueUserIdsPerPage.ContainsKey(pageNameAndUserId.pageName))
	{
		// 該当のページに対応するユーザーIDが一件も追加されていない場合はユーザーIDリストを初期化
		uniqueUserIdsPerPage.Add(pageNameAndUserId.pageName, new HashSet<string>() { pageNameAndUserId.userId });
	}
	else
	{
		// 該当のページに対応するユーザーIDを追加
		// HashSetへの追加なので重複追加はされない
		uniqueUserIdsPerPage[pageNameAndUserId.pageName].Add(pageNameAndUserId.userId);
	}
}

// ページごとのユニークユーザー数を出力
foreach (var (pageName, users) in uniqueUserIdsPerPage)
{
	Console.WriteLine($"{pageName}:{users.Count}");
}

```

# まとめ
本記事ではC#からGoogleスプレッドシートを操作するための手順をまとめました。
事前準備は必要ですが、それさえしてしまえば筆者が以前記事にしたCSVデータをプログラムで操作するときと大差ないプログラムで操作ができるため、ぜひ活用したいと思いました。(以前の記事は[こちら。](https://qiita.com/tat_tt/items/8c4647e8ef49076232eb))