Thu Thập Dữ Liệu Phân Trang Với DotnetSpider

Giới thiệu về DotnetSpider

DotnetSpider là một framework thu thập dữ liệu (web crawler) mã nguồn mở dành cho nền tảng .NET. Để xây dựng một trình thu thập dữ liệu hoàn chỉnh, chúng ta cần bốn thành phần chính: Bộ tải xuống (Downloader), Bộ lập lịch URL (URL Scheduler), Bộ xử lý trang (Page Processor) và Đường ống dữ liệu (Data Pipeline). DotnetSpider đã cung cấp sẵn các triển khai cho Bộ tải xuống và Bộ lập lịch URL, giúp nhà phát triển tập trung vào việc tùy chỉnh Bộ xử lý trang để trích xuất dữ liệu và Đường ống dữ liệu để lưu trữ dữ liệu.

Cài đặt Môi trường

Để bắt đầu, bạn cần một môi trường phát triển .NET. Ví dụ, Visual Studio 2017 trở lên với .NET Framework 4.5+ hoặc .NET Core.

Khởi tạo Dự án Console

1. Tạo một dự án ứng dụng Console mới trong Visual Studio.

2. Trong Solution Explorer, chuột phải vào dự án của bạn và chọn "Manage NuGet Packages..." (Quản lý Gói NuGet...).

3. Tìm kiếm gói `DotnetSpider.Core` và cài đặt nó vào dự án Console của bạn. Đây là thư viện cốt lõi cung cấp các API cần thiết cho việc xây dựng trình thu thập dữ liệu.

Định nghĩa Cấu trúc Dữ liệu

Trước khi trích xuất dữ liệu, chúng ta cần định nghĩa một đối tượng C# để lưu trữ thông tin sẽ được thu thập từ các trang web. Ví dụ, nếu chúng ta muốn thu thập thông tin video, chúng ta có thể tạo một lớp như sau:

public class VideoEntry
{
    public string Title { get; set; }
    // Có thể thêm các thuộc tính khác như URL, mô tả, v.v.
}

Xây dựng Bộ xử lý Trang (Page Processor)

Bộ xử lý trang chịu trách nhiệm phân tích nội dung HTML của trang web và trích xuất dữ liệu mong muốn. Bạn cần triển khai giao diện IPageProcessor hoặc kế thừa từ lớp trừu tượng BasePageProcessor. Dưới đây là ví dụ về một bộ xử lý trang để trích xuất tiêu đề video:

using System.Collections.Generic;
using System.Text;
using DotnetSpider.Core;
using DotnetSpider.Core.Processor;
using DotnetSpider.Core.Selector;
using Request = DotnetSpider.Core.Request;

public class VideoContentProcessor : BasePageProcessor
{
    protected override void Handle(Page page)
    {
        // Sử dụng thuộc tính Selectable của Page để truy vấn và cấu trúc dữ liệu
        // Ví dụ: Tìm tất cả các div có class 'yk-pack pack-film' chứa thông tin video
        var videoNodes = page.Selectable.SelectList(Selectors.XPath("//div[@class='yk-pack pack-film']")).Nodes();
        
        List<VideoEntry> extractedVideos = new List<VideoEntry>();
        foreach (var element in videoNodes)
        {
            var video = new VideoEntry();
            // Trích xuất thuộc tính 'alt' từ thẻ img có class 'quic' bên trong mỗi video element
            video.Title = element.Select(Selectors.XPath(".//img[@class='quic']/@alt")).GetValue();
            extractedVideos.Add(video);
        }
        
        // Lưu đối tượng dữ liệu đã trích xuất vào Page với một khóa duy nhất
        // để Đường ống dữ liệu có thể truy cập sau này.
        page.AddResultItem("ExtractedVideoItems", extractedVideos);

        // --- Logic xử lý phân trang sẽ được thêm vào đây sau ---
        // Tìm và thêm các liên kết phân trang vào hàng đợi để tiếp tục thu thập
        var paginationLinks = page.Selectable.SelectList(Selectors.XPath("//ul[@class='yk-pages']/li/a")).Links().Nodes();
        foreach (var linkElement in paginationLinks)
        {
            string nextUrl = linkElement.GetValue();
            if (!string.IsNullOrEmpty(nextUrl))
            {
                page.AddTargetRequest(new Request(nextUrl, null));
            }
        }
    }
}

Lưu ý:

  • Thuộc tính Selectable của đối tượng Page cho phép bạn thực hiện các truy vấn bằng XPath, CSS, JSONPath hoặc Regex trên nội dung HTML đã tải xuống.
  • Phương thức GetValue() khi được gọi có thể trả về văn bản đã được loại bỏ các thẻ HTML.
  • Dữ liệu đã được cấu trúc (ví dụ: một danh sách VideoEntry) được lưu vào page.ResultItems với một khóa (ví dụ: "ExtractedVideoItems") để chuyển đến Đường ống dữ liệu.

Định nghĩa Đường ống Dữ liệu (Data Pipeline)

Đường ống dữ liệu nhận các đối tượng ResultItems từ Bộ xử lý trang và thực hiện các hành động lưu trữ hoặc xử lý dữ liệu, chẳng hạn như lưu vào cơ sở dữ liệu, ghi vào tệp, hoặc đơn giản là in ra console.

using System;
using System.Text;
using System.Collections.Generic;
using System.Threading; // Để sử dụng Interlocked
using DotnetSpider.Core.Pipeline;
using DotnetSpider.Core.Processor; // Để truy cập VideoEntry

public class ConsoleOutputPipeline : BasePipeline
{
    private static long recordCounter = 0; // Đếm số bản ghi đã xử lý

    public override void Process(ResultItems resultItems)
    {
        // Lấy dữ liệu đã trích xuất bằng khóa đã định nghĩa trong Page Processor
        if (resultItems.Results.TryGetValue("ExtractedVideoItems", out object itemsObj) && itemsObj is List<VideoEntry> videoList)
        {
            StringBuilder outputBuilder = new StringBuilder();
            foreach (var videoItem in videoList)
            {
                Interlocked.Increment(ref recordCounter); // Tăng biến đếm một cách an toàn cho đa luồng
                outputBuilder.Append($" [Video #{recordCounter}] Tiêu đề: {videoItem.Title}\n");
            }
            Console.WriteLine(outputBuilder.ToString());
        }
        // Tại đây, bạn có thể triển khai logic để lưu dữ liệu vào cơ sở dữ liệu, tệp, hoặc gửi đến một dịch vụ khác.
    }
}

Khởi tạo và Chạy Trình Thu Thập Dữ liệu

Cuối cùng, chúng ta cần cấu hình Site, thêm các URL ban đầu, khởi tạo đối tượng Spider với Bộ lập lịch, Bộ xử lý trang và Đường ống dữ liệu đã tùy chỉnh, sau đó chạy nó.

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using DotnetSpider.Core;
using DotnetSpider.Core.Downloader;
using DotnetSpider.Core.Pipeline;
using DotnetSpider.Core.Processor;
using DotnetSpider.Core.Scheduler;
using DotnetSpider.Core.Selector;

// Trong phương thức Main của chương trình Console:
public class Program
{
    public static void Main(string[] args)
    {
        // Cấu hình các thiết lập chung cho việc thu thập dữ liệu
        var configSite = new Site
        {
            EncodingName = "UTF-8", // Đảm bảo đúng mã hóa
            RemoveOutboundLinks = true, // Không theo dõi các liên kết ra ngoài miền chính
            CycleRetryTimes = 3, // Thử lại 3 lần nếu có lỗi tải xuống
            SleepTime = 1000, // Thời gian chờ giữa các yêu cầu (miliseconds)
            UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" // Tùy chỉnh User-Agent
        };

        // Thêm các URL bắt đầu thu thập dữ liệu. Ví dụ: các trang phân trang đầu tiên.
        for (int pageIndex = 1; pageIndex <= 2; ++pageIndex) // Thử với 2 trang đầu tiên
        {
            configSite.AddStartUrl($"http://list.youku.com/category/show/c_96_s_1_d_1_p_{pageIndex}.html");
        }

        // Tạo đối tượng Spider, cấu hình các thành phần chính
        Spider webSpider = Spider.Create(
            configSite,
            new QueueDuplicateRemovedScheduler(), // Sử dụng bộ lập lịch trong bộ nhớ có loại bỏ URL trùng lặp
            new VideoContentProcessor() // Bộ xử lý trang tùy chỉnh của chúng ta
        ).AddPipeline(new ConsoleOutputPipeline()); // Đường ống dữ liệu tùy chỉnh của chúng ta

        webSpider.Downloader = new HttpClientDownloader(); // Bộ tải xuống HTTP client mặc định
        webSpider.ThreadNum = 2; // Số luồng đồng thời để thu thập dữ liệu
        webSpider.EmptySleepTime = 5000; // Thời gian ngủ khi hàng đợi URL trống (miliseconds)

        Console.WriteLine("Bắt đầu quá trình thu thập dữ liệu...");
        webSpider.Run(); // Khởi động trình thu thập dữ liệu
        Console.WriteLine("Hoàn tất quá trình thu thập dữ liệu.");
    }
}

Thực hiện Thu thập Dữ liệu Phân trang

Phần quan trọng để thu thập dữ liệu từ nhiều trang là hướng dẫn trình thu thập dữ liệu cách tìm và thêm các liên kết đến các trang tiếp theo vào hàng đợi. Điều này được thực hiện bằng cách sửa đổi phương thức Handle trong VideoContentProcessor (như đã thêm ở trên).

Bằng cách thêm đoạn mã này vào Handle method:

// Tìm và thêm các liên kết phân trang vào hàng đợi để tiếp tục thu thập
var paginationLinks = page.Selectable.SelectList(Selectors.XPath("//ul[@class='yk-pages']/li/a")).Links().Nodes();
foreach (var linkElement in paginationLinks)
{
    string nextUrl = linkElement.GetValue();
    if (!string.IsNullOrEmpty(nextUrl))
    {
        page.AddTargetRequest(new Request(nextUrl, null));
    }
}

Trình thu thập dữ liệu sẽ tự động phân tích các liên kết phân trang trên mỗi trang đã tải xuống và thêm chúng vào hàng đợi của bộ lập lịch. Điều này cho phép nó tiếp tục thu thập dữ liệu từ tất cả các trang, cho đến khi không còn liên kết phân trang mới nào được tìm thấy hoặc đạt đến giới hạn cấu hình.

Thẻ: DotnetSpider C# web scraping .NET Data Extraction

Đăng vào ngày 25 tháng 7 lúc 00:51