專案網址,對 code 有興趣可以到 Github Repo 看看!
動機
身為一個省錢的學生,每次開學要買原文書都要先去各大網站搜索一番,希望可以找到便宜的書籍,或者是一般課外書如果圖書館借不到也會貨比三家再抉擇。心想如果能夠有個網站把所有的書籍整理起來給我,那會讓我找書的過程更有效率。
專案規劃
爬蟲來源:以我個人買書會搜尋的三個網站進行爬蟲,分別是博客來、蝦皮書城以及城邦。
專案主要邏輯:考量到書籍的價格變動頻率不高,且為了避免頻繁發送請求 IP 會被封鎖,僅有第一次搜尋關鍵字時會進行爬蟲並將商品存到資料庫,隨後若輸入重複的關鍵字,會直接從資料庫撈取書籍資料。
更新資料庫:搜尋過的關鍵字會存放到資料庫,每天凌晨根據這些關鍵字進行爬蟲並自動更新資料庫。
爬蟲資料來源
技術面
環境:Node.js / Express.js
資料庫:MySQL
其他相關套件:cheerio、request、nodemailer、node-schedule
STEP1 爬蟲
由於上述的三個網站都沒有提供api串接,所以使用最陽春的方式—直接去該網站剖析html,抓取需要的資訊。
首先下載需要的套件
npm i cheerio request
使用request發送請求,使用方式如下,第一個參數放想存取的網站,第二個參數則是放callback,細節請參考官方文件
將取得的body放入cheerio.load中即可載入html文件並開始剖析
request('https://www.books.com.tw/', function (error, response, body) {
if (error || !body) {
return;
}
const $ = cheerio.load(body); // 載入 body
});
剖析的方式和 css selector 的方式非常像,從頁面上選取需要的元素
tbody.find('td').eq(2).find('.list-date li a')// 從 tbody 中找到第3個 td, 並找到所有在 list-date class 中 list 的 a
- 在抓不到想要的元素時可以使用
.html()
把選取結果印出來
tbody.find(‘td’).eq(3).find(‘a’).html() //<a> ... </a>
- 和陣列的index 一樣,標籤的順序是從 0 開始算起
tbody.find('td').eq(2) //這邊選取的是第三個 <td>
以博客來來說,它的每個商品都是包在 <tbody> 的標籤當中,因此我先將所有<tbody>取出來再跑迴圈,把所有元素整理好放到陣列
為了增加比對的精確性,加了 Regular expression進一步判斷書名或作者是否包含關鍵字
最後再從 index.js 引用這個函式就可以成功爬蟲了,城邦書店也是用類似的方法進行。
蝦皮書城因為它是採取AJAX技術動態載入資料,跟其它兩個網站的爬蟲方式有點不同,如果直接去看網頁原始碼,會發現找不到頁碼上的資訊。
為了取得所需資訊,要去找網頁向伺服器發送的請求網址。
首先按F12 打開開發者模式,接著打開Netword頁籤並重新整理,就可以看到網頁發送的請求,我們再從裡面找出哪個網址是用來請求商品資訊
如上圖所示,在 https://shopee.tw/api/v4/search/search_items?by=relevancy… 點擊 preview 會看到回傳的正是我們要的商品資訊,也就代表我們要用這個網址來獲得 api 資訊。
回傳的資料為JSON格式,商品資訊都是包在名為item_basic的物件,運用迴圈將資料接住,和另外兩個網站比起來,蝦皮不需要一個一個分析html tag,寫爬蟲的速度比較快,但有些資料需要自己拼湊,像是商品連結、照片等。
另外為了避免蝦皮反爬蟲的機制,可以在發送request時一併在headers加入user-agent, cookie等偽裝。
STEP2 更新資料庫
資料爬完後,會存到資料庫,每天固定時間更新,計時的工具使用 node-schedule 搭配node-mailer,等資料庫更新完畢後,會寄信通知,若出錯也會寄信,讓整個流程更為可控。
使用方式很簡單,schedule.sheduleJob函式中放指定時間和預期要做的事情
const schedule = require('node-schedule');
const job = schedule.scheduleJob('42 * * * *', function(){
console.log('The answer to life, the universe, and everything!');
});
時間格式如下(取自官方文件)
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59, OPTIONAL)
以上就是爬蟲大致的程式邏輯,下一篇會開始介紹基本功能和過程中的收穫和心得!