[P2] - Lấy dữ liệu website bằng puppeteer

phần 1 mình đã nắm được những kiến thức cơ bản về thằng puppeteer này rồi Mục tiêu của mình ở phần này là sẽ quét những bài viết to ở mỗi category của trang 24h.com.vn. Sau đó vào từng bài để lấy phần header

1. Lấy link bài viết

Nhắc lại chút kiến thức của phần 1 nhé. Mình sẽ dùng hàm evaluate của thằng puppeteer để chạy js trong browser. Những bài viết mình muốn lấy nó nằm trong thẻ <div class="colLeft"> & <span class="news-title">. Vậy ta vào console của Chrome code tạm trước xem sao đã

// Lấy toàn bộ thẻ a chứa link & title
let titles = document.querySelectorAll("div.colLeft span.news-title a");
let ar_title = [];
// lấy ra link & title của bài viết => đẩy vào mảng ar_title
titles.forEach(item => {
    ar_title.push({
        href: item.getAttribute('href').trim(),
        title: item.getAttribute('title').trim(),
    })
});

Vậy là ta đã có được link & title của những bài viết mà ta cần lấy. Áp dụng chỗ code trên vào trong hàm evaluate trong phần 1 nhé

(async() => {
    try {
        const browser = await puppeteer.launch({headless: false});
        const page = await browser.newPage();
        page.setViewport({width: 1280, height: 720});
        await page.goto(URL);

        const articles = await page.evaluate(() => {
            let titles = document.querySelectorAll("div.colLeft span.news-title a");
            let ar_title = [];
            titles.forEach(item => {
                ar_title.push({
                    href: item.getAttribute('href').trim(),
                    title: item.getAttribute('title').trim(),
                })
            });
            return ar_title
        });
        
        console.log(ar_title);
        await browser.close();
    } catch (error) {
        console.log("Catch : " + error);
    }
})();

2. Vào từng bài viết để lấy content

2.1: Code sai SML

Ta có link từng bài viết rồi. Giờ thì chọc vào từng bài để lấy header thôi. Thử xem sao Theo hướng thông thường ta sẽ chạy forEach để lấy link sau đó mở 1 page mới => lấy dữ liệu ở trong hàm evaluate

let i = 0;
await Promise.all(articles.map(item => {
    return browser.newPage().then(async page => {
        await page.goto(URL + item.href);

        let title = await page.evaluate(() => {
            return document.querySelector("p.baiviet-sapo").innerText;
        });

        i++;
        console.log(i + ': ', title);
        // await page.close();
    });
}));

Bạn thử chạy code phía trên xem sao Kinh khủng phải không. Nó bật 1 đống tab, điều này ảnh hưởng nghiêm trọng đến hiệu năng & khiến máy dễ bị treo

2.2: Refactor code lại

Code thế kia thì không xài được rồi. Vậy giờ hướng của ta sẽ là

  1. Mở trang chủ. Lấy ra list những link bài viết
  2. Ở tab đó. Di chuyển đến bài viết đầu tiên. Lấy header của bài viết đó
  3. Ở tab đó. Di chuyển đến bài viết thứ 2. Lấy header của bài viết đó
  4. Cứ lặp như vậy ta sẽ lấy được hết header của các bài viết mà vẫn đảm bảo được hiệu năng. Chỉ là hơi lâu một chút =))

Ok. Thử nào Đầu tiên là ta phải loop biến articles để lấy ra url từng bài viết

// chứa danh sách những promise
const promises = [];
for (let i = 0; i < articles.length; i++) {
    promises.push(await getTitle(articles[i].href, page, i))
}

Sau đó gọi đến hàm chuyên để lấy header

async function getTitle(link, page, key) {
    await page.goto(URL + link, {
    // Set timeout cho page
        timeout: 3000000
    });
    // Chờ 2s sau khi page được load để tránh overload
    await page.waitFor(2000);

    let title = await page.evaluate(() => {
        let header = document.querySelector("p.baiviet-sapo");
        if (header === null) {
            header = document.querySelector("div.imageTitle a");
        }
        return header.innerText;
    });

    console.log('Page ID Spawned', key, title);
    return page;
}

Done. Bạn thử chạy xem có được như plan mình vạch ra phía trên không

3. Download ảnh được load bằng AJAX

3.1: Mục tiêu

Mình có tìm được 1 trang load content bằng ajax: https://demo.tutorialzine.com/2009/09/simple-ajax-website-jquery/demo.html Mình sẽ tìm cách download ảnh con mèo ở page 3

3.2: Hướng giải quyết

  1. Goto đến trang đó
  2. Click vào page 3
  3. Lấy được đường dẫn ảnh cần download
  4. Download ảnh đó về

3.3: Practice

Mình sẽ bỏ qua những đoạn code cơ bản nhé

await page.click('#navigation > li:nth-child(3) > a');
await page.waitForSelector('div#pageContent img');

Mình sẽ click vào page 3. Sau đó chờ cho đến khi ảnh con mèo được load ra Lúc này ta mới dùng hàm evaluate để lấy được đường dẫn của ảnh

const imgUrl = await page.evaluate(() => {
    return document.querySelector('div#pageContent img').getAttribute('src');
});

Bạn chú ý là đường dẫn này là tương đối nhé. Chưa có domain

Code để lấy được đường dẫn ảnh của ta sẽ như sau

const puppeteer = require('puppeteer');
let DOMAIN = 'https://demo.tutorialzine.com/2009/09/simple-ajax-website-jquery';
let URL = DOMAIN + '/demo.html';

(async() => {
    try {
        const browser = await puppeteer.launch({headless: false});
        const page = await browser.newPage();
        await page.goto(URL);

        await page.click('#navigation > li:nth-child(3) > a');
        await page.waitForSelector('div#pageContent img');

        const imgUrl = await page.evaluate(() => {
            return document.querySelector('div#pageContent img').getAttribute('src');
        });
        console.log(imgUrl);
        
        browser.close()
    } catch (error) {
        console.log("Catch : " + error);
    }
})();

Để download ảnh thì nodejs có bộ lib image-downloader. Bạn có thể tham khảo ở đây

Ta có url rồi. Giờ chỉ việc ghép vào xài thôi

console.log(imgUrl);

const options = {
    url: DOMAIN + '/' + imgUrl,
    dest: 'images'
};
const { filename, image } = await download.image(options);
browser.close()

Vậy là mình đã hướng dẫn bạn cách lấy được nội dung bài viết & download img được load bằng AJAX Tất cả code trên đều chỉ là mình tự mày mò nên chắc chắn nó chưa được clear & clean. Rất mong nhận được sự góp ý để code của mình có thể hoàn thiện được hơn

All Rights Reserved