爬蟲是一個技術,他將網頁的數據收集下來
Google Sheets 是一個容器,他可以儲存資料並將資料以不同面向做展示爬蟲 X Google Sheets = 你要學習的技術整合
 今日目標
 今日目標crawlerIG、crawler這兩隻函式,讓他們依照我們想要格式回傳爬蟲資料1.1 分析 Google Sheets 上要呈現的資料、寫入時可能遇到的問題
1.2 組合回傳的資訊:以 crawlerFB 為範例
updateGoogleSheets2.1 用主程式傳遞爬蟲資料
2.2 改寫 updateGoogleSheets 函式來接收並處理爬蟲資料
3.1 分析寫入 Google Sheets 的步驟:writeSheet
3.2 在官方文件尋找 寫入Sheet 的範例
3.3 第一欄寫入 title(粉專名稱):writeTitle
3.4 取得 Sheet 最後一欄的空白欄位:getLastCol
3.5 將 trace(追蹤人數)資訊寫入最後一欄:writeTrace
crawlerIG、crawler這兩隻函式,讓他們依照我們想要格式回傳爬蟲資料過去我們只將 FB粉專、IG粉專的資訊用 console.log 輸出,現在我們要把這些資料有結構的存入 json 物件方便後續使用
粉專網址是唯一值,所以使用粉專名稱+粉專網址作為key就能解決這個問題,同時也方便使用者可以點擊連結直接前往粉專crawlerFB 為範例無論這個粉專爬蟲成功或是失敗我們都要記錄他的數據
finally 代表在 try-catch 結束後會執行的任務,忘記的可以回到Day14 try-catch-finally 基礎語法說明複習喔
async function crawlerFB (driver) {
    const isLogin = await loginFacebook(driver)
    if (isLogin) {
        console.log(`FB開始爬蟲`)
        let result_array = []
        for (fanpage of fanpage_array) {
            let trace = null
            try {
                const isGoFansPage = await goFansPage(driver, fanpage.url)
                if (isGoFansPage) {
                    await driver.sleep((Math.floor(Math.random() * 4) + 3) * 1000)
                    trace = await getTrace(driver, By, until)
                }
                if (trace === null) {
                    console.log(`${fanpage.title}無法抓取追蹤人數`)
                } else {
                    console.log(`${fanpage.title}追蹤人數:${trace}`)
                }
            } catch (e) {
                console.error(e);
                continue;
            } finally {// 將粉專的資訊塞入物件
                result_array.push({
                    url: fanpage.url,
                    title: fanpage.title,
                    trace: trace
                })
            }
        }
        // 回傳FB粉專爬蟲資料
        return result_array
    }
}
updateGoogleSheets主程式傳遞爬蟲資料主程式將 crawlerIG、crawlerFB 回傳的爬蟲資訊提供給 updateGoogleSheets 當參數
async function crawler () {
    const driver = await initDrive();
    if (!driver) {
        return
    }
    //取得回傳的爬蟲資料
    const ig_result_array = await crawlerIG(driver)
    const fb_result_array = await crawlerFB(driver)
    driver.quit();
    //將回傳的爬蟲資料傳遞給Google Sheets處理
    await updateGoogleSheets(ig_result_array, fb_result_array)
}
updateGoogleSheets 函式來接收並處理爬蟲資料writeSheet 將收到的爬蟲資料寫入對應的 Sheetasync function updateGoogleSheets (ig_result_array, fb_result_array) {
    try {
        const auth = await getAuth()
        let sheets = await getFBIGSheet(auth)
        console.log('FB、IG Sheet資訊:')
        console.log(sheets)
        // 將爬蟲資料寫入各自的Sheet
        await writeSheet('FB粉專', fb_result_array, auth)
        await writeSheet('IG帳號', ig_result_array, auth)
        console.log(`成功更新Google Sheets:https://docs.google.com/spreadsheets/d/${process.env.SPREADSHEET_ID}`);
    } catch (err) {
        console.error('更新Google Sheets失敗');
        console.error(err);
    }
}
writeSheet目標:希望寫入的 Google Sheets 長得像下圖:
我們先將想要完成的功能羅列出來,再一個個去實現:
粉專名稱+粉專網址 組合的 HYPERLINK,每次執行爬蟲時我們會用 writeTitle 這個函式來更新第一欄的資料getLastCol 來取得 Sheet 最後一欄的空白欄位writeTrace 把爬蟲下來的追蹤人數寫入就完成嚕實作:將上述要完成的功能各自撰寫成函式後,由 writeSheet 這個函式來管理寫入 Google Sheets 的步驟
async function writeSheet (title, result_array, auth) {
    // title可以直接超連結到粉專
    let title_array = result_array.map(fanpage => [`=HYPERLINK("${fanpage.url}","${fanpage.title}")`]);
    // 開頭填入填上FB粉專/IG帳號
    title_array.unshift([title])//unshift是指插入陣列開頭
    // 在第一欄寫入title(粉專名稱)
    await writeTitle(title, title_array, auth)
    // 取得目前最後一欄
    let lastCol = await getLastCol(title, auth)
    
    let trace_array = result_array.map(fanpage => [fanpage.trace]);
    // 抓取當天日期
    const datetime = new Date()
    trace_array.unshift([dateFormat(datetime, "GMT:yyyy/mm/dd")])
    // 再寫入trace(追蹤人數)
    await writeTrace(title, trace_array, lastCol, auth)
}
寫入Sheet 的範例昨天的經驗我們可以很容易找到今天所需的資源,在範例首頁的大標題Basic Writing就符合今天的需求 
這個方法能寫入指定 Sheet 範圍內的資料 
Try this API來確認 Method: spreadsheets.values.update 是否能正確更新 Sheet 的內容,相關操作方式昨天的文章有示範,這裡就不再贅述了,下面我把官方範例翻譯了一下,讓大家更清楚使用的方法
let title = '你的sheet title'
//Google Sheets能吃的array格式範例
let array = [['test1'],['test2'],['test3'],['test4']]
async function writeSheet (title, array, auth) {//auth為憑證通過後取得
  const sheets = google.sheets({ version: 'v4', auth });
  const request = {
    spreadsheetId: process.env.SPREADSHEET_ID,
    valueInputOption: "USER_ENTERED",//寫入格式的分類有:INPUT_VALUE_OPTION_UNSPECIFIED|RAW|USER_ENTERED
    range: [
      `'${title}'!A:A`//title是sheet的標題,A:A是能寫入的範圍
    ],
    resource: {
      values: array
    }
  }
  try {
    await sheets.spreadsheets.values.update(request);//執行後即完成Google Sheets更新
    console.log(`updated ${title} title`);
  } catch (err) {
    console.error(err);
  }
}
writeTitleasync function writeTitle (title, title_array, auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const request = {
        spreadsheetId: process.env.SPREADSHEET_ID,
        valueInputOption: "USER_ENTERED",
        range: [
        `'${title}'!A:A` // 這是寫入第一欄的意思
        ],
        resource: {
        values: title_array
        }
    }
    try {
        await sheets.spreadsheets.values.update(request);
        console.log(`updated ${title} title`);
    } catch (err) {
        console.error(err);
    }
}
getLastColtoColumnName 把取得的欄位名稱轉換為英文,這樣 Google Sheets 才知道要寫入的欄位async function getLastCol (title, auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const request = {
        spreadsheetId: process.env.SPREADSHEET_ID,
        ranges: [
        `'${title}'!A1:ZZ1`
        ],
        majorDimension: "COLUMNS",
    }
    try {
        let values = (await sheets.spreadsheets.values.batchGet(request)).data.valueRanges[0].values;
        // console.log(title + " StartCol: " + toColumnName(values.length + 1))
        return toColumnName(values.length + 1)
    } catch (err) {
        console.error(err);
    }
}
function toColumnName (num) {//Google Sheets無法辨認數字欄位,需轉為英文才能使用
    for (var ret = '', a = 1, b = 26; (num -= a) >= 0; a = b, b *= 26) {
        ret = String.fromCharCode(parseInt((num % b) / a) + 65) + ret;
    }
    return ret;
}
writeTraceasync function writeTrace (title, trace_array, lastCol, auth) {
    const sheets = google.sheets({ version: 'v4', auth });
    const request = {
        spreadsheetId: process.env.SPREADSHEET_ID,
        valueInputOption: "USER_ENTERED",
        range: [
        `'${title}'!${lastCol}:${lastCol}`//將追蹤人數填入最後一欄
        ],
        resource: {
        values: trace_array
        }
    }
    try {
        await sheets.spreadsheets.values.update(request);
        console.log(`updated ${title} trace`);
    } catch (err) {
        console.error(err);
    }
}
 執行程式
 執行程式yarn start
 
 
目前為止將爬蟲寫入 Google Sheets 的動作已經完成了,大家可以思考一下還有什麼東西是我們忽略的呢?有什麼狀況會造成錯誤?希望大家在下方提供自己的想法喔~
我在 Medium 平台 也分享了許多技術文章
❝ 主題涵蓋「MIS & DEVOPS、資料庫、前端、後端、MICROSFT 365、GOOGLE 雲端應用、個人研究」希望可以幫助遇到相同問題、想自我成長的人。❞
在許多人的幫助下,本系列文章已出版成書,並添加了新的篇章與細節補充:
- 加入更多實務經驗,用完整的開發流程讓讀者了解專案每個階段要注意的事項
- 將爬蟲的步驟與技巧做更詳細的說明,讓讀者可以輕鬆入門
- 調整專案架構
- 優化爬蟲程式,以更廣的視角來擷取網頁資訊
- 增加資料驗證、錯誤通知等功能,讓爬蟲執行遇到問題時可以第一時間通知使用者
- 排程部分改用 node-schedule & pm2 的組合,讓讀者可以輕鬆管理專案程序並獲得更精確的 log 資訊
有興趣的朋友可以到天瓏書局選購,感謝大家的支持。
購書連結:https://www.tenlong.com.tw/products/9789864348008