將Google Fit的每日步數,體重和距離導出到Google表格

最近接觸到一個概念被稱為量化自我,主要就是將自己生活中的行為記錄下來,並且記錄成數據,每年或是定期進行數據分析,能夠調整自己整年的步伐,讓自己成為更好的人,詳細內容可能會放在另外一期的文章,然而我卻遇到了一個問題,像是GOOGLE FIT 或是地圖這樣的城市能夠很好的記錄下來我們日常的足跡等數據,但是卻無法很好的匯出,讓它成為一個數據分析的工具,因此我求助萬能得谷歌搜尋,找到了解決方法,利用GOOGLE SHEET當中內建的小程序,搭配一些API讓他能夠每天同步我GOOGLE FIT的數據, 至於地圖的檔案要如何匯出我還在思考。可能之後文章會提到。 下列文章是我翻譯並且稍微修改網路上的作者如何解決這個方案


Google Fit是跟踪日常步數的好方法,而無需攜帶Fitbit或其他專用跟踪器。 不過,要獲取這些數據並不容易,據我所知,唯一的方法是Google Takeout,它不是為自動化而設計的。 幸運的是,這裡有一個API,您幾乎可以使用Google表格進行任何操作。

 如果您想導出步數,體重和距離,則此帖子將為您提供所需的一切,只需按照以下說明操作,即可啟動並運行電子表格。 這也是將OAuth2與Google Apps腳本一起使用的良好入門,應該成為更複雜的Google Fit集成的一個不錯的起點。 如果您有任何疑問或反饋,請在下面留下評論。

 首先,您需要一個Google表格,該表格所附的應用腳本項目和一個Google API項目,該項目將提供對Fitness API的訪問權限。 這聽起來可能令人生畏,但只需幾分鐘就可以啟動並運行所有內容。

 在Google雲端硬盤中創建一個新的電子表格,然後隨意調用它。 將第一個標籤重命名為“指標”。 在單元格A1中輸入“日期”,在B1中輸入“步長”,在C1中輸入“重量”,在D1中輸入“距離”。 要同時獲取歷史記錄,請使用相同的標題創建另一個名為“歷史記錄”的標籤。 接下來,從“工具”菜單中選擇“腳本編輯器...”,這將打開一個新的應用程序腳本項目。

 為apps腳本項目命名,然後從“資源”菜單中選擇“庫...”。 在“添加庫”旁邊,輸入1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF並單擊添加。 這將找到Google OAuth2庫。 選擇最新版本(在撰寫本文時為24),然後單擊“保存”。 然後從“文件”菜單中選擇“項目屬性”,並記下腳本ID(一連串的字母和數字)。

 打開Google API控制台。 創建一個新項目,並將其命名為“ Google Fit Sheet”。 在控制台中,單擊“啟用API和服務”,然後找到並選擇Fitness API。 然後轉到密鑰並創建OAuth客戶端ID。 系統會要求您創建一個同意屏幕,您唯一需要輸入的字段就是產品名稱(即“我的健身應用”)。 然後選擇“ Web應用程序”作為應用程序類型。 您需要設置名稱和授權的重定向URL。 重定向URL為https://script.google.com/macros/d/{SCRIPTID}/usercallback,將{SCRIPTID}替換為您上面記下的實際腳本ID。 添加後,記下客戶端ID和客戶端密鑰。

 返回apps腳本項目,並將下面的代碼粘貼到Code.gs窗口中:
以下是程式碼
// add your Google API Project OAuth client ID and client secret here
var ClientID = '';
var ClientSecret = '';

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Google Fit')
      .addItem('Authorize if needed (does nothing if already authorized)', 'showSidebar')
      .addItem('Get Metrics for Yesterday', 'getMetrics')
      .addItem('Get Metrics for past 60 days', 'getHistory')
      .addItem('Reset Settings', 'clearProps')
      .addToUi();
}

function getMetrics() {
  getMetricsForDays(1, 1, 'Metrics');
}

function getHistory() {
  getMetricsForDays(1, 60, 'History');
}

// see step count example at https://developers.google.com/fit/scenarios/read-daily-step-total
// adapted below to handle multiple metrics (steps, weight, distance), only logged if present for day
function getMetricsForDays(fromDaysAgo, toDaysAgo, tabName) {
  var start = new Date();
  start.setHours(0,0,0,0);
  start.setDate(start.getDate() - toDaysAgo);

  var end = new Date();
  end.setHours(23,59,59,999);
  end.setDate(end.getDate() - fromDaysAgo);
  
  var fitService = getFitService();
  
  var request = {
    "aggregateBy": [
      {
        "dataTypeName": "com.google.step_count.delta",
        "dataSourceId": "derived:com.google.step_count.delta:com.google.android.gms:estimated_steps"
      },
      {
        "dataTypeName": "com.google.weight.summary",
        "dataSourceId": "derived:com.google.weight:com.google.android.gms:merge_weight"
      },
      {
        "dataTypeName": "com.google.distance.delta",
        "dataSourceId": "derived:com.google.distance.delta:com.google.android.gms:merge_distance_delta"
      }
    ],
    "bucketByTime": { "durationMillis": 86400000 },
    "startTimeMillis": start.getTime(),
    "endTimeMillis": end.getTime()
  };
  
  var response = UrlFetchApp.fetch('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', {
    headers: {
      Authorization: 'Bearer ' + fitService.getAccessToken()
    },
    'method' : 'post',
    'contentType' : 'application/json',
    'payload' : JSON.stringify(request, null, 2)
  });
  
  var json = JSON.parse(response.getContentText());
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName(tabName);
  
  for(var b = 0; b < json.bucket.length; b++) {
    // each bucket in our response should be a day
    var bucketDate = new Date(parseInt(json.bucket[b].startTimeMillis, 10));
    
    var steps = -1;
    var weight = -1;
    var distance = -1;
    
    if (json.bucket[b].dataset[0].point.length > 0) {
      steps = json.bucket[b].dataset[0].point[0].value[0].intVal;
    }
    
    if (json.bucket[b].dataset[1].point.length > 0) {
      weight = json.bucket[b].dataset[1].point[0].value[0].fpVal;
    }
    
    if (json.bucket[b].dataset[2].point.length > 0) {
      distance = json.bucket[b].dataset[2].point[0].value[0].fpVal;
    }
    
    sheet.appendRow([bucketDate, 
                     steps == -1 ? ' ' : steps, 
                     weight == -1 ? ' ' : weight, 
                     distance == -1 ? ' ' : distance]);
  }
}

// functions below adapted from Google OAuth example at https://github.com/googlesamples/apps-script-oauth2

function getFitService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('fit')

      // Set the endpoint URLs, which are the same for all Google services.
      .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')

      // Set the client ID and secret, from the Google Developers Console.
      .setClientId(ClientID)
      .setClientSecret(ClientSecret)

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.
      .setCallbackFunction('authCallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())

      // Set the scopes to request (space-separated for Google services).
      // see https://developers.google.com/fit/rest/v1/authorization for a list of Google Fit scopes
      .setScope('https://www.googleapis.com/auth/fitness.activity.read https://www.googleapis.com/auth/fitness.body.read https://www.googleapis.com/auth/fitness.location.read')

      // Below are Google-specific OAuth2 parameters.

      // Sets the login hint, which will prevent the account chooser screen
      // from being shown to users logged in with multiple accounts.
      .setParam('login_hint', Session.getActiveUser().getEmail())

      // Requests offline access.
      .setParam('access_type', 'offline')

      // Forces the approval prompt every time. This is useful for testing,
      // but not desirable in a production application.
      //.setParam('approval_prompt', 'force');
}

function showSidebar() {
  var fitService = getFitService();
  if (!fitService.hasAccess()) {
    var authorizationUrl = fitService.getAuthorizationUrl();
    var template = HtmlService.createTemplate(
        '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
        'Close this after you have finished.');
    template.authorizationUrl = authorizationUrl;
    var page = template.evaluate();
    SpreadsheetApp.getUi().showSidebar(page);
  } else {
  // ...
  }
}

function authCallback(request) {
  var fitService = getFitService();
  var isAuthorized = fitService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}

function clearProps() {
  PropertiesService.getUserProperties().deleteAllProperties();
}



在代碼頂部,有空格可以從API控制台輸入客戶端ID和客戶端密鑰。 輸入這些並保存項目。

 切換回您的Google表格並重新加載。 重新加載後,將出現一個Google Fit菜單項。 首先選擇“授權...”,您將看到一個屏幕以授權腳本,然後是帶有鏈接的側欄。 點擊鏈接以授權腳本訪問您的Google Fit數據。 然後,您可以關閉側邊欄,然後從Google Fit菜單中選擇“獲取昨天的指標”。 您應該看到帶有昨天的日期和適應性數據的新行已添加到電子表格中。

 最後一步是自動提取數據。 返回apps腳本項目,然後從“編輯”菜單中選擇“當前項目”的觸發器。 添加觸發器以將getMetrics()作為時間驅動的日計時器運行-我建議在凌晨5點到6點之間。 如果發生任何問題,例如Google Fit授權到期(在這種情況下,您只需再次返回並從Google Fit菜單進行授權),您還可以單擊通知以添加電子郵件警報。

 至此,您已經準備就緒。 每天,電子表格都會從前一天開始自動更新您的步數。 您可以添加圖表,移動平均線,導出到其他系統,增加體重或BMI等。我想在此博客的某處添加7天移動平均步數作為半公開的激勵工具...觀看此空間 。

 請注意,在幾天之內,沒有重量數據的重量在電子表格中將為空白。  Google Fit不返回最近的已知重量,僅返回記錄更新的天的已知值。

 如果您希望將此示例擴展到其他數據類型,那麼此API資源管理器頁面對於查找API文檔未列出的數據類型非常有幫助。

 幾次使用此腳本,我的授權都處於錯誤狀態,並開始從API收到400錯誤響應。 如果發生這種情況,請運行您的Google Fit應用,單擊底部的“個人資料”圖標,然後單擊右上方的“設置”圖標。 點擊管理已連接的應用,然後從Google Fit斷開腳本。 最後,從工作表的菜單中運行“重置設置”選項,然後再次授權。

 我於2019年1月21日更新了這篇文章,以擴展示例以處理重量和距離以及台階。 我還改進了歷史記錄功能,可以在一個API調用中處理很多天,而不是我之前添加的一次快速破解,一次只能一天完成一次。 我建議使用上面的代碼,而不要使用下面的註釋中的任何內容(至少是此更新之前的註釋)。

留言