
前言
這一篇將會接續前一篇的文章內容,繼續把前一篇沒講完的爬蟲內容給補完。
續談爬蟲
一開始,我們先回顧一下我們前面寫了什麼東西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| const fs = require('fs');
const cheerio = require('cheerio');
const getData = async (url) => { try { const response = await fetch(url); const data = await response.text(); return data; } catch (error) { console.log(error); } }
const crawler = async () => { const html = await getData('https://ithelp.ithome.com.tw/2022ironman/signup/list'); const $ = cheerio.load(html); const paginationInner = $('span.pagination-inner > a').last(); const lastPage = Number(paginationInner.text()); const data = [];
for(let i = 1; i <= lastPage; i++) { console.log(`正在爬取第 ${i} 頁`);
const html = await getData(`https://ithelp.ithome.com.tw/2022ironman/signup/list?page=${i}`);
const $ = cheerio.load(html);
const listCard = $('.list-card');
listCard.each((index, element) => { const name = $(element).find('.contestants-list__name').text(); const category = $(element).find('.tag span').text(); const title = $(element).find('.contestants-list__title').text(); const url = $(element).find('.contestants-list__title').attr('href'); data.push({ name, category, title, url, }); });
await new Promise((resolve) => { setTimeout(() => { resolve(); }, 5000); }) }
fs.writeFileSync('./data.json', JSON.stringify(data)); }
crawler();
|
基本上這個爬蟲幫我們整理出了以下資料
1 2 3 4 5 6 7 8 9 10
| [ { "name":"Ray", "category":"Modern Web", "title":"終究都要學 React 何不現在學呢?", "url":"https://ithelp.ithome.com.tw/users/20119486/ironman/5111" }, ]
|
但我們還缺瀏覽人數、Like 人數、留言人數的資料,所以這時候我們就必須要進入到文章頁面中,來取得這些資料,而文章頁面我們已經撈到了,只是我們還沒有解析文章頁面的資料而已。
接下來我們就要來想辦法解析文章頁面的資料,其實很簡單,因為我們已經將資料轉換成 data.json 了,所以我們只要再寫一個爬蟲來解析 data.json 的 url 並去取得文章頁面的資料就可以了。
但這邊要請你在剛剛的專案資料夾 example-ithelp-crawler 在建立檔案,叫做 parse-list.js,因為我們要區分兩個爬蟲,所以我們就分別寫在不同的檔案中,而剛剛的檔案 index.js 請幫我改成 get-list.js
接著就直接來看程式碼,一開始我們先看頁面請求回來的結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const data = require('./data.json'); const fs = require('fs'); const cheerio = require('cheerio');
const getData = async (url) => { try { const response = await fetch(url); const data = await response.text(); return data; } catch (error) { console.log(error); } }
const crawler = async () => { const length = data.length; console.log(length); for(let i = 0; i <= length; i++) { console.log(`正在爬取第 ${i} 筆資料`); const html = await getData(data[i].url); const $ = cheerio.load(html); } }
crawler()
|
執行後我們只需要找到這一段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <div class="qa-list profile-list ir-profile-list"> </div> <div class="qa-list profile-list ir-profile-list"> <div class="profile-list__condition"> <a class="qa-condition "> <span class="qa-condition__count"> 0 </span> <span class="qa-condition__text"> Like </span> </a> <a class="qa-condition "> <span class="qa-condition__count"> 0 </span> <span class="qa-condition__text"> 留言 </span> </a> <a class="qa-condition qa-condition--change "> <span class="qa-condition__count"> 651 </span> <span class="qa-condition__text"> 瀏覽 </span> </a> </div> <div class="profile-list__content"> <div class="ir-qa-list__status"> <span class="ir-qa-list__days ir-qa-list__days--profile "> DAY 1 </span> </div> <h3 class="qa-list__title"> <a href="https://ithelp.ithome.com.tw/articles/10287240 " class="qa-list__title-link"> Day1-C語言的hello_world </a> </h3> <p class="qa-list__desc"> 系統:ubuntu-22.04 需要安裝套件如下(Command): sudo apt install build-essential C: #include... </p> <div class="qa-list__info"> <a title="2022-09-01 20:29:49" class="qa-list__info-time"> 2022-09-01 </a> ‧ 由 <a href="https://ithelp.ithome.com.tw/users/20151652/profile" class="qa-list__info-link"> Hello_world </a> 分享 </div> </div> </div> <div class="qa-list profile-list ir-profile-list"> </div>
|
所以我們就可以先確定我們要撈的是 .qa-list.profile-list.ir-profile-list 這個元素,而我們要的資料是 Like、留言、瀏覽,所以我們就可以先來撈這三個資料撈出來後,還要全部加總起來,所以為了避免太複雜,一開始我們先只取得第一頁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| const data = require('./data.json'); const fs = require('fs'); const cheerio = require('cheerio');
const getData = async (url) => { try { const response = await fetch(url); const data = await response.text(); return data; } catch (error) { console.log(error); } }
const crawler = async () => { const length = data.length; for(let i = 0; i <= length; i++) { console.log(`正在爬取第 ${i} 筆資料`); const html = await getData(data[i].url); const $ = cheerio.load(html);
const qaList = $('.qa-list.profile-list.ir-profile-list > div.profile-list__condition');
let like = 0; let comment = 0; let view = 0;
qaList.each((index, element) => { const qaListElement = $(element);
qaListElement.find('a').each((index, element) => { const qaListElementA = $(element); const qaListElementAText = qaListElementA.text();
if(qaListElementAText.includes('Like')) { like += Number(qaListElementA.find('.qa-condition__count').text()); } if(qaListElementAText.includes('留言')) { comment += Number(qaListElementA.find('.qa-condition__count').text()); } if(qaListElementAText.includes('瀏覽')) { view += Number(qaListElementA.find('.qa-condition__count').text()); } }); });
console.log(like, comment, view) } }
crawler()
|
基本上不意外你應該是可以正常取得並統計成功的,後面接下來就針對分頁去撰寫了,那麼我們就來看看分頁的結構
1 2 3 4 5 6 7 8 9
| <div class="profile-pagination"> <ul class="pagination"> <li class="disabled"><span>上一頁</span></li> <li class="active"><span>1</span></li> <li><a href="https://ithelp.ithome.com.tw/users/20129584/ironman/5891?page=2">2</a></li> <li><a href="https://ithelp.ithome.com.tw/users/20129584/ironman/5891?page=3">3</a></li> <li><a href="https://ithelp.ithome.com.tw/users/20129584/ironman/5891?page=2" rel="next">下一頁</a></li> </ul> </div>
|
概念其實跟前面的差不多,所以一樣要取得分頁最後一個,也就是 3,但這邊我們不可以寫死,因為有可能參賽者是只有 1 頁,甚至 2 頁而已,因此這一段完全都要靠判斷的,底下我也貼上完整程式碼,逐行補上註解來說明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| const data = require('./data.json'); const fs = require('fs'); const cheerio = require('cheerio');
const getData = async (url) => { try { const response = await fetch(url); const data = await response.text(); return data; } catch (error) { console.log(error); } }
const crawler = async () => { const length = data.length;
for(let i = 0; i < length; i++) { console.log(`正在爬取第 ${i + 1} 筆資料, ${data[i].url}`); const html = await getData(data[i].url); const $ = cheerio.load(html);
const page = $('.profile-pagination > ul > li').last().prev().find('a').text();
let like = 0; let comment = 0; let view = 0;
for(let j = 0; j < page; j++) { console.log(`分頁第 ${j + 1} 頁, ${data[i].url}?page=${j + 1}`); const html = await getData(`${data[i].url}?page=${j + 1}`); const $ = cheerio.load(html); const qaList = $('.qa-list.profile-list.ir-profile-list > div.profile-list__condition'); qaList.each((index, element) => { const qaListElement = $(element);
qaListElement.find('a').each((index, element) => { const qaListElementA = $(element); const qaListElementAText = qaListElementA.text();
if(qaListElementAText.includes('Like')) { like += Number(qaListElementA.find('.qa-condition__count').text()); } if(qaListElementAText.includes('留言')) { comment += Number(qaListElementA.find('.qa-condition__count').text()); } if(qaListElementAText.includes('瀏覽')) { view += Number(qaListElementA.find('.qa-condition__count').text()); } }); }); } console.log(like, comment, view)
data[i].like = like; data[i].comment = comment; data[i].view = view;
await new Promise((resolve) => { setTimeout(() => { resolve(); }, 5000); }) }
fs.writeFileSync('./data2.json', JSON.stringify(data)); }
crawler()
|
Note
此段程式碼僅示範,建議不要隨便拉下來執行,因為在資料較多的關係,所以跑起來會很慢,建議可以自己去找一些資料量較少的網站來練習,或者將 const length = data.length; 改成 const length = 10; 來測試。

那麼透過以上程式碼,前一篇+這一篇你應該會得到兩個檔案,分別是獲取參賽列表(get-list.js)跟獲取參賽者文章(parse-list)頁面,為什麼要特別拆成兩部分呢?因為參賽資料其實並不會沒事一直更動,所以基本上久久跑一次就可以了,所以才特別只跑一次哩。
但我這邊就不花時間介紹說明前端了,畢竟如果再搭配前端來介紹呈現畫面的話,可能就沒完沒了了 QQ
只是我相信你應該已經發現當我們學會如何使用爬蟲時,我們就可以使用爬蟲取得我們想要的資料,並組合成我們想要的資料格式哩~
那麼這一篇就準備先到這邊,我們下一篇見哩。
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
Advertisement