Xây dựng crawler siêu đơn giản với Java (Phần 1)

Tổng quan

Giới thiệu

Crawler là một công cụ giúp thu thập dữ liệu, thông tin từ các trang web khác nhau. Một trong những ví dụ về crawler mà chúng ta gặp hằng ngày là Google. Google là một hệ thống có nhiều máy chủ có thể crawling rất nhiều trang web trên Internet, từ đó chúng ta có thể tìm kiếm nội dung những trang web mà chúng ta cần dựa vào từ khoá cụ thể. Hoặc là những trang web so sánh giá cả từ nhiều nguồn khác nhau (websosanh.vn), trang tin báo tổng hợp (baomoi.com) và nhiều ví dụ khác mà mình không thể liệt kê hết ở đây.

Chúng ta có thể tự viết một crawler đơn giản nhằm thu gom một số dữ liệu cơ bản nào đó. Khi hướng dẫn học viên học module 2 (Advance Programming with Java) tại CodeGym, mình thường giao bài tập xây dựng công cụ crawler này. Ví dụ thu thập giá bất động sản trên các trang rao vặt hoặc giá sản phẩm trên các trang thương mại điện tử. Qua bài viết này, mình sẽ hướng dẫn lại các bạn làm bài tập này với ngôn ngữ lập trình Java.

Một số yêu cầu cơ bản để thực hiện bài tập này:

  • Nắm vững cú pháp ngôn ngữ lập trình Java
  • Sử dụng được ngôn ngữ đánh dấu HTML
  • Sử dụng được biểu thức chính quy (regular expression) — còn được gọi là regex
  • Một ít hiểu biết về HTTP (Giao thức được sử dụng để truy cập các trang web qua Internet)
  • Hiểu cơ bản phương pháp lập trình hướng đối tượng
  • Hiểu cơ bản về design pattern

Ghi chú: Trên thực tế, có nhiều thư viện hỗ trợ chúng ta làm crawler hiệu quả hơn cách làm trong bài này. Với mục đích học là chính, mình sẽ không sử dụng các thư viện đấy mà sẽ tự xây dựng lấy, các bạn nhé!

Thiết kế chương trình

Chúng ta sẽ xây dựng một công cụ có thể thu thập được tin tức bất động sản đang rao (bao gồm bán và cho thuê) tại các website sau:

Mô tả dữ liệu

Khai báo class có tên là ClassifiedAd để mô tả thông tin thu thập được từ các trang web. Bao gồm: tiêu đề, loại tin rao, diện tích, giá, mô tả chi tiết, hình ảnh (link).

Sau khi khảo sát nội dung các trang web, chúng ta thấy rằng:

  • Các tin có thể hiển thị giá tổng (tính trên toàn bộ diện tích) hoặc giá theo mét vuông. Vì vậy cần khai báo những class sau để mô tả loại giá được hiển thị: enum TypePriceclass Price.
  • Đơn vị tiền có thể là “triệu đồng” hoặc “tỷ đồng”. Vì vậy cần thuộc tính unit tương ứng trong class Price. Giá trị được liệt kê trong enum Unit để xác định đơn vị tiền tệ.
  • Các tin rao được phân loại thành: bán căn hộ, bán nhà đất, bán biệt thự, cho thuê,… enum TypeAd được khai báo cho mục đích này.
enum TypePrice {
    PRICE_PER_M2,   // loại giá dựa trên m2
    TOTAL_PRICE     // giá toàn bộ
}
enum Unit {
    MILLION_VND,
    BILLION_VND,
}
class Price {
    private Float price;
    private TypePrice typePrice;
		private Unit unit;
}
enum TypeAd {
		...,
    SELL,
    RENTAL,
    OTHERS
}
class ClassifiedAd {
    private String title;       // tiêu đề
    private TypeAd typeAd;      // loại tin
    private Price price;        // giá
    private Float acreage;      // diện tích
    private String description; // mô tả
}

Thiết kế tổng quan

Qua khảo sát các trang web mà cần thu thập tin, chúng ta có thể xác định thứ tự các bước để thu thập tin như sau:

  1. Truy cập trang chủ, liệt kê danh sách những danh mục tin (ví dụ: chung cư, nhà đất, biệt thự,…)
  2. Truy cập các danh mục tin để liệt kê các tin đang rao
  3. Truy cập trang chi tiết của từng tin để lấy các thông tin chi tiết

Chúng ta sẽ áp dụng pattern Template Method để chuyển các bước trên thành một dãy các bước xử lý chung cho mỗi trang web.

public abstract class Crawler {
// bước 1
    abstract Iterable<Subpage> inspectHomepage();
		
// bước 2
    abstract Iterable<DetailPage> inspectSubpage(Subpage subpage);
		
// bước 3
    abstract ClassifiedAd inspectDetailPage(DetailPage detailPage);
// Đây là thao tác chung cho tất cả các trang web mà chúng ta muốn thu thập tin
    Iterable<ClassifiedAd> inspect() {
        List<ClassifiedAd> classifiedAds = new ArrayList<>();
        Iterable<Subpage> subpages = inspectHomepage();
        for (Subpage subpage: subpages) {
            Iterable<DetailPage> detailPages = inspectSubpage(subpage);
            for (DetailPage detailPage: detailPages) {
                ClassifiedAd classifiedAd = inspectDetailPage(detailPage);
                classifiedAds.add(classifiedAd);
            }
        }
        return classifiedAds;
    }
}

Với mỗi trang web cụ thể, chúng ta có một cách thu thập khác nhau (vì giao diện mỗi trang khác nhau). Trong tương lai, chúng ta có thể bổ sung những trang web khác hoặc thay đổi cách thức thu thập. Vì thế chúng ta sẽ xây dựng các class phục vụ việc crawle cho từng trang web cụ thể theo thiết kế như sau:

Sơ đồ lớp (class diagram) chương trình Crawler

Ở thiết kế trên, chúng ta áp dụng kiến thức về abstract class trong lập trình hướng đối tượng nhằm đảm bảo có thể dễ dàng mở rộng các trang web muốn thu thập. Khi cần bổ sung trang mới, chúng ta có thể implement thêm Crawler mà không phải sửa đổi ở khung thiết kế và luồng thực thi của chương trình. Cuối cùng là class MySimpleCrawler chứa đoạn mã khởi động chương trình.

Bây giờ, chúng ta viết mã kiểm tra xem ý tưởng thiết kế trên có thể thực hiện được chưa nhé! Tính năng ban đầu sẽ là khởi động chương trình và lấy những tin rao ở các trang batdongsan.com.vn.

Ghi chú: Ở công cụ siêu đơn giản này, chúng ta chưa tính đến việc làm thế nào để tối ưu thời gian chạy, lấy nhiều tin ở các trang tiếp theo, lên lịch chạy để luôn cập nhật được tin mới nhất, hoặc thậm chí là cấu hình thời gian ngắt quãng giữa các lần thu thập (để tránh trường hợp một số trang không cho phép tạo request liên tục trong khoảng thời gian nhất định nào đó).

Tham khảo khóa học lập trình web 6 tháng, đảm bảo 100% công việc đầu ra!

Nguồn: https://topdev.vn/blog/xay-dung-crawler-sieu-don-gian-voi-java/


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Leave a Reply

Your email address will not be published. Required fields are marked *