Hướng dẫn
Quảng cáo

Bóc tách nội dung trang web với PHP chi tiết từ A-Z

Hướng dẫn cách bóc tách nội dung các trang Web bằng các công cụ mã nguồn mở PHP (Guzzle, XML và XPath), Goutte, Simple HTML DOM và Symfony Panther

Trong nhiều dự án PHPcủa mình các bạn có thể sẽ gặp trường hợp cần quét nội dung một trang web để trích xuất thông tin đó và sử dụng vào dự án của mình. Trong hướng dẫn này, chúng ta sẽ thảo luận về các công cụ và dịch vụ khác nhau mà bạn có thể sử dụng với PHP để lấy nội dung thông tin một trang web. Các công cụ mà chúng ta sẽ thảo luận là Guzzle, Goutte, Simple HTML DOM và trình duyệt Symfony Panther.

Ghi chú: Trước khi tiến hành việc lập trình để lấy thông tin một trang web, bạn nên đọc kỹ Điều khoản dịch vụ của họ để đảm bảo rằng việc bạn lấy và sử dụng thông tin từ họ là cho phép. Việc thu thập dữ liệu - ngay cả khi nó có thể truy cập công khai - có khả năng gây quá tải cho máy chủ của trang web. (Biết đâu đấy - nếu bạn hỏi một cách lịch sự, họ thậm chí có thể cung cấp cho bạn một khóa API để bạn không phải mất công lập trình lấy thông tin :D.)

Chuẩn bị công cụ để viết mã PHP bóc tách dữ liệu trang web

Để có thể làm theo các hướng dẫn sau đây thì bạn cần chuẩn bị các yêu cầu cần thiết sau:

- Phiên bản PHP đang sử dụng là mới nhất
- Đã cài đặt Composer
- Trình soạn thảo lập trình (Sublime Text 3)
- Localhost để chạy thử các file PHP

Bạn có thể tham khảo cài đặt OpenServerđể có đầy đủ các yêu cầu trên luôn. Trong ví dụ này mình sử dụng Console của OpenServer để chạy các lệnh composer nhé.

Bóc tách dữ liệu trang web bằng PHP
OpenServer đã cài đặt đầy đủ các yếu tố cần thiết cho ví dụ


Ở đây mình dùng luôn thư mục dự án là localhost nhé
Mở Console sau đó điều hướng di chuyển đến thư mục localhost

Ví dụ

cd domains\localhost

Chạy hai lệnh sau trong Console của bạn để khởi tạo tệp composer.json :

Ví dụ

composer init — require="php >=7.4" — no-interaction
composer update


Bóc tách dữ liệu trang web bằng PHP
Giờ đây chúng ta bắt đầu đi vào xem xét các cách bóc tách nội dung trang web


Bóc tách nội dung trang web với PHP sử dụng Guzzle, XML, và XPath


Guzzle là một ứng dụng HTTP PHP cho phép bạn gửi các yêu cầu HTTP một cách nhanh chóng và dễ dàng. Nó có một giao diện đơn giản để xây dựng các chuỗi truy vấn.

XML là một ngôn ngữ đánh dấu mã hóa các tài liệu để con người có thể đọc được và máy móc có thể đọc được.

XPath là một ngôn ngữ truy vấn điều hướng và lựa chọn các nút XML.

Hãy xem cách chúng ta có thể sử dụng ba công cụ này cùng nhau để lấy thông tin một trang web.

Bắt đầu bằng cách cài đặt Guzzle thông qua Composer bằng cách thực hiện lệnh sau trong Console của bạn:

Ví dụ

composer require guzzlehttp/guzzle

Khi bạn đã cài đặt Guzzle, hãy tạo một tệp PHP mới mà chúng ta sẽ thêm mã vào. Chúng ta sẽ gọi nó là guzzle_requests.php .

Đối với phần ví dụ này, chúng ta sẽ lấy thông tin trang web Books to Scrape. Bạn có thể làm theo các bước tương tự mà chúng tôi xác định ở đây để quét bất kỳ trang web nào bạn chọn.

Trang web Books to Scrape trông giống như sau:
Bóc tách dữ liệu trang web bằng PHP

Chúng ta cần trích xuất tên sách và hiển thị chúng trên trình duyệt.

Bước đầu tiên trong việc lấy thông tin một trang web là hiểu bố cục HTML của nó. Trong trường hợp này, bạn có thể xem bố cục HTML của trang này bằng cách nhấp chuột phải vào trang, ngay phía trên sản phẩm đầu tiên trong danh sách và chọn Kiểm tra (Inspect) .

Đây là ảnh chụp màn hình hiển thị một đoạn mã nguồn trang:
Bóc tách dữ liệu trang web bằng PHP

Bạn có thể thấy rằng danh sách được chứa bên trong phần tử <ol class = "row"> . Phần tử con trực tiếp tiếp theo là phần tử <li> .

Những gì chúng ta cần là tên cuốn sách. Nó nằm bên trong <a> , lần lượt bên trong <h3> , bên trong <article> , cuối cùng nằm bên trong phần tử <li> .

Để khởi tạo Guzzle, XML và Xpath, hãy thêm mã sau vào tệp guzzle_requests.php :

Ví dụ

<?php
# Lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \GuzzleHttp\Client();
$response = $httpClient->get('https://books.toscrape.com/');
$htmlString = (string) $response->getBody();
//thêm dòng này để ngăn chặn bất kỳ cảnh báo nào
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML($htmlString);
$xpath = new DOMXPath($doc);

Đoạn mã trên sẽ tải trang web thành một chuỗi. Sau đó, chúng ta phân tích cú pháp chuỗi bằng cách sử dụng XML và gán nó cho biến $xpath .

Điều tiếp theo bạn muốn là nhắm mục tiêu nội dung văn bản bên trong thẻ <a> . Thêm mã sau vào tệp:

Ví dụ

$titles = $xpath->evaluate('//ol[@class="row"]//li//article//h3/a');
$extractedTitles = [];
foreach ($titles as $title) {
$extractedTitles[] = $title->textContent.PHP_EOL;
echo $title->textContent.PHP_EOL;
}


Trong đoạn mã ở trên, <strong>//ol[@class="row"] </strong>nhận toàn bộ danh sách.

Mỗi mục trong danh sách có một thẻ <a> mà chúng ta đang nhắm mục tiêu để trích xuất tên thực tế của cuốn sách. Chúng ta chỉ có một thẻ <h3> chứa <a>, điều này giúp bạn nhắm thẻ mục tiêu trực tiếp dễ dàng hơn.

Chúng ta sử dụng vòng lặp foreach để trích xuất nội dung văn bản và hiển thị chúng trên trình duyệt.

Đến bước này, bạn có thể chọn làm bất cứ điều gì đó dữ liệu đã trích xuất của mình, có thể gán dữ liệu cho một biến mảng, ghi vào tệp hoặc lưu trữ trong cơ sở dữ liệu.

Bạn duyệt địa chỉ tệp tin http://localhost/guzzle_requests.php trình duyệt sẽ có kết quả như hình dưới

Bóc tách dữ liệu trang web bằng PHP

Bây giờ, điều gì sẽ xảy ra nếu chúng ta cũng muốn nhận được giá của cuốn sách?
Bóc tách dữ liệu trang web bằng PHP


Giá xảy ra bên trong thẻ <p> , bên trong thẻ <div>. Như bạn có thể thấy, có nhiều hơn một thẻ <p> và nhiều hơn một thẻ <div>.

Để tìm đúng mục tiêu, chúng ta sẽ sử dụng các lớp CSS, may mắn cho chúng ta, là duy nhất cho mỗi thẻ. Đây là đoạn mã để lấy thẻ giá và nối nó với chuỗi tiêu đề:

Ví dụ

$titles = $xpath->evaluate('//ol[@class="row"]//li//article//h3/a');
$prices = $xpath->evaluate('//ol[@class="row"]//li//article//div[@class="product_price"]//p[@class="price_color"]');
foreach ($titles as $key => $title) {
echo $title->textContent . ' @ '. $prices[$key]->textContent.PHP_EOL;
}


Duyệt lại file guzzle_requests.php bạn sẽ có kết quả:
Bóc tách dữ liệu trang web bằng PHP

Toàn bộ mã của bạn sẽ trông như thế này:

Ví dụ

<?php
# Lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \GuzzleHttp\Client();
$response = $httpClient->get('https://books.toscrape.com/');
$htmlString = (string) $response->getBody();
// thêm dòng này để ngăn chặn bất kỳ cảnh báo nào
libxml_use_internal_errors(true);
$doc = new DOMDocument();
$doc->loadHTML($htmlString);
$xpath = new DOMXPath($doc);
$titles = $xpath->evaluate('//ol[@class="row"]//li//article//h3/a');
$prices = $xpath->evaluate('//ol[@class="row"]//li//article//div[@class="product_price"]//p[@class="price_color"]');
foreach ($titles as $key => $title) {
echo $title->textContent . ' @ '. $prices[$key]->textContent.PHP_EOL;
}

Tất nhiên, đây là chỉ là một ví dụ cơ bản về cách lấy thông tin từ một trang web và bạn chắc chắn có thể làm cho nó tốt hơn. Hãy chuyển sang thư viện tiếp theo.

Mẹo: Xem thêm Các hàm xử lý file trong PHP nếu muốn ghi nội dung vào tệp tin

Bóc tách dữ liệu trang web bằng PHP với Goutte

Goutte là một ứng dụng khách HTTP tuyệt vời khác dành cho PHP, được tạo riêng cho việc tìm kiếm web. Nó được phát triển bởi người tạo ra Symfony Framework và cung cấp một API đẹp để thu thập dữ liệu từ các phản hồi HTML / XML của các trang web.

Dưới đây là một số thành phần mà nó bao gồm để làm cho việc thu thập thông tin web trở nên đơn giản:

Thành phần BrowserKit để mô phỏng hoạt động của trình duyệt web.
Thành phần CssSelector để dịch các truy vấn CSS thành các truy vấn XPath.
Thành phần DomCrawler mang lại sức mạnh của DOMDocument XPath.
Symfony HTTP Client là một thành phần khá mới từ nhóm Symfony.
Cài đặt Goutte qua Composer bằng cách thực hiện lệnh sau trên Console của bạn:

Ví dụ

composer require fabpot/goutte

Khi bạn đã cài đặt gói Goutte, hãy tạo một tệp PHP mới để thực hiện các mã mới của chúng ta - đặt tên là goutte_requests.php .

Trong phần này, cũng giống như với thư viện Guzzle trong phần đầu tiên. Chúng tôi sẽ lấy tên sách từ trang web Books to Scrape bằng cách sử dụng Goutte. Sau đó, chúng ta sẽ lấy giá của nó.

Mã của tệp goutte_requests.php như sau:

Ví dụ

<?php
# lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \Goutte\Client();
$response = $httpClient->request('GET', 'https://books.toscrape.com/');
$titles = $response->evaluate('//ol[@class="row"]//li//article//h3/a');
$prices = $response->evaluate('//ol[@class="row"]//li//article//div[@class="product_price"]//p[@class="price_color"]');
// Chúng ta có thể lưu trữ giá thành một mảng
$priceArray = [];
foreach ($prices as $key => $price) {
$priceArray[] = $price->textContent;
}
// Chúng ta trích xuất các tiêu đề cùng với giá và hiển thị trên trên trình duyệt
foreach ($titles as $key => $title) {
echo $title->textContent . ' @ '. $priceArray[$key] . PHP_EOL;
}

Duyệt file goutte_requests.php bạn sẽ có kết quả:
Bóc tách dữ liệu trang web bằng PHP

Đây là một cách lấy nội dung trang web bằng Goutte. Giờ chúng ta xem xét về một phương pháp khác bằng cách sử dụng thành phần Bộ chọn CSS (CSS Selector) đi kèm với Goutte. Bộ chọn CSS đơn giản hơn so với việc sử dụng XPath được hiển thị trong các phương pháp trước đó.

Tạo một tệp PHP khác, đặt tên là goutte_css_requests.php . Thêm mã sau vào tệp:

Ví dụ

<?php
# lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \Goutte\Client();
$response = $httpClient->request('GET', 'https://books.toscrape.com/');
// lấy giá cho vào 1 mảng
$prices = [];
$response->filter('.row li article div.product_price p.price_color')->each(function ($node) use (&$prices) {
$prices[] = $node->text();
});
// hiển thị tên và giá
$priceIndex = 0;
$response->filter('.row li article h3 a')->each(function ($node) use ($prices, &$priceIndex) {
echo $node->text() . ' @ ' . $prices[$priceIndex] .PHP_EOL;
$priceIndex++;
});

Như bạn có thể thấy, việc sử dụng thành phần Bộ chọn CSS dẫn đến mã rõ ràng hơn và dễ đọc hơn.

Bạn có thể nhận thấy rằng chúng ta đã sử dụng toán tử &. Điều này đảm bảo rằng chúng ta đưa tham chiếu của biến vào vòng lặp “each” chứ không chỉ giá trị của biến. Nếu &$prices được sửa đổi trong vòng lặp, giá trị thực bên ngoài vòng lặp cũng được sửa đổi.

Duyệt file goutte_css_requests.php bạn sẽ thấy kết quả tương tự như kết quả trong ảnh chụp màn hình trước đó:
Bóc tách dữ liệu trang web bằng PHP

Trình lấy thông tin trang web với PHP và Goutte của chúng tôi vẫn đang hoạt động tốt cho đến nay. Hãy đi sâu hơn một chút và xem liệu chúng ta có thể nhấp vào một liên kết và điều hướng đến một trang khác hay không.

Trên trang web demo của chúng ta, Books to Scrape , nếu bạn nhấp vào tiêu đề của một cuốn sách, một trang sẽ hiển thị các chi tiết của cuốn sách, chẳng hạn như:
Bóc tách dữ liệu trang web bằng PHP

Chúng ta muốn xem nếu bạn nhấp vào liên kết từ danh sách sách, điều hướng đến trang chi tiết sách và trích xuất mô tả. Kiểm tra trang để xem những gì chúng ta sẽ nhắm mục tiêu:
Bóc tách dữ liệu trang web bằng PHP
Luồng mục tiêu của chúng ta sẽ là từ phần tử <div class = "content"> , sau đó là <div id = "content_inner"> , sau đó là thẻ <article> chỉ xuất hiện một lần và cuối cùng là thẻ <p> .

Chúng ta có một số thẻ <p> - thẻ có mô tả là thẻ thứ tư bên trong <div class = content> cha. Vì mảng bắt đầu từ 0, chúng ta sẽ nhận được nút ở chỉ mục thứ 3 .

Bây giờ chúng ta biết những gì chúng ta đang nhắm mục tiêu, giờ hãy viết mã.

Trước tiên, hãy thêm gói composer sau để trợ giúp phân tích cú pháp HTML5:

Ví dụ

composer require masterminds/html5

Tiếp theo, sửa đổi tệp goutte_css_requests.php như sau:

Ví dụ

<?php
# lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \Goutte\Client();
$response = $httpClient->request('GET', 'https://books.toscrape.com/');
// cho giá vào 1 mảng
$prices = [];
$response->filter('.row li article div.product_price p.price_color')
->each(function ($node) use (&$prices) {
$prices[] = $node->text();
});
// hiển thị tên, giá và mô tả
$priceIndex = 0;
$response->filter('.row li article h3 a')
->each(function ($node) use ($prices, &$priceIndex, $httpClient) {
$title = $node->text();
$price = $prices[$priceIndex];
//lấy mô tả
$description = $httpClient->click($node->link())
->filter('.content #content_inner article p')->eq(3)->text();
// hiển thị kết quả
echo "{$title} @ {$price} : {$description}\n\n";
$priceIndex++;
});

Duyệt file goutte_css_requests.php bạn sẽ thấy kết quả:
Bóc tách dữ liệu trang web bằng PHP

Sử dụng thành phần Bộ chọn CSS của Goutte và tùy chọn nhấp vào một trang, bạn có thể dễ dàng thu thập dữ liệu toàn bộ trang web với nhiều trang và trích xuất nhiều dữ liệu tùy ý.

Bóc tách nội dung trang web bằng PHP với Simple HTML DOM

Simple HTML DOM là một thư viện quét web PHP tối giản khác mà bạn có thể sử dụng để thu thập thông tin một trang web. Hãy thảo luận về cách bạn có thể sử dụng thư viện này để lấy nội dung một trang web. Cũng giống như trong các ví dụ trước, chúng ta sẽ lấy nội dung trang web Books to Scrape.

Trước khi bạn có thể cài đặt gói, hãy sửa đổi tệp composer.json của bạn và thêm các dòng mã sau ngay bên dưới khối require:{} để tránh gặp lỗi lập phiên bản:

Ví dụ

"minimum-stability": "dev",
"prefer-stable": true

Bây giờ, bạn có thể cài đặt thư viện bằng lệnh sau:

Ví dụ

composer require simplehtmldom/simplehtmldom


Sau khi thư viện được cài đặt, hãy tạo một tệp PHP mới có tên simplehtmldom_requests.php .

Chúng ta đã thảo luận về bố cục của trang web mà chúng ta đang tìm kiếm trong các phần trước. Vì vậy, chúng ta sẽ chỉ đi thẳng vào mã. Thêm mã sau vào tệp simplehtmldom_requests.php :

Ví dụ

<?php
# lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = new \simplehtmldom\HtmlWeb();
$response = $httpClient->load('https://books.toscrape.com/');
// hiển thị tên
echo $response->find('title', 0)->plaintext . PHP_EOL . PHP_EOL;
// cho giá vào 1 mảng
$prices = [];
foreach ($response->find('.row li article div.product_price p.price_color') as $price) {
$prices[] = $price->plaintext;
}
// hiển thị tên và giá
foreach ($response->find('.row li article h3 a') as $key => $title) {
echo "{$title->plaintext} @ {$prices[$key]} \n";
}


Duyệt file simplehtmldom_requests.php bạn sẽ thấy kết quả tương tự các ví dụ trước.

Bạn có thể tìm thêm các phương pháp để thu thập thông tin trang web bằng cách sử dụng thư viện Simple HTML DOM từ các tài liệu API chính thức

Bóc tách dữ liệu trang web bằng PHP với trình duyệt Headless Browser (Symfony Panther)

Trình duyệt Headless là trình duyệt không có giao diện người dùng đồ họa. Các trình duyệt không có đầu cho phép bạn sử dụng thiết bị đầu cuối của mình để tải trang web trong một môi trường tương tự như trình duyệt web. Điều này cho phép bạn viết mã để kiểm soát việc duyệt web như chúng ta vừa thực hiện ở các bước trước.

Vậy tại sao điều này lại cần thiết?

Trong quá trình phát triển web hiện đại, hầu hết các nhà phát triển đều sử dụng các JavaScript web framework. Các framework này tạo ra mã HTML bên trong các trình duyệt. Trong các trường hợp khác, AJAX tự động tải nội dung.

Trong các ví dụ trước, chúng ta đã sử dụng một trang HTML tĩnh, vì vậy đầu ra là nhất quán.

Trong các trường hợp động, khi bạn sử dụng JavaScript và AJAX để tạo HTML, đầu ra của cấu trúc DOM có thể khác nhau rất nhiều. Điều này sẽ làm cho phần mềm lấy nội dung của chúng ta thất bại. Các Headless Browser xuất hiện để xử lý các vấn đề như vậy trong các trang web hiện đại.

Các thư viện Symfony Panther PHP hoạt động tốt với các trình duyệt Headless. Bạn có thể sử dụng thư viện để quét các trang web và chạy thử nghiệm bằng các trình duyệt thực.

Ngoài ra, nó cung cấp các phương pháp tương tự như thư viện Goutte, vì vậy bạn có thể sử dụng nó thay vì Goutte.

Không giống như các thư viện tìm kiếm web trước đây mà chúng ta đã thảo luận trong hướng dẫn này, Panther có thể thực hiện những việc sau:

  • Thực thi mã JavaScript trên các trang web
  • Hỗ trợ kiểm tra trình duyệt từ xa
  • Hỗ trợ tải không đồng bộ các phần tử bằng cách đợi các phần tử khác tải trước khi thực thi một dòng mã
  • Hỗ trợ tất cả các triển khai của Chrome của Firefox
  • Có thể chụp ảnh màn hình
  • Cho phép chạy mã JS tùy chỉnh hoặc các truy vấn XPath của bạn trong ngữ cảnh của trang được tải.

Ở các ví dụ trước chúng ta đã tìm cách bóc tách và lấy thông tin 1 trang web, ở ví dụ này chúng ta sẽ thay đổi khác hơn bằng cách sẽ tải một trang HTML và chụp ảnh màn hình của trang.

Cài đặt Symfony Panther bằng lệnh sau:

Ví dụ

composer require symfony/panther


Tạo một tệp php mới, đặt tên là panther_requests.php . Thêm mã sau vào tệp:

Ví dụ

<?php
# lấy nội dung trang web: https://books.toscrape.com/
require 'vendor/autoload.php';
$httpClient = \Symfony\Component\Panther\Client::createChromeClient();
// đối với ứng dụng khách Firefox, hãy sử dụng dòng bên dưới để thay thế
//$httpClient = \Symfony\Component\Panther\Client::createFirefoxClient();
// nhận kết quả
$response = $httpClient->get('https://books.toscrape.com/');
// chụp ảnh màn hình và lưu trữ trong thư mục hiện tại
$response->takeScreenshot($saveAs = 'books_scrape_homepage.jpg');
// hiển thị tên sách
$response->getCrawler()->filter('.row li article h3 a')
->each(function ($node) {
echo $node->text() . PHP_EOL;
});

Để mã này chạy trên hệ thống của bạn, bạn phải cài đặt trình điều khiển (driver) cho Chrome hoặc Firefox, tùy thuộc vào ứng dụng khách ($httpClient) bạn đã sử dụng trong mã của mình.

May mắn thay, Composer có thể tự động làm điều này cho bạn. Thực thi lệnh sau trong Console của bạn để cài đặt và phát hiện trình điều khiển:

Ví dụ

composer require --dev dbrekelmans/bdi && vendor/bin/bdi detect drivers

Mẹo: Kiểm tra xem trong thư mục của dự án localhost đã có thư mục drivers chưa, nếu chưa có thì bạn cần tải driver cho phiên bản của trình duyệt đang sử dụng nhé
Driver Chrome
Driver Firefox

Bóc tách dữ liệu trang web bằng PHP
Cấu trúc thư mục drivers

Bây giờ bạn có thể duyệt địa chỉ http://localhost/panther_requests.php trên trình duyệt và nó sẽ chụp ảnh màn hình của trang web và lưu trữ trong thư mục hiện tại. Sau đó, nó sẽ hiển thị danh sách các đầu sách từ trang web.

Kết luận

Trong hướng dẫn này, chúng ta đã thảo luận về các thư viện mã nguồn mở PHP khác nhau mà bạn có thể sử dụng để lấy thông tin một trang web.

Nếu bạn đã làm theo hướng dẫn, bạn sẽ có thể tạo một công cụ cơ bản để thu thập thông tin một hoặc hai trang cùng lúc.

Trong bài viết này đã đề cập đến hầu hết các phương pháp bạn có thể sử dụng với các thư viện. Bạn có thể chọn xây dựng dựa trên kiến thức này và tạo ra một công cụ tốt hơn có thể thu thập thông tin hàng nghìn trang. Bạn có thể tải về mã viết trong hướng dẫn này tại đây.

Vui lòng liên hệ nếu bạn có bất kỳ câu hỏi nào.

Bài viết này đã giúp ích cho bạn?

Bài viết mới

Advertisements