0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Google Sheets APIでシートの最終行を取得(Nodejs)

Last updated at Posted at 2022-01-13

結論

GASのgetLastRowのようなメソッドは無いので、
spreadsheets.values.append で空の行を追加し戻り値から最終行を取得すればOKです。

セットアップ

GCPSheets API を有効化
Credentials ページで OAuth Client ID を作成(Application Typeは Desktop app を選択)し、client_secret ファイルを credentials.json という名前で保存
③ google api パッケージをインストール

npm install googleapis@39 --save

コード

下記のファイルをコピペして、credentials.jsonと同じ場所に保存し実行してください。
※SPREADSHEET_ID と SHEET_NAME はご自身のものに変更してください)
※認証部分のコードは Google公式のquickstart と全く同じです。

getLastRow.js
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

//最終行を取得したいシートの名前とスプレッドシートIDを指定=====

const SPREADSHEET_ID = '*************************';
const SHEET_NAME = 'シート1';

//====================================================

const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
const CLIENT_SECRET_PATH = 'credentials.json';
const TOKEN_PATH = 'token.json'; // トークンファイル名(トークンファイルは初めてこのファイルを実行する際に自動生成される)


fs.readFile(CLIENT_SECRET_PATH, (err, content) => {
    if (err) return console.log('Error loading client secret file:', err);
    authorize(JSON.parse(content), getLastRow);
});

/**
認証関数 既に認証されていればcallbackのみ実行
 */
function authorize(credentials, callback) {
    const { client_secret, client_id, redirect_uris } = credentials.installed;
    const oAuth2Client = new google.auth.OAuth2(
        client_id, client_secret, redirect_uris[0]);

    // Check if we have previously stored a token.
    fs.readFile(TOKEN_PATH, (err, token) => {
        if (err) return getNewToken(oAuth2Client, callback);
        oAuth2Client.setCredentials(JSON.parse(token));
        callback(oAuth2Client);
    });
}

/**
トークン生成関数 既にtokenファイルがあればcallbackのみ実行
 */
function getNewToken(oAuth2Client, callback) {
    const authUrl = oAuth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: SCOPES,
    });
    console.log('Authorize this app by visiting this url:', authUrl);
    const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
    });
    rl.question('Enter the code from that page here: ', (code) => {
        rl.close();
        oAuth2Client.getToken(code, (err, token) => {
            if (err) return console.error('Error while trying to retrieve access token', err);
            oAuth2Client.setCredentials(token);
            // Store the token to disk for later program executions
            fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                if (err) return console.error(err);
                console.log('Token stored to', TOKEN_PATH);
            });
            callback(oAuth2Client);
        });
    });
}

/**
最終行を取得する関数
 */
async function getLastRow(auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const lastRow = await new Promise((resolve, reject) => {
        sheets.spreadsheets.values.append({
            spreadsheetId: SPREADSHEET_ID,
            range: `${SHEET_NAME}!A1`,
            valueInputOption: 'USER_ENTERED',
            insertDataOption: 'INSERT_ROWS',
            resource: {
                values: [
                    [],
                ],
            }
        }, (err, result) => {
            if (err) {
                // Handle error.
                console.log(err);
            } else {
                //result.data.tableRangeが無い = シートのどのセルにも値が入力されていないので、0を返す
                if(!result.data.tableRange) return resolve(0);
                const match = result.data.tableRange.match(/^.*![A-Z]+\d+:[A-Z]+(?<lastRow>\d+)$/);
                if(match){
                    resolve(match.groups.lastRow);
                } else {
                    //1つのセルのみ入力されているシートの場合は上記の正規表現ではエラーになる
                    const match_only1cell = result.data.tableRange.match(/^.*![A-Z]+(?<lastRow>\d+)$/);
                    resolve(match_only1cell.groups.lastRow);
                }
            }
        })
    });
    
    //最終行を出力
    console.log(`lastRow: ${lastRow}`);
}

解説

sheets.spreadsheets.values.append の result.data は下記のようになっています。
tableRangeが空行追加前の既存データ範囲なので、ここの範囲の末尾を正規表現で抜き出すことで最終行を取得できます。
image.png

おまけ GetValues・SetValuesも搭載したクラス

GoogleSheetsClass.js
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');

class GoogleSheetsClass {
    constructor() {
        this.SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
        this.sheets = null;
        //認証しsheetsオブジェクトを取得しておく
        fs.readFile(CLIENT_SECRET_PATH, (err, content) => {
            if (err) return console.log('Error loading client secret file:', err);
            this.authorize(JSON.parse(content), auth => {
                this.sheets = google.sheets({ version: 'v4', auth });
            });
        });
    }

    /**
    認証関数 既に認証されていればcallbackのみ実行
    */
    authorize(credentials, callback) {
        const { client_secret, client_id, redirect_uris } = credentials.installed;
        const oAuth2Client = new google.auth.OAuth2(
            client_id, client_secret, redirect_uris[0]);

        // Check if we have previously stored a token.
        fs.readFile(TOKEN_PATH, (err, token) => {
            if (err) return this.getNewToken(oAuth2Client, callback);
            oAuth2Client.setCredentials(JSON.parse(token));
            callback(oAuth2Client);
        });
    }

    /**
    トークン生成関数 既にtokenファイルがあればcallbackのみ実行
    */
    getNewToken(oAuth2Client, callback) {
        const authUrl = oAuth2Client.generateAuthUrl({
            access_type: 'offline',
            scope: this.SCOPES,
        });
        console.log('Authorize this app by visiting this url:', authUrl);
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout,
        });
        rl.question('Enter the code from that page here: ', (code) => {
            rl.close();
            oAuth2Client.getToken(code, (err, token) => {
                if (err) return console.error('Error while trying to retrieve access token', err);
                oAuth2Client.setCredentials(token);
                // Store the token to disk for later program executions
                fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
                    if (err) return console.error(err);
                    console.log('Token stored to', TOKEN_PATH);
                });
                callback(oAuth2Client);
            });
        });
    }

    //getLastRow
    async getLastRow(spreadsheet_id, sheet_name) {
        return new Promise((resolve, reject) => {
            this.sheets.spreadsheets.values.append({
                spreadsheetId: spreadsheet_id,
                range: `'${sheet_name}'!A1`,
                valueInputOption: 'USER_ENTERED',
                insertDataOption: 'INSERT_ROWS',
                resource: {
                    values: [
                        [],
                    ],
                }
            }, (err, result) => {
                if (err) {
                    // Handle error.
                    throw err;
                } else {
                    //result.data.tableRangeが無い = シートのどのセルにも値が入力されていないので、0を返す
                    if(!result.data.tableRange) return resolve(0);
                    const match = result.data.tableRange.match(/^.*![A-Z]+\d+:[A-Z]+(?<lastRow>\d+)$/);
                    if(match){
                        resolve(match.groups.lastRow);
                    } else {
                        //1つのセルのみ入力されているシートの場合は上記の正規表現ではエラーになる
                        const match_only1cell = result.data.tableRange.match(/^.*![A-Z]+(?<lastRow>\d+)$/);
                        resolve(match_only1cell.groups.lastRow);
                    }
                }
            })
        });
    }
    
    /*
    ** getValues
    ** rangeは"A1:B2"形式で指定
    */
    async getValues(spreadsheet_id, sheet_name, range){
        return new Promise((resolve, reject) => {
            this.sheets.spreadsheets.values.get({
                spreadsheetId: spreadsheet_id,
                range: `'${sheet_name}'!${range}`,
            }, (err, result) => {
                if (err) { throw err; } else {
                    resolve(result.data.values);
                }
            });
        });
    }

    /*
    ** setValues
    ** rangeは"A1:B2"形式、 valuesは二次元配列を指定
    */
    async setValues(spreadsheet_id, sheet_name, range, values){
        return new Promise((resolve, reject) => {
            this.sheets.spreadsheets.values.update({
                spreadsheetId: spreadsheet_id,
                range: `'${sheet_name}'!${range}`,
                valueInputOption: "USER_ENTERED",
                resource : {
                    values : values //[[]]
                }
            }, (err, result) => {
                if (err) { throw err; } else {
                    resolve(result.data);
                }
            });
        });
    }
}

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?