iT邦幫忙

2021 iThome 鐵人賽

DAY 11
2
Modern Web

JavaScript Easy Go!系列 第 11

#11 Web Crawler 4

今天,來優化爬蟲的速度。

調查問題成因

回顧一下,我們的程式執行了以下步驟:

  1. 下載網頁
  2. 解析網頁
  3. 合併數據
  4. 儲存數據

我們先來記錄一下各步驟執行的時間。

// 在 Top Level 增加記錄用時的變數
let DownloadT = 0,
    ParseT = 0,
    OrganizeT = 0,
    SaveT = 0;

我們用 Date.now() 來記錄各時間點的時間,然後計算差值來獲取所花時間。

Download: 542.958s, Parse: 69.241s, Organize: 0.005s, Save: 0.036s

總共花了 612.24 秒,其中花最多時間的是下載網頁 (88.7%),再來是解析網頁 (11.3%),合併數據及儲存數據的部分所花時間則皆不足 0.1%。

爬取 1047 個頁面花費 543 秒,平均一個網頁請求需約 500 毫秒。用瀏覽器的 console 調查了一下,發現單一網頁文件大小只有約 15 kB 爬取所有頁面也才需要約 15 MB 的大小。我們可以推斷,請求延遲阻塞了許多時間

在處理請求延遲的問題上,我們可以使用並發來減少其對總時間之影響,當然也得確定並發數量不會過大造成伺服器負擔。

我們的方法是:第一次請求時紀錄總共有多少頁,接著直接依照紀錄的執行請求,而不從當前頁面推斷下一頁,這樣就不會因為要知道前一頁才能處理下一頁而造成組塞了。當然為了預防在爬取時有新的文章出現,我們會在每次爬取時確定總頁數有沒有增加,動態的提升總頁數紀錄

更新程式

我們在執行前先設定最大並發上限:

// 在 Top Level 且在 main() 執行前
let available = 12; // 最大並行上限設為 12
const finished = [];
function isAvailable() {
    return new Promise((resolve) => {
        if (available > 0) {
            available--;
            resolve();
        } else finished.push(resolve);
    });
}

我們會使用 isAvailable 來阻塞避免超過最大上限,當已有最大上限之行程在執行時,則會等待直至現有行程完成而出現空缺。

同時,我們將 crawler 中的部分程式移出至新的 task 函式:

async function task(url) {
    console.log(`Crawling Page: ${url}`);
    const result = new Map();
    const DT = Date.now();
    const html = await fetch(url).then((res) => res.text());
    DownloadT += Date.now() - DT;

    const PT = Date.now();
    const dom = new JSDOM(html);
    const document = dom.window.document;

    // 以 CSS Selector 尋找總頁數
    const pageCount = parseInt(document.querySelector(".pagination > li:nth-last-child(2)").textContent);

    const articles = document.querySelectorAll("li.ir-list");
    for (const article of articles) {
        const parsed = parseArticle(article);
        result.set(parsed.link, parsed);
    }
    ParseT += Date.now() - PT;

    if (finished.length > 0) {
        // 釋出空間讓其他任務可以執行
        const resolve = finished.shift();
        resolve();
    }

    return { result, pageCount };
}

所以現在我們的 crawler 函式只剩下:

async function crawler(startURL) {
    const result = new Map();

    // 先爬取第一頁
    const firstPage = await task(startURL);
    firstPage.result.forEach((val, key) => result.set(key, val));
    
    // 總頁數,注意:我們用 let 而非 const
    let total = firstPage.pageCount;
    // 紀錄任務用,最後使用 Promise.all 來確認所有請求皆已完成
    const tasks = [];
    for (let i = 2; i <= total; i++) {
        // 用來在已達最大上限時阻塞
        await isAvailable();
        // 把任務放入任務集合
        tasks.push(
            task(startURL + "?page=" + i).then((page) => {
                page.result.forEach((val, key) => result.set(key, val));
                // 如果發現總頁數增加了,則更新總頁數
                if (page.pageCount > total) total = page.pageCount;
            })
        );
        // 當到最後一頁時,等待所有任務都完成再跳出,或有新的總頁數則繼續
        if(i === total) await Promise.all(tasks);
    }

    // 回傳陣列型態的 result
    return [...result.values()];
}

其他的部分,我們不需要做任何更動,執行看看吧!

優化效果

Running...

Crawled 10563 Articles in 130.73s
Download: 1370.683s, Parse: 53.579s, Organize: 0.006s, Save: 0.166s

我們用 130.7 秒爬了 10563 篇文章,平均一秒爬取 80 篇文章,也就是 8 頁,速度是未優化前的 3.2 倍
等等,Download 時間為什麼有 1370 秒?因為 Download 跟 Parse 都是並行的,所以計時上會有重疊的狀況。

並行 (concurrency) 的好處是可以讓 CPU 的使用效率更高,而無須花太多時間因等待而閒置計算資源。

資料爬下來之後?

資料爬下來之後就拿來分析並做成下面那個東西吧!
明天就來做每日鐵人賽熱門 Top 10


每日鐵人賽熱門 Top 10 (0924)

以 9/24 20:00 ~ 9/25 20:00 文章觀看數增加值排名

  1. +953 Day 1 無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  2. +803 Day 3 雲端四大平台比較:AWS . GCP . Azure . Alibaba
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  3. +801 Day 2 AWS 是什麼?又為何企業這麼需要 AWS 人才?
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  4. +777 Day 4 網路寶石:AWS VPC Region/AZ vs VPC/Subnet 關係介紹
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  5. +750 Day 5 網路寶石:AWS VPC 架構 Routes & Security (上)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  6. +727 Day 6 網路寶石:AWS VPC 架構 Routes & Security (下)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  7. +725 Day 8 網路寶石:【Lab】VPC外網 Public Subnet to the Internet (IGW) (下)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  8. +724 Day 9 運算寶石:EC2 重點架構
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  9. +717 Day 10 運算寶石:EC2 儲存資源 Instance Store vs Elastic Block Storage (EBS)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題
  10. +714 Day 7 網路寶石:【Lab】VPC外網 Public Subnet to the Internet (IGW) (上)
    • 作者: 用圖片高效學程式
    • 系列:無限手套 AWS 版:掌控一切的 5 + 1 雲端必學主題

看來今天 AWS 文章觀看數增加速度收斂一點了


上一篇
#10 Web Crawler 3
下一篇
#12 Web Crawler 5
系列文
JavaScript Easy Go!31

尚未有邦友留言

立即登入留言