阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

您是否遇到過需要「點擊按鈕」等操作才能顯示更多內容的網頁?此類頁面稱為“動態網頁”,因為它們根據使用者互動加載更多內容。相較之下,靜態網頁會立即顯示所有內容,無需使用者操作。

從動態頁面中抓取內容可能會令人畏懼,因為它需要模擬用戶交互,例如單擊按鈕來存取其他隱藏內容。在本教程中,您將學習如何透過「載入更多」按鈕從無限滾動的網頁中抓取資料。

先決條件

要學習本教程,您需要:

  • Node.js :安裝標記為「LTS」(長期支援)的版本,該版本比最新版本更穩定。

  • Npm :這是一個用於安裝套件的套件管理器。好訊息是「npm」會自動隨 Node.js 安裝,這使得事情變得更快。

  • Cheerio :用於解析 HTML

  • Puppeteer :您將使用它來控制無頭瀏覽器。

  • 用於建立 Scraper 的 IDE :您可以獲得任何程式碼編輯器,例如Visual Studio Code

此外,您還需要對 HTML、CSS 和 JavaScript 有基本的了解。您還需要一個網頁瀏覽器,例如Chrome

初始化專案

建立一個新資料夾,然後在程式碼編輯器中開啟它。在程式碼編輯器中找到“終端”標籤並打開一個新終端。以下介紹如何使用 Visual Studio Code 發現它。

在 VS Code 中找到終端選項卡

接下來,在終端機中執行以下命令來安裝此建置所需的軟體包。

$ npm install cheerio puppeteer

在程式碼編輯器的專案資料夾中建立一個新文件,並將其命名為dynamicScraper.js

幹得好,夥計!

造訪頁面內容

Puppeteer是一個功能強大的 Node.js 庫,可讓您控制無頭 Chrome 瀏覽器,使其成為與網頁互動的理想選擇。借助 Puppeteer,您可以使用 URL 定位網頁、存取內容並輕鬆地從該頁面提取資料。

在本部分中,您將了解如何使用無頭瀏覽器開啟頁面、存取內容以及擷取該頁面的 HTML 內容。您可以在此處找到本教學的目標網站。

注意:您需要在dynamicScraper.js中編寫所有程式碼。

首先使用 require() Node.js 內建函數匯入 Puppeteer,它可以幫助您載入模組:核心模組、第三方函式庫(如 Puppeteer)或自訂模組(如本機 JS 檔案)。

const puppeteer = require('puppeteer');

接下來,定義一個變數來儲存目標 URL。這樣做不是強制性的,但它使您的程式碼更清晰,因為您只需從程式碼中的任何位置引用此全域變數即可。

const url = 'https://www.scrapingcourse.com/button-click';

下一步是建立啟動無頭瀏覽器並檢索目標頁面的 HTML 內容的函數。您應該選擇立即呼叫函數表達式(IIFE) 方法以使事情變得更快。

使用 try-and-catch 區塊定義非同步IIFE:

(async () => {
    try {
        // Code goes here
    } catch (error) {
        console.error('Error:', error.message);
    }
})();

注意:您應該在 try 區塊內編寫本教學部分的所有其他程式碼。

在 IIFE 內部,建立一個新的 Puppeteer 實例並開啟新頁面進行互動。

使用 launch 方法啟動 puppeteer 函式庫的新實例,並將無頭模式傳遞給它。無頭模式可以設定為 true 或 false。將無頭模式設為 true 會使無頭瀏覽器在 puppeteer 啟動時不可見,但將其設為 false 會使瀏覽器可見。

啟動 Puppeteer 後,您還需要呼叫 newPage 方法,該方法會觸發在無頭瀏覽器中開啟新分頁。

// Launch Puppeteer
const browser = await puppeteer.launch({ headless: false }); // Headless mode
const page = await browser.newPage(); // Open a new page

現在,查詢 newPage 方法以定位預期的 URL,並使用page.goto方法在此新分頁中開啟網站。除此之外,您要確保 Puppeteer 僅當且僅當頁面加載了所有必需資源(如圖像和 JS)時才認為頁面已準備好進行互動和資料提取。

為了確保頁面準備就緒,Puppeteer 提供了一個名為waitUntil的選項,它可以採用各種值來定義載入頁面的不同條件:

  • load :等待 load 事件觸發,該事件在 HTML 文件及其資源(例如圖片、CSS、JS)載入後發生。但是,這可能無法解釋在load事件之後載入的其他 JavaScript 渲染內容。

  • domcontentloaded :等待DOMContentLoaded事件,該事件在解析初始 HTML 後觸發。但這會在外部資源(如映像或其他 JS)載入之前載入。

  • networkidle2 :等待 500 毫秒,直到沒有超過兩個活動網路請求(正在進行的 HTTP 請求(例如,載入映像、腳本或其他資源))。當處理發出小型連續請求但不影響主要內容的頁面時,首選此值。

// Navigate to the target URL
await page.goto(url, {
    waitUntil: 'networkidle2', // Ensure the page is fully loaded
});

最後,您只需要使用page.content()來擷取目前頁面的所有 HTML 內容。最重要的是,您應該關閉瀏覽器實例,以避免不必要的記憶體使用,這會降低系統速度。在腳本末尾使用browser.close()來關閉瀏覽器。

// Get the full HTML content of the page
const html = await page.content();

// Log the entire HTML content
console.log(html);

// Close the browser
await browser.close();

使用您現有的程式碼,瀏覽器將非常快速地載入和關閉,您甚至可能無法很好地查看頁面。在這種情況下,您可以使用page.waitForTimeout方法將瀏覽器延遲幾秒鐘。此方法應該出現在browser.close方法之前。

// Delay for 10 seconds to allow you to see the browser
await page.waitForTimeout(10000);

這是本節的完整程式碼:

const puppeteer = require('puppeteer');

const url = 'https://www.scrapingcourse.com/button-click';

(async () => {
    try {
        // Launch Puppeteer
        const browser = await puppeteer.launch({ headless: false }); // Headless mode
        const page = await browser.newPage(); // Open a new page

        // Navigate to the target URL
        await page.goto(url, {
            waitUntil: 'networkidle2', // Ensure the page is fully loaded
        });

        // Get the entire HTML content of the page
        const html = await page.content();

        // Log the entire HTML content
        console.log(html);

        // Delay for 10 seconds to allow you to see the browser
        await page.waitForTimeout(10000);

        // Close the browser
        await browser.close();
    } catch (error) {
        console.error('Error fetching the page:', error.message);
    }
})();

儲存檔案並使用以下命令在終端機內執行腳本:

$ node dynamicScraper.js

該腳本將開啟一個無頭瀏覽器,如下所示:

無頭瀏覽器的範例

瀏覽器加載,Puppeteer 取得其全部 HTML 內容,Console 將內容記錄到終端。

這是您應該在終端中得到的輸出:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Load More Button Challenge - ScrapingCourse.com</title>
</head>
<body>
    <header>
        <!-- Navigation Bar -->
        <nav>
            <a href="/">
                <img src="logo.svg" alt="Logo">
                <span>Scraping Course</span>
            </a>
        </nav>
    </header>

    <main>
        <!-- Product Grid -->
        <div id="product-grid">
            <div class="product-item">
                <a href="/ecommerce/product/chaz-kangeroo-hoodie">
                    <img src="mh01-gray_main.jpg" alt="Chaz Kangeroo Hoodie">
                    <span class="product-name">Chaz Kangeroo Hoodie</span>
                    <span class="product-price">$52</span>
                </a>
            </div>
            <div class="product-item">
                <a href="/ecommerce/product/teton-pullover-hoodie">
                    <img src="mh02-black_main.jpg" alt="Teton Pullover Hoodie">
                    <span class="product-name">Teton Pullover Hoodie</span>
                    <span class="product-price">$70</span>
                </a>
            </div>
            <!-- Additional products (3-12) follow the same structure -->
        </div>

        <!-- Load More Button -->
        <div id="load-more-container">
            <button id="load-more-btn">Load more</button>
        </div>
    </main>
</body>
</html>

請注意,上面的程式碼結構是您的輸出應該的樣子。

哇!你應該為自己走到這一步而感到自豪。您剛完成了抓取網頁內容的第一次嘗試。

模擬載入更多產品流程

在這裡,您想要存取更多產品,為此,您需要多次點擊「加載更多」按鈕,直到耗盡所有產品清單或獲得想要存取的所需數量的產品。

要存取此按鈕並點擊它,您必須先使用任何 CSS 選擇器(元素的類別、id、屬性或標記名稱)來找到該元素。

本教學的目標是從目標網站取得至少 48 個產品,為此,您必須點擊「載入更多」按鈕至少三次。

首先使用任意 CSS 選擇器找到「載入更多」按鈕。進入目標網站,找到「載入更多」按鈕,右鍵單擊,然後選擇inspect選項。

如何使用檢查元素選項存取開發人員工具

選擇檢查選項將開啟開發人員工具,如下頁所示:

開啟開發人員工具檢查特定按鈕元素

上面的螢幕截圖顯示「載入更多」按鈕元素有一個值為「load-more-btn」的id屬性。您可以使用此id選擇器在模擬過程中找到按鈕並多次單擊它。

回到程式碼,仍然在try區塊內,在登出頁面上預設 12 個產品的先前 HTML 內容的程式碼行之後。

定義您想要點選按鈕的次數。回想一下,每次點擊都會額外加載 12 個產品。對於 48 個產品,需要點擊 3 次才能加載剩餘的 36 個產品。

// Number of times to click "Load More"
const clicks = 3;

接下來,您想要循環模擬點擊。模擬將使用執行i次的for迴圈,其中iclicks變數。

for (let i = 0; i < clicks; i++) {
    try {

    } catch (error) {
        console.log('No more "Load More" button or an error occurred:', error.message);
        break; // Exit the loop if no button is found or an error occurs
    }
}

注意:本節的其餘程式碼應編寫在 for 迴圈的 try 區塊內。

為了幫助偵錯和追蹤輸出,請登出目前的按一下嘗試。

console.log(`Clicking the 'Load More' button - Attempt ${i + 1}`);

接下來,您希望能夠找到“加載更多”按鈕並單擊至少三次。但在模擬點擊之前,您應該確保“加載更多”按鈕可用。

Puppeteer 提供了waitForSelector()方法來在使用元素之前檢查它的可見性。

對於「載入更多」按鈕,您必須先使用其id選擇器的值找到它,然後檢查可見性狀態,如下所示:

// Wait for the "Load More" button to be visible and click it
await page.waitForSelector('#load-more-btn', { visible: true });

現在您知道「載入更多」按鈕可用,您可以使用 Puppeteer click()方法點擊它。

// Click the Load more button once it is available
await page.click('#load-more-btn');

一旦模擬單擊“加載更多”按鈕,您應該等待內容加載,然後再模擬另一次單擊,因為資料可能取決於伺服器請求。您必須使用setTimeout()在請求之間引入延遲。

下面的程式碼通知腳本在模擬再次點擊「載入更多」按鈕之前等待至少兩秒鐘。

// Wait 2 seconds for the new content to load using setTimeout
await new Promise(resolve => setTimeout(resolve, 2000));

為了總結本節的內容,您希望在每次點擊後使用content()方法取得目前的 HTML 內容,然後將輸出登出到終端。

// Get and log the updated full HTML after each click
html = await page.content();
console.log(`Full HTML after ${12 * (i + 2)} products loaded:`);
console.log(html);

到目前為止您的完整程式碼:

const puppeteer = require('puppeteer');

(async () => {
    try {
        const browser = await puppeteer.launch({ headless: false }); // Launch the browser
        const page = await browser.newPage(); // Open a new page

        // Navigate to the target website
        await page.goto('https://www.scrapingcourse.com/button-click', {
            waitUntil: 'networkidle2', // Wait until the network is idle
        });

        console.log("Initial page loaded with 12 products");
        // Get full HTML of the initial page
        let html = await page.content();

        // Log the full HTML (first 12 products)
        console.log(html); 

        // Number of times to click "Load More"
        const clicks = 3; 
        for (let i = 0; i < clicks; i++) {
            try {
                console.log(`Clicking the 'Load More' button - Attempt ${i + 1}`);

                // Wait for the "Load More" button to be visible and click it
                await page.waitForSelector('#load-more-btn', { visible: true });
                await page.click('#load-more-btn');

                // Wait 2 seconds for the new content to load using setTimeout
                await new Promise(resolve => setTimeout(resolve, 2000)); 

                // Get and log the updated full HTML after each click
                html = await page.content();
                console.log(`Full HTML after ${12 * (i + 2)} products loaded:`);
                console.log(html);
            } catch (error) {
                console.log('No more "Load More" button or an error occurred:', error.message);
                break; // Exit the loop if no button is found or an error occurs
            }
        }

        // Delay for 10 seconds to allow you to see the browser
        await new Promise(resolve => setTimeout(resolve, 10000)); 

        await browser.close(); // Close the browser
    } catch (error) {
        console.error('Error fetching the page:', error.message);
    }
})();

下面是模擬按鈕點擊 3 次以獲得 48 個產品的輸出:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Load More Button Challenge</title>
</head>
<body>
    <header>
        <nav>
            <!-- Navigation and Logo Section -->
        </nav>
    </header>

    <main>
        <h1>Load More Products</h1>

        <!-- Products Section -->
        <div id="product-grid">
            <!-- Product 1 -->
            <div class="product-item">
                <a href="https://scrapingcourse.com/ecommerce/product/chaz-kangeroo-hoodie">
                    <img src="https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh01-gray_main.jpg" alt="Chaz Kangeroo Hoodie">
                    <div>
                        <span>Chaz Kangeroo Hoodie</span>
                        <span>$52</span>
                    </div>
                </a>
            </div>

            <!-- Product 2 -->
            <div class="product-item">
                <a href="https://scrapingcourse.com/ecommerce/product/teton-pullover-hoodie">
                    <img src="https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh02-black_main.jpg" alt="Teton Pullover Hoodie">
                    <div>
                        <span>Teton Pullover Hoodie</span>
                        <span>$70</span>
                    </div>
                </a>
            </div>

            <!-- Product 3 -->
            <div class="product-item">
                <a href="https://scrapingcourse.com/ecommerce/product/bruno-compete-hoodie">
                    <img src="https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh03-black_main.jpg" alt="Bruno Compete Hoodie">
                    <div>
                        <span>Bruno Compete Hoodie</span>
                        <span>$63</span>
                    </div>
                </a>
            </div>

            <!-- ... -->
            <!-- Additional products follow the same structure -->
            <!-- Total of 48 products loaded -->
        </div>

        <!-- Load More Button -->
        <div id="load-more-container">
            <button id="load-more-btn">Load More</button>
        </div>
    </main>

    <!-- Bootstrap and jQuery libraries -->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

解析產品資訊

解析目前輸出(48 個產品的整個 HTML)以使其可讀且結構良好非常重要。為了使當前輸出有意義,您需要提取每個產品的特定訊息,例如名稱、價格、圖像 URL 和連結。

您需要存取目標網站,加載更多產品,並檢查它們以查看每個產品的結構,並知道使用哪些class選擇器來獲取所需的資訊。

下面的程式碼片段是產品結構的樣子:

<div class="product-item flex flex-col items-center rounded-lg">
    <a href="https://scrapingcourse.com/ecommerce/product/bruno-compete-hoodie">
        <img class="product-image rounded-lg" width="200" height="240" decoding="async" fetchpriority="high" src="https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh03-black_main.jpg" alt="Bruno Compete Hoodie">
        <div class="product-info self-start text-left w-full">
            <span class="product-name">Bruno Compete Hoodie</span>
            <br>
            <span class="product-price text-slate-600">$63</span>
        </div>
    </a>
</div>

在檢查每個產品時,您應該注意到它們都有一個共同的類別: product-item 。您將使用此類來獲取產品清單中的每個產品。

這些是您需要解析的產品元素的 CSS 選擇器值:產品名稱 ( .product-name )、價格 ( .product-price )、圖片 ( .product-image ) 和連結 ( <a>標記)。

首先,導入Cheerio庫,該庫將用於解析 HTML 內容。

const cheerio = require('cheerio');

現在,您應該只關心與所有 48 個產品的輸出進行互動。為此,您需要清理上一節中的先前程式碼。

您還需要在for循環區塊之後降低html變數,這樣您就只能獲得所有 48 種產品的一個輸出。

您的清理程式碼應該與以下程式碼片段相同:

const puppeteer = require('puppeteer');
const cheerio = require('cheerio');

(async () => {
    try {
        const browser = await puppeteer.launch({ headless: false }); // Launch the browser
        const page = await browser.newPage(); // Open a new page

        // Navigate to the target website
        await page.goto('https://www.scrapingcourse.com/button-click', {
            waitUntil: 'networkidle2', // Wait until the network is idle
        });

        // Number of times to click "Load More"
        const clicks = 3; 
        for (let i = 0; i < clicks; i++) {
            try {
                // Wait for the "Load More" button to be visible and click it
                await page.waitForSelector('#load-more-btn', { visible: true });
                await page.click('#load-more-btn');

                // Wait 2 seconds for the new content to load using setTimeout
                await new Promise(resolve => setTimeout(resolve, 2000)); 
            } catch (error) {
                console.log('No more "Load More" button or an error occurred:', error.message);
                break; // Exit the loop if no button is found or an error occurs
            }
        }

        // Get the final HTML content
        const html = await page.content();

        await browser.close(); // Close the browser
    } catch (error) {
        console.error('Error fetching the page:', error.message);
    }
})();

現在,讓我們開始使用Cheerio進行 HTML 解析。

首先,Cheerio 需要存取它想要解析的 HTML 內容,為此,它提供了一個load()方法來接收該 HTML 內容,使其可以使用類似 jQuery 的語法進行存取。

使用 HTML 內容建立 Cheerio 庫的實例:

// Load HTML into Cheerio
const $ = cheerio.load(html);

現在您可以使用 $ 來查詢和操作載入的 HTML 中的元素。

接下來,初始化一個陣列來儲存產品資訊。該陣列將保存提取的資料,每個產品將儲存為一個物件,包含namepriceimagelink

// Array to store product information
const products = [ ];

回想一下,每個產品都有一個類別.product-item 。您將使用它與 Cheerio ($) 的變數實例來取得每個產品,然後執行一些操作。

.each()方法用於透過.product-item類別選擇器迭代每個匹配的元素。

$('.product-item').each((_, element) => {

});

讓我們使用特定詳細資訊的類別選擇器從每個產品中檢索產品詳細資訊。例如,要取得產品名稱,您需要使用類別選擇器.product-item來尋找每個產品中的子元素。檢索該子元素的文字內容並修剪它以防止任何空格。

$('.product-item').each((_, product) => {
    const name = $(product).find('.product-name').text().trim();
})
  • $(element).find('.product-name') :在目前 .product-item 中搜尋具有類別 .product-name 的子元素。

  • .text() :檢索元素內的文字內容。

  • .trim() :從文字中刪除不必要的空格。

利用這個概念,讓我們使用它們的 class 屬性來取得priceimage URLlink

$('.product-item').each((_, product) => {
    const name = $(product).find('.product-name').text().trim();
    const price = $(product).find('.product-price').text().trim();
    const image = $(product).find('.product-image').attr('src');
    const link = $(product).find('a').attr('href');
})

現在您已經擁有了所有預期的訊息,下一步是將每個解析的產品資訊作為單獨的物件推送到products陣列。

$('.product-item').each((_, product) => {
    const name = $(product).find('.product-name').text().trim();
    const price = $(product).find('.product-price').text().trim();
    const image = $(product).find('.product-image').attr('src');
    const link = $(product).find('a').attr('href');

    products.push({
        name,
        price,
        image,
        link,
    });
});

最後,註銷products陣列以在終端中獲得預期的輸出。

console.log(`Total products parsed: ${products.length}`);
console.log(products);

您的整個程式碼應類似於以下程式碼片段:

const puppeteer = require('puppeteer');
const cheerio = require('cheerio');

(async () => {
    try {
        const browser = await puppeteer.launch({ headless: false }); // Launch the browser
        const page = await browser.newPage(); // Open a new page

        // Navigate to the target website
        await page.goto('https://www.scrapingcourse.com/button-click', {
            waitUntil: 'networkidle2', // Wait until the network is idle
        });

        // Number of times to click "Load More"
        const clicks = 3; 
        for (let i = 0; i < clicks; i++) {
            try {
                // Wait for the "Load More" button to be visible and click it
                await page.waitForSelector('#load-more-btn', { visible: true });
                await page.click('#load-more-btn');

                // Wait 2 seconds for the new content to load using setTimeout
                await new Promise(resolve => setTimeout(resolve, 2000)); 
            } catch (error) {
                console.log('No more "Load More" button or an error occurred:', error.message);
                break; // Exit the loop if no button is found or an error occurs
            }
        }

        // Get the final HTML content
        const html = await page.content();

        // Load HTML into Cheerio
        const $ = cheerio.load(html); 
        // Array to store product information
        const products = []; 

        $('.product-item').each((_, product) => {
            const name = $(product).find('.product-name').text().trim();
            const price = $(product).find('.product-price').text().trim();
            const image = $(product).find('.product-image').attr('src');
            const link = $(product).find('a').attr('href');

            products.push({
                name,
                price,
                image,
                link,
            });
        });

        console.log(`Total products parsed: ${products.length}`);
        console.log(products); // Output all parsed product information

        await browser.close(); // Close the browser
    } catch (error) {
        console.error('Error fetching the page:', error.message);
    }
})();

儲存並執行腳本時,輸出應如下所示:

Total products parsed: 48
[
  {
    name: 'Chaz Kangeroo Hoodie',
    price: '$52',
    image: 'https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh01-gray_main.jpg',
    link: 'https://scrapingcourse.com/ecommerce/product/chaz-kangeroo-hoodie'
  },
  {
    name: 'Teton Pullover Hoodie',
    price: '$70',
    image: 'https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh02-black_main.jpg',
    link: 'https://scrapingcourse.com/ecommerce/product/teton-pullover-hoodie'
  },
  {
    name: 'Bruno Compete Hoodie',
    price: '$63',
    image: 'https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh03-black_main.jpg',
    link: 'https://scrapingcourse.com/ecommerce/product/bruno-compete-hoodie'
  },
  {
    name: 'Frankie  Sweatshirt',
    price: '$60',
    image: 'https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/mh04-green_main.jpg',
    link: 'https://scrapingcourse.com/ecommerce/product/frankie--sweatshirt'
  },

  // Every other product goes here, reduced to make things brief and concise
  {
    name: 'Zoltan Gym Tee',
    price: '$29',
    image: 'https://scrapingcourse.com/ecommerce/wp-content/uploads/2024/03/ms06-blue_main.jpg',
    link: 'https://scrapingcourse.com/ecommerce/product/zoltan-gym-tee'
  }
]

將產品資訊匯出至 CSV

下一步是將解析後的產品資訊(目前採用 JavaScript 物件表示法 (Json) 格式)匯出為逗號分隔值 (CSV) 格式。我們將使用json2csv函式庫將解析的資料轉換為其對應的 CSV 格式。

首先導入所需的模組。

Node.js 提供檔案系統 (fs) 模組用於檔案處理,例如將資料寫入檔案。導入fs模組後,您應該解構json2csv庫中的parse()方法。

const fs = require('fs');
const { parse } = require('json2csv');

CSV 檔案通常需要列標題;按照與解析資訊相同的順序仔細寫下此內容。這裡,解析的資料是products陣列,其中每個元素都是一個具有四個鍵(名稱、價格、圖像和連結)的物件。您應該使用這些物件鍵來命名列標題以進行正確的對應。

定義 CSV 檔案的欄位(列標題):

// Define CSV fields
const fields = ['name', 'price', 'image', 'link'];

現在您已經定義了字段,接下來的操作是將目前解析的資訊轉換為 CSV 格式。 parse()方法的工作格式如下:parse(WHAT_YOU_WANT_TO_CONVERT, { YOUR_COLUMN_HEADERS })。

// Convert JSON to CSV
const csv = parse(products, { fields });

現在,您必須將此 CSV 資訊儲存到檔案副檔名為 .csv 的新檔案中。使用 Node.js 時,您可以使用fs模組上的writeFileSync()方法來處理檔案建立。此方法有兩個參數:檔案名稱和資料。

// Save CSV to a file 
fs.writeFileSync('products.csv', csv);

本節的完整程式碼應如下所示:

const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
const fs = require('fs');
const { parse } = require('json2csv');

(async () => {
    const browser = await puppeteer.launch({ headless: false }); // Launch Puppeteer
    const page = await browser.newPage(); // Open a new page

    // Navigate to the website
    await page.goto('https://www.scrapingcourse.com/button-click', {
        waitUntil: 'networkidle2',
    });

    // Click "Load More" 3 times to load all products
    for (let i = 0; i < 3; i++) {
        try {
            await page.waitForSelector('#load-more-btn', { visible: true });
            await page.click('#load-more-btn');
            await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for 2 seconds
        } catch (error) {
            console.log('No more "Load More" button or an error occurred:', error.message);
            break;
        }
    }

    // Get the final HTML content
    const html = await page.content();

    // Use Cheerio to parse the product data
    const $ = cheerio.load(html);
    const products = [];

    $('.product-item').each((_, element) => {
        const name = $(element).find('.product-name').text().trim();
        const price = $(element).find('.product-price').text().trim();
        const image = $(element).find('.product-image').attr('src');
        const link = $(element).find('a').attr('href');

        products.push({
            name,
            price,
            image,
            link,
        });
    });

    console.log(`Total products parsed: ${products.length}`);

    // Convert product information to CSV
    try {
        // Define CSV fields
        const fields = ['name', 'price', 'image', 'link']; 
        // Convert JSON to CSV
        const csv = parse(products, { fields });

        // Save CSV to a file 
        fs.writeFileSync('products.csv', csv); 
        console.log('Product information exported to products.csv');
    } catch (error) {
        console.error('Error exporting to CSV:', error.message);
    }

    await browser.close(); // Close the browser
})();

儲存並執行腳本後,您應該會看到名為products.csv的檔案自動新增到您的檔案結構中。

輸出 - products.csv:

產生的 CSV 快照

結論

本教學深入研究了從需要模擬才能存取其隱藏內容的頁面中抓取資料的複雜性。您學習如何使用 Node.js 和一些附加程式庫在動態頁面上執行網頁抓取,將抓取的資料解析為更有條理的格式,並將其解壓縮到 CSV 檔案中。


原文出處:https://dev.to/shegz/how-to-scrape-data-from-a-page-with-infinite-scroll-4o14

按讚的人:

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈