如果說爬蟲是在鍛鍊你分析網頁的能力,那麼 Google Sheets 就是在教你閱讀官方文件的技巧
接下來幾天的目標是將之前的爬蟲資料放入 Google Sheets,實踐上會分成幾個步驟:
使用 Google api 時你要煩惱的不是沒有功能,而是你找不到功能在哪裡
,因為這份 api 文件實在是太龐大了,對新手來說非常容易在文件中迷路...
所以今天的文章重點在如何從官方文件找出需要的資源
,希望能幫助到剛接觸巨型文件的朋友們
1.1 在官方文件尋找 讀取Sheet 的範例
1.2 調整範例以符合專案需求:getSheets
getFBIGSheet
3.1 在官方文件尋找 新增Sheet 的範例
3.2 調整範例以符合專案需求:addSheets
4.1 建立外部函式模組:updateGoogleSheets
4.2 將取得 auth(認證)的步驟改寫為函式:getAuth
updateGoogleSheets
建議你所需要的功能都先從
有範例的文件
找起,因為沒範例的文件你還要自己瞎猜程式結構
讀取Sheet
的範例先觀察大標題的描述
(可用關鍵字 sheet 搜尋來輔助)
標題 Sheet Operations 的描述上比較符合我們需求,因為我們要
找出 spreadsheet 底下的 sheet 資訊
一樣先從標題下手
標題 Determine sheet ID and other properties 跟
sheet 的詳細資訊
有關
第一段給了一個連結,並說
這個方法能取得特定 spreadsheet 底下 sheet 的屬性
- 首先我們看到
Google 非常貼心提供線上測試 api 的地方
- 進入測試 api 全螢幕模式後,
填寫自己的 spreadsheetId、憑證權限
接著按下 EXCUTE 即可在右下角看到 api 的回傳資訊
- 打開自己的 Google Sheets 來與 api 的回傳資訊做對照
- 從回傳的 json 我們就能知道所需的資訊被放在
sheets
這個陣列中,並且可用sheets[].properties.title
這個參數判斷 'FB粉專'、'IG帳號' 的 Sheet 是否已經建立
getSheets
這個函式的目標是 取得指定 spreadsheetId 下 sheets 資訊
,根據上面 api 回傳的 json 結構分析,我們所需要的資料就在response.sheets
裡面
async function getSheets (auth) {//取得Google Sheets所有的sheet
const sheets = google.sheets({ version: 'v4', auth });
const request = {
spreadsheetId: process.env.SPREADSHEET_ID,
includeGridData: false,
}
try {
let response = (await sheets.spreadsheets.get(request)).data;
const sheets_info = response.sheets
return sheets_info
} catch (err) {
console.error(err);
}
}
getFBIGSheet
這隻函式是今天功能的核心,我們將取得 FB粉專、IG帳號的 sheet 資訊
分成幾個步驟
getSheets
getSheets
回傳的資料裡面
addSheet
async function getFBIGSheet (auth) {// 取得FB粉專、IG帳號的Sheet資訊
const sheets = [//我們Google Sheets需要的sheet
{ title: 'FB粉專', id: null },
{ title: 'IG帳號', id: null }
]
const online_sheets = await getSheets(auth)//抓目前存在的sheet
for (sheet of sheets) {
online_sheets.forEach(online_sheet => {
if (sheet.title == online_sheet.properties.title) {
// 如果線上已經存在相同的sheet title就直接使用相同id
sheet.id = online_sheet.properties.sheetId
}
})
if (sheet.id == null) {//如果該sheet尚未被建立,則建立
console.log(sheet.title + ':not exsit')
try {
sheet.id = await addSheet(sheet.title, auth)
} catch (e) {
console.error(e)
}
}
}
return sheets;
}
新增Sheet
的範例讀取Sheet的經驗
可以知道想要操作 Sheet 就要在Sheet Operations這個頁面尋找資源
標題 Add a sheet 超級直觀,就是他惹!
- 第一段提供一個連結,並說
這方法能新增 sheet 到 spreadsheet
- 下方提供了 request 的範例,讓我們能大概了解
新增 Sheet 可以調整哪些參數
- 打開 Try this API(在最下方),
Request body 請依下圖設定
,title 的內容可以自行調整
- 按下 EXCUTE 按鈕後,確認自己的 Google Sheets
真的成功新增一個 title 為 test1 的 Sheet
- 從回傳資料中找出 sheetId 的位置:
replies[0].addSheet.properties.sheetId
addSheets
這個函式的目標是 用提供的 title 建立一個新 Sheet
,Google Sheets 在新增一個 Sheet 時會自動產生一個不重複的 sheetId,我們將 sheetId 回傳代表我們新增成功
(sheetId 在後面的章節會使用到)
async function addSheet (title, auth) {//新增一個sheet到指定的Google Sheets
const sheets = google.sheets({ version: 'v4', auth });
const request = {
// The ID of the spreadsheet
"spreadsheetId": process.env.SPREADSHEET_ID,
"resource": {
"requests": [{
"addSheet": {//這個request的任務是addSheet
// 你想給這個sheet的屬性
"properties": {
"title": title
}
},
}]
}
};
try {
const response = (await sheets.spreadsheets.batchUpdate(request)).data;
const sheetId = response.replies[0].addSheet.properties.sheetId
console.log('added sheet:' + title)
return sheetId
}
catch (err) {
console.log('The API returned an error: ' + err);
}
}
updateGoogleSheets
別忘記我們學習 Google Sheets 是為了寫入爬蟲的資料,所以要建立一個給主程式用來更新 Google Sheets 的外部函式模組
,這個函式今天有兩個任務:
getAuth
getFBIGSheet
//讓其他程式在引入時可以使用這個函式
exports.updateGoogleSheets = updateGoogleSheets;
async function updateGoogleSheets () {
try {
const auth = await getAuth()
let sheets = await getFBIGSheet(auth)//取得線上FB、IG的sheet資訊
console.log(sheets)
} catch (err) {
console.error('更新Google Sheets失敗');
console.error(err);
}
}
getAuth
考慮到 Google Sheets 的 api 都需要通過憑證取得授權才能操作,所以我把這個步驟獨立成一個函式
,由於取得授權這塊採用 callback 的函式,所以我們要用 Promise 的方式來處理。
function getAuth () {
return new Promise((resolve, reject) => {
try {
const content = JSON.parse(fs.readFileSync('tools/google_sheets/credentials.json'))
authorize(content, auth => {
resolve(auth)
})
} catch (err) {
console.error('憑證錯誤');
reject(err)
}
})
}
updateGoogleSheets
要爬蟲完成後才會把資料寫入 Google Sheet,所以將 updateGoogleSheets 這個函式放在最後執行
為了方便測試今天的功能先把爬蟲的功能暫時註解
require('dotenv').config(); //載入.env環境檔
const { initDrive } = require("./tools/initDrive.js");
const { crawlerFB } = require("./tools/crawlerFB.js");
const { crawlerIG } = require("./tools/crawlerIG.js");
const { updateGoogleSheets } = require("./tools/google_sheets");
async function crawler () {
// const driver = await initDrive();
// if (!driver) {//driver不存在就結束程式
// return
// }
// //因為有些人是用FB帳號登入IG,為了避免增加FB登出的動作,所以採取先對IG進行爬蟲
// await crawlerIG(driver)
// await crawlerFB(driver)
// driver.quit();
//處理Google Sheets相關動作
await updateGoogleSheets()
}
crawler()
yarn start
你會遇到如下的錯誤:
因為 Google Sheets 權限不足(原本只有readonly)
,所以我們要重新申請 token
修改憑證的執行權限
// 原本的範本是有readonly,這樣只有讀取權限,將它拿掉後才擁有修改的權限
// const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
yarn start
yarn start
,只會回傳建立好的 sheetId 給你,不會再重新建立callback、Promise 和 async/await 那些事兒
我在 Medium 平台 也分享了許多技術文章
❝ 主題涵蓋「MIS & DEVOPS、資料庫、前端、後端、MICROSFT 365、GOOGLE 雲端應用、個人研究」希望可以幫助遇到相同問題、想自我成長的人。❞
在許多人的幫助下,本系列文章已出版成書,並添加了新的篇章與細節補充:
- 加入更多實務經驗,用完整的開發流程讓讀者了解專案每個階段要注意的事項
- 將爬蟲的步驟與技巧做更詳細的說明,讓讀者可以輕鬆入門
- 調整專案架構
- 優化爬蟲程式,以更廣的視角來擷取網頁資訊
- 增加資料驗證、錯誤通知等功能,讓爬蟲執行遇到問題時可以第一時間通知使用者
- 排程部分改用 node-schedule & pm2 的組合,讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊
有興趣的朋友可以到天瓏書局選購,感謝大家的支持。
購書連結:https://www.tenlong.com.tw/products/9789864348008