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
Selectablecủa đối tượngPagecho 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àopage.ResultItemsvớ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.