Laravel Crawler Data cực kì đơn giản với Goutte

Data là một trong những yếu tô quan trọng trong bất kì ứng dụng hay trang web nào. Đặc biệt trong các dự án mới luôn cần có dữ liệu để vận hành các chức năng. Nhưng vấn đề ở đây, dự án mới thì lấy đâu ra data?, mà việc nhập tay sẽ mất rất nhiều thời gian, dữ liệu lại không sát với thức tế. Vậy làm sao có thể có data? Crawler Data chính là câu trả lời cho bạn.

Crawler Data là tự động hóa việc trích xuất thông tin từ một nguồn nào đó, cụ thể ở bài viết này là chúng ta sẽ lấy thông tin các bài tin tức trên một website

1. Chuẩn bị

Chuẩn bị cho project laravel (Php, Composer, Mysql) và config kết nối đến database.

Chúng ta sẽ cài đặt package Laravel-goutte nên các bạn hãy đọc trước.

Địa chỉ website mẫu.

Ảnh để crawler data

2. Cài đặt package

Đầu tiên cài đặt package bằng composer

$ composer require weidner/goutte

Sau đó kiểm tra trong composer.json

"require": {
        "php": "^7.3|^8.0",
        "laravel/framework": "^8.54",
        "weidner/goutte": "^2.1"
    },

Sau khi cài đặt thành công thì các bạn khai báo trong config/app.php

return [
	'providers' => [

        // ...

        /*
         * Package Service Providers...
         */
        Weidner\Goutte\GoutteServiceProvider::class, // [1] This will register the Package in the laravel echo system

        // ...

    ],

    // ...

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,

        // ...

        'Goutte' => Weidner\Goutte\GoutteFacade::class,

        // ...
    ],

];

3. Migrations và Models

Để không làm project trở nên quá phức tạp, với mỗi bài viết, ta chỉ cần lấy thông tin là: title, content, description. Database tương ứng chỉ có một bảng duy nhất. Chạy lệnh sau để tạo 2 file model và migrations cho bảng posts.

$ php artisan make:model Post

Hàm up() trong file migration sẽ có nội dung như sau:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->longText('title');
        $table->longText('content');
        $table->longText('description');
        $table->timestamps();
    });
}

Sau đó để tạo bảng trong CSDL thì chúng ta sẽ dùng câu lênh:

$ php artisan migrate

4. Crawler Data

4.1 Bắt đầu

Trước tiên, ta tạo 1 command line, các bạn cũng có thể sử dụng route, controller để thực hiện. Ta sẽ xử lý logic luôn trong command line

Tạo command line:

$ php artisan make:command ScrapePost

Vào file \app\Console\Commands\ScrapePost.php và chúng ta sẽ thực hiện logic trong đó.

4.2 Tìm hiểu cấu trúc HTML

Để crawl dữ liệu từ một website, trước tiên ta phải hiểu cấu trúc HTML của website đó được tổ chức thế nào. Điều này cũng đồng nghĩa, nếu có bất kì cập nhật nào của chủ website, làm thay đổi cấu trúc HTML thì scraper của ta có khả năng ở nên vô dụng.

Truy cập vào link, kiểm tra các phần tử của website:

Phẩn tích ảnh muốn crawler

Một bài viết sẽ được chia làm 3 phần là title, description, content. Tất cả các bài ở cột bên trái mà chúng ta muốn cào sẽ có cấu trúc này. Trước tiên ta sẽ test cào phần title

Bạn F12 để inspect phần html của phần title. Phần title là thẻ h1 và có class là dt-news__title.

thuộc tính của title

Tượng tự, bạn truy cập vào link để kiểm tra các phần description, content của website:

  • Tất cả description đều được đặt giữa thẻ h2
thuộc tính của description
  • Content được năm trọn trong thẻ div có class "dt-news__content"
thuộc tính của content

Lưu ý rằng cấu trúc HTML trên có thể đã thay đổi so với thời điểm bài viết được tạo ra, vì vậy, bạn nên tự mình vào link và tìm tòi. Đây có lẽ là công đoạn thú vị nhất trong toàn bộ quá trình.

4.3 Crawler Data thôi

Trước tiên, phần cào data này mình sẽ thực hiện login trên command line, các bạn có thể sử dụng route, controller để thực hiện.

Tạo command line

$ php artisan make:command ScrapePost

Truy cập vào thư mục /app/Console/Commands/ScrapePost.php để thực hiện logic trong đó.

Logic sẽ được xử lý trong handle(), link trong phần request GET là bài viết Người bán nhà phố để đi tìm "vùng xanh hạnh phúc"

Để test mình sẽ thực hiện lấy title của bài viết trước

// ScrapePost.php

/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'scrape:post';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
    $crawler = GoutteFacade::request('GET', 'https://dantri.com.vn/lao-dong-viec-lam/can-tho-f0-f1-bot-lo-lang-vi-khong-phai-tra-vien-phi-20210829181940605.htm');
    $title = $crawler->filter('h1.dt-news__title')->each(function ($node) {
    	return $node->text();
    })[0];
    print($title);
}

Phần protected $signature = 'command:name'; các bạn đổi tên thành 'scrape:post' nhé

Rồi bạn chạy command bằng terminal:

scrape title bài viết

Vậy là đã mình đã thành công lấy được title bài viết, giờ mình sẽ lấy các phần còn lại nhé.

//handle

$title = $crawler->filter('h1.dt-news__title')->each(function ($node) {
	return $node->text();
})[0];

$description = $crawler->filter('div.dt-news__sapo h2')->each(function ($node) {
	return $node->text();
})[0];
$description = str_replace('Dân trí', '', $description);

$content = $crawler->filter('div.dt-news__content')->each(function ($node) {
	return $node->text();
})[0];

Kết quả:

scrape bài viết

Ta đã lấy thành công thông tin bài viết. Vậy để lấy thêm được nhiều bài viết khác nữa thì sao? Ở phần chuẩn bị, các bạn thấy các bài viết đều có cấu trúc html như bài mình cào, vậy chỉ cần lấy link của các bài viết thì mình cũng sẽ lấy được nội dụng bài viết đó.

Phần href mà mình cần lấy nằm trong thẻ h3 có class là news-item__title, mình sẽ chọc vào thẻ h3 lấy thẻ a và attr là href.

public function handle()
{
    $crawler = GoutteFacade::request('GET', 'https://dantri.com.vn/lao-dong-viec-lam.htm');
    $linkPost = $crawler->filter('h3.news-item__title a')->each(function ($node) {
    	return $node->attr("href");
    });

    foreach ($linkPost as $link) {
    	print($link . "\n");
    }
}
scrape link các bài viết

Mình đã lấy ra mảng linkPost chứa link những bài viết, vậy mình chỉ cần dùng vòng lặp chọc vào từng link một và xử lý bằng logic lúc ban đầu để lấy title, description, content là mình có thể lấy được được hết nội dung của đống bài viết này rồi.

public function handle()
    {
        $crawler = GoutteFacade::request('GET', 'https://dantri.com.vn/bat-dong-san.htm');
        $linkPost = $crawler->filter('h3.news-item__title a')->each(function ($node) {
            return $node->attr("href");
        });

        foreach ($linkPost as $link) {
            $this->scrapeData($link);
        }
    }
    
public function scrapeData($url)
    {
        $crawler = GoutteFacade::request('GET', $url);

        $title = $this->crawlData('h1.dt-news__title', $crawler);

        $description = $this->crawlData('div.dt-news__sapo h2', $crawler);

        $description = str_replace('Dân trí', '', $description);

        $content = $this->crawlData('div.dt-news__content', $crawler);

        $dataPost = [
            'title' => $title,
            'content' => $content,
            'description' => $description
        ];

        Post::create($dataPost);
    }

protected function crawlData(string $type, $crawler)
    {
        $result = $crawler->filter($type)->each(function ($node) {
            return $node->text();
        });

        if(!empty($result)) {
            return $result[0];
        }

        return '';
    }

Mình đã chọc vào từng link và xử lý bằng hàm scrapedata, logic của scrapedata, handle thì tương tự nhau và mình cũng đã hướng dẫn từ đầu. Ở cuối mình có lưu vào database.

Cuối cùng bạn chạy lại lệnh:

php artisan scrape:post

Vậy là bạn đã có thể crawler data từ một trang web nào đó rồi. Các bạn sự dụng data với mục định test hoặc nghiên cứu thôi nhé.

DB sau khi crawler data

Kết luận

Việc crawler data trở nên đơn giản hơn với Goutte. Bạn có thể tham khảo thêm về Goutte tại đây: https://github.com/FriendsOfPHP/Goutte
Nhưng việc sử dụng Goutte vẫn còn nhiều hạn chế. Việc scrape data chỉ dùng lại ở trang đầu. Để xử lý được, cần dùng một browser simulator như puppeteer của Javascript. Nội dung về puppeteer có thể sẽ được thảo luận ở một bài viết trong tương lai.

Tài liệu tham khảo