爬蟲是一個技術,他將網頁的數據收集下來
Google Sheets 是一個容器,他可以儲存資料並將資料以不同面向做展示爬蟲 X Google Sheets = 你要學習的技術整合
crawlerIG、crawler
這兩隻函式,讓他們依照我們想要格式回傳爬蟲資料1.1 分析 Google Sheets 上要呈現的資料、寫入時可能遇到的問題
1.2 組合回傳的資訊:以 crawlerFB
為範例
updateGoogleSheets
2.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);
}
}
writeTitle
async 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);
}
}
getLastCol
toColumnName
把取得的欄位名稱轉換為英文,這樣 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;
}
writeTrace
async 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