Mở đầu
Khi truy cập các trang web hàng ngày, bạn có thể bắt gặp rất nhiều thông tin hữu ích từ danh sách sản phẩm, bảng giá, đến các bài viết, câu trích dẫn truyền cảm hứng. Thay vì sao chép thủ công từng dòng dữ liệu, tại sao không để máy tính làm giúp bạn? Đó chính là lúc bạn cần tìm hiểu về web scraping. Web scraping, còn được gọi là web crawling hoặc web spidering, là quá trình thu thập dữ liệu từ các trang web một cách tự động. Chúng ta sẽ cùng tìm hiểu những kiến thức nền tảng của quá trình này thông qua một ví dụ thực hành thú vị. Mình sẽ hướng dẫn bạn sử dụng Scrapy, một framework Python phổ biến và mạnh mẽ, để thu thập các câu trích dẫn từ trang web “Quotes to Scrape”, được thiết kế riêng cho việc thử nghiệm trình thu thập dữ liệu. Sau khi hoàn tất các bước trong hướng dẫn này, bạn sẽ có một web scraper ( công cụ dùng để làm web scraping) hoạt động hoàn chỉnh, có khả năng duyệt qua từng trang chứa trích dẫn và hiển thị nội dung trực tiếp trên màn hình. Scraper này cũng sẽ dễ dàng mở rộng, cho phép bạn tùy chỉnh hoặc ứng dụng trong các dự án thu thập dữ liệu thực tế sau này. Trước khi bắt đầu xây dựng web scraper đầu tiên của bạn bằng Scrapy, bạn cần chuẩn bị một vài điều kiện tiên quyết bạn .
Chuẩn bị
Đầu tiên, bạn cần một môi trường phát triển Python 3 hoạt động trên máy tính của mình. Điều này có nghĩa là bạn đã cài đặt Python 3 và có thể chạy các lệnh Python từ dòng lệnh hoặc terminal. Nếu bạn chưa cài đặt Python, bạn có thể tải phiên bản mới nhất từ trang chính thức python.org và làm theo hướng dẫn phù hợp với hệ điều hành của bạn.
Ngoài ra, bạn cũng nên cài đặt công cụ quản lý gói pip
, thường đi kèm với Python, để dễ dàng cài đặt thư viện Scrapy và các thư viện khác mà bạn sẽ sử dụng trong quá trình xây dựng scraper. Sau khi chuẩn bị đầy đủ chúng ta cùng nhau thực hành nào.
Bước 1 Tạo một Scraper cơ bản
Scraping là một quy trình gồm hai bước:
- Tìm kiếm và tải về các trang web một cách có hệ thống:
- Đây là bước đầu tiên, nơi scraper của bạn sẽ thực hiện việc duyệt qua các URL (liên kết) để truy cập các trang web mà bạn muốn thu thập dữ liệu từ đó.
- Trích xuất thông tin từ các trang đã tải về
- Sau khi tải về các trang web, bước tiếp theo là tìm ra thông tin bạn cần và trích xuất nó. Dữ liệu có thể là văn bản, hình ảnh, bảng, hoặc bất kỳ dạng thông tin nào được hiển thị trên trang web.
Cả hai bước trên có thể được triển khai theo nhiều cách khác nhau trong nhiều ngôn ngữ lập trình. Bạn có thể tự xây dựng một scraper từ đầu bằng cách sử dụng các module hoặc thư viện có sẵn trong ngôn ngữ của mình, nhưng điều này sẽ đi kèm với một số thử thách khi scraper của bạn trở nên phức tạp hơn.
Chẳng hạn, bạn sẽ cần phải xử lý tính đồng thời (concurrency) để có thể thu thập nhiều trang web cùng lúc. Điều này có thể đòi hỏi bạn phải sử dụng các công cụ như multithreading hoặc asynchronous programming để tối ưu hóa hiệu suất. Thêm vào đó, bạn sẽ muốn biến đổi dữ liệu thu thập được thành các định dạng khác nhau như CSV, XML hoặc JSON để dễ dàng sử dụng và phân tích.
Ngoài ra, một thách thức khác đó là việc xử lý các trang web yêu cầu các cài đặt hoặc mẫu truy cập đặc biệt. Một số trang web có thể yêu cầu bạn phải thực hiện các bước như giả lập một trình duyệt (user-agent), xử lý các Captchas, hoặc thậm chí yêu cầu bạn phải đăng nhập để truy cập dữ liệu. Trong những tình huống như vậy, việc sử dụng Scrapy có thể giúp bạn giải quyết tất cả những vấn đề này một cách dễ dàng và hiệu quả.
Scrapy là một trong những thư viện Python phổ biến và mạnh mẽ nhất dành cho web scraping. Nó cho chúng ta một phương pháp “batteries included” (bao gồm sẵn tất cả công cụ cần thiết), nghĩa là Scrapy xử lý nhiều chức năng phổ biến mà mọi scraper đều cần, giúp các lập trình viên không phải làm lại từ đầu mỗi khi xây dựng một scraper. Với Scrapy, bạn có thể dễ dàng quản lý việc tải trang, xử lý dữ liệu, chuyển đổi định dạng, và nhiều tính năng khác, giúp tiết kiệm thời gian và công sức cho việc phát triển scraper. Scrapy, giống như hầu hết các thư viện Python, đều có sẵn trên PyPI (Python Package Index), một kho lưu trữ cộng đồng chứa tất cả các phần mềm Python đã được phát hành. Bạn có thể dễ dàng cài đặt Scrapy chỉ bằng một lệnh đơn giản sau:
pip install scrapy
Nếu bạn gặp bất kỳ sự cố nào khi cài đặt hoặc muốn cài đặt Scrapy mà không sử dụng pip, hãy tham khảo tài liệu cài đặt chính thức. Sau khi cài đặt Scrapy, hãy tạo một thư mục mới cho dự án của chúng ta. Bạn có thể thực hiện việc này trong terminal bằng cách chạy:
mkdir quote-scraper
Bây giờ, hãy điều hướng đến thư mục mới mà bạn vừa tạo:
cd quote-scraper
Tiếp theo, hãy tạo một tệp Python mới cho scraper của chúng ta, gọi là scraper.py
. Tất cả mã nguồn của chúng ta trong hướng dẫn này sẽ được đặt trong tệp này. Bạn có thể sử dụng bất kỳ phần mềm chỉnh sửa mã nào mà bạn thích để tạo tệp này, ví dụ như Visual Studio Code, Sublime Text, hoặc PyCharm. Việc tổ chức mã của bạn vào các tệp riêng biệt sẽ giúp dự án dễ dàng quản lý và mở rộng trong tương lai. Sau khi tạo xong tệp scraper.py
, bạn đã sẵn sàng để bắt đầu viết mã cho scraper của mình.
Bắt đầu dự án bằng cách tạo một scraper cơ bản sử dụng Scrapy làm nền tảng. Để làm điều này, bạn cần tạo một lớp Python kế thừa từ scrapy.Spider
, một lớp spider cơ bản được cung cấp bởi Scrapy. Lớp này sẽ có hai thuộc tính bắt buộc:
- name: Tên của spider.
- start_urls: Một danh sách các URL mà spider sẽ bắt đầu crawl.
Dưới đây là ví dụ về cách định nghĩa một spider cơ bản:
import scrapy
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
Hãy cùng phân tích đoạn mã này từng dòng một:
Đầu tiên, chúng ta thêmscrapy
để có thể sử dụng các lớp mà thư viện cung cấp.
Tiếp theo, chúng ta lấy lớp Spider
được cung cấp bởi Scrapy và tạo một lớp con từ lớp đó. Hãy tưởng tượng lớp con là một dạng chuyên biệt hơn của lớp cha. Lớp Spider
có các phương thức và hành vi định nghĩa cách theo dõi các URL và trích xuất dữ liệu từ các trang tìm thấy, nhưng nó không biết phải tìm ở đâu hoặc dữ liệu nào cần được trích xuất. Bằng cách tạo lớp con từ Spider
, chúng ta có thể cung cấp cho nó thông tin đó. Cuối cùng, chúng ta đặt tên cho lớp là QuoteSpider
và cung cấp cho trình thu thập dữ liệu một URL duy nhất để bắt đầu: https://quotes.toscrape.com
. Nếu bạn mở đường dẫn này trong trình duyệt, bạn sẽ thấy một trang hiển thị các câu trích dẫn nổi tiếng đó chỉ là trang đầu tiên trong số nhiều trang chứa dữ liệu tương tự.
Giờ là lúc bạn kiểm tra hoạt động của scraper mà mình vừa tạo. Thông thường, bạn sẽ chạy các file Python bằng lệnh kiểu như python path/to/file.py
. Tuy nhiên, Scrapy cung cấp một giao diện dòng lệnh riêng giúp bạn dễ dàng quản lý và khởi chạy các spider mà không cần viết thêm đoạn mã khởi động thủ công.
Để bắt đầu scraper của bạn, hãy sử dụng lệnh sau trong thư mục chứa file scraper.py
:
scrapy runspider scraper.py
Lệnh này sẽ tự động tìm lớp spider bạn đã định nghĩa trong file, khởi chạy nó, và bắt đầu thu thập dữ liệu từ URL mà bạn đã cung cấp. Nếu mọi thứ được cấu hình đúng, bạn sẽ thấy Scrapy hiển thị thông tin tiến trình trên màn hình và bắt đầu tải nội dung từ https://quotes.toscrape.com
. Đây là bước đầu tiên để bạn thấy scraper hoạt động thực tế.
Output
2022-12-02 10:30:08 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet Password: b4d94e3a8d22ede1
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
...
'scrapy.extensions.logstats.LogStats']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
...
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
...
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider opened
2022-12-02 10:30:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2022-12-02 10:49:32 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com> (referer: None)
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Closing spider (finished)
2022-12-02 10:30:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 226,
...
'start_time': datetime.datetime(2022, 12, 2, 18, 30, 8, 492403)}
2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider closed (finished)
Kết quả cho chúng ta khá nhiều thông tin được hiển thị, vì vậy hãy cùng nhau phân tích từng phần để hiểu rõ hơn về những gì xảy ra phía sau.
- Khi bạn chạy lệnh
scrapy runspider
, Scrapy bắt đầu khởi tạo scraper của bạn. - Trong quá trình này, nó nạp thêm các thành phần và phần mở rộng cần thiết để có thể xử lý việc gửi yêu cầu HTTP và trích xuất dữ liệu từ các URL.
- Sau đó, Scrapy sử dụng URL bạn đã chỉ định trong danh sách
start_urls
để gửi yêu cầu đầu tiên. Nó lấy về nội dung HTML giống như khi bạn mở URL đó bằng trình duyệt. - Tiếp theo, HTML được chuyển đến phương thức
parse
mặc định. Đây là phương thức Scrapy sử dụng để xử lý nội dung trang khi chưa có phương thức nào khác được định nghĩa. Tuy nhiên, vì bạn chưa viết phương thứcparse
của riêng mình, nên spider không thực hiện thêm hành động nào và kết thúc ngay sau khi tải trang.
Ở bước tiếp theo, bạn sẽ định nghĩa phương thức parse
để Scrapy biết chính xác bạn muốn thu thập phần nào từ nội dung trang đã tải.
Bước 2 Trích xuất dữ liệu từ một trang
Chúng ta vừa tạo một chương trình cơ bản có thể tải về nội dung của một trang web, nhưng vẫn chưa thực hiện hành động “scraping” hay “spidering” thực thụ. Bây giờ, đã đến lúc thêm logic để nó bắt đầu trích xuất dữ liệu có ý nghĩa. Khi quan sát trang web mà chúng ta muốn thu thập dữ liệu, bạn sẽ thấy nó có cấu trúc khá đều đặn và nhất quán như sau:
- Một tiêu đề ở trên mỗi trang.
- Liên kết để đăng nhập.
- Sau đó là các trích dẫn, được hiển thị dưới dạng bảng hoặc danh sách có thứ tự. Mỗi trích dẫn có định dạng tương tự nhau.
Khi bắt đầu viết một scraper, điều đầu tiên bạn cần làm là xem xét mã nguồn HTML của trang web và làm quen với cấu trúc của nó. Việc hiểu rõ trang web được tổ chức như thế nào sẽ giúp bạn xác định chính xác nơi cần trích xuất dữ liệu. Dưới đây là một phiên bản đơn giản hóa của đoạn HTML chứa các trích dẫn, đã được lược bỏ bớt các thẻ không liên quan để dễ đọc hơn:
quotes.toscrape.com<body>
...
<div class="quote" itemscope itemtype="<http://schema.org/CreativeWork>">
<span class="text" itemprop="text">“I have not failed. I've just found 10,000 ways that won't work.”</span>
<span>by <small class="author" itemprop="author">Thomas A. Edison</small>
<a href="/author/Thomas-A-Edison">(about)</a>
</span>
<div class="tags">
Tags:
<meta class="keywords" itemprop="keywords" content="edison,failure,inspirational,paraphrased" / >
<a class="tag" href="/tag/edison/page/1/">edison</a>
<a class="tag" href="/tag/failure/page/1/">failure</a>
<a class="tag" href="/tag/inspirational/page/1/">inspirational</a>
<a class="tag" href="/tag/paraphrased/page/1/">paraphrased</a>
</div>
</div>
...
</body>
Để thu thập dữ liệu từ trang web này, bạn sẽ cần thực hiện hai bước đơn giản nhưng quan trọng:
Bước 1 Bạn cần xác định từng khối chứa trích dẫn bằng cách tìm kiếm các phần tử HTML và class phù hợp.
Bước 2 Bạn sẽ cần đi sâu vào từng khối trích dẫn này để trích xuất chính xác dữ liệu.
Scrapy thu thập dữ liệu dựa trên các selector mà bạn cung cấp. Selector là những mẫu (pattern) cho phép bạn xác định và tìm kiếm một hoặc nhiều phần tử HTML trên trang web, từ đó trích xuất nội dung bên trong chúng. Đây là một bước rất quan trọng trong quy trình scraping vì trang web thường có cấu trúc lặp lại và việc sử dụng selector giúp bạn truy cập chính xác đến dữ liệu bạn cần.
Scrapy hỗ trợ hai loại selector phổ biến là CSS selectors và XPath selectors. Cả hai đều có cú pháp khác nhau nhưng đều rất mạnh mẽ và linh hoạt, cho phép bạn truy xuất nội dung từ bất kỳ phần tử HTML nào, cho dù nó nằm sâu trong DOM hoặc được lồng ghép nhiều lớp.
Thông thường, CSS selectors dễ đọc và viết hơn nếu bạn đã quen với cách dùng trong các tệp HTML hoặc khi viết CSS. Trong khi đó, XPath lại cực kỳ mạnh mẽ khi bạn cần duyệt qua các cấu trúc phức tạp hoặc truy cập các thuộc tính cụ thể.
Chúng ta sẽ sử dụng CSS selectors trong giai đoạn này. Nếu bạn nhìn vào phần HTML của trang web, bạn sẽ thấy rằng mỗi trích dẫn (quote) đều được bao quanh bởi một thẻ có lớp (class) là quote
. Bây giờ, chúng ta sẽ tạo một phương thức mới trong lớp spider của mình có tên là parse
. Phương thức này sẽ được Scrapy gọi mặc định sau khi tải xong nội dung của một trang. Trong phương thức này, chúng ta sẽ sử dụng selector.quote
để truy xuất danh sách các phần tử trích dẫn từ đối tượng response
. Đoạn mã cơ bản sẽ trông như sau:
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
for quote in response.css(QUOTE_SELECTOR):
pass
Đoạn mã này lấy tất cả các tập hợp trên trang và lặp lại chúng để trích xuất dữ liệu. Bây giờ hãy trích xuất dữ liệu từ các trích dẫn đó để chúng ta có thể hiển thị nội dung cần thiết.
Khi quan sát lại mã nguồn HTML của trang mà chúng ta đang phân tích, bạn sẽ nhận thấy rằng nội dung của mỗi câu trích dẫn được đặt bên trong một thẻ <span>
có class là text
. Còn tên tác giả của trích dẫn đó nằm trong một thẻ <small>
với class là author
. Nhờ việc xác định chính xác vị trí và đặc điểm của các phần tử HTML này, chúng ta có thể dễ dàng sử dụng Scrapy để trích xuất dữ liệu một cách chính xác và có cấu trúc. Đây là bước quan trọng giúp scraper phân biệt và lấy đúng thông tin từ trang web.
quotes.toscrape.com ...
<span class="text" itemprop="text">“I have not failed. I've just found 10,000 ways that won't work.”</span>
<span>by <small class="author" itemprop="author">Thomas A. Edison</small>
...
Đối tượng quote
mà chúng ta đang sử dụng trong vòng for cũng có phương thức css
, cho phép bạn truyền vào một selector để tìm các phần tử con bên trong nó. Điều này giúp việc trích xuất dữ liệu trở nên linh hoạt và chính xác hơn.
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
}
Lưu ý: Dấu phẩy ở cuối sau extract_first()
không phải là lỗi đánh máy. Trong Python, việc đặt dấu phẩy ở cuối một mục trong dictionary (hoặc tuple, list) là cú pháp hợp lệ. Đây cũng là một thói quen tốt để dễ dàng thêm các phần tử mới sau này mà không cần chỉnh sửa dòng trước, giúp mã dễ bảo trì hơn.
Bạn sẽ thấy hai điều đang diễn ra trong đoạn mã này:
- Đầu tiên, chúng ta sử dụng
::text
ở cuối các CSS selector để chỉ lấy phần văn bản bên trong các thẻ HTML, chứ không lấy toàn bộ thẻ. - Thứ hai, chúng ta gọi phương thức
extract_first()
trên kết quả trả về từquote.css(TEXT_SELECTOR)
vì chúng ta chỉ muốn lấy phần tử đầu tiên chính là nội dung văn bản cần thiết. Việc sử dụngextract_first()
giúp ta nhận được một chuỗi văn bản (string) thay vì một danh sách các phần tử HTML. Bây giờ chúng ta lưu lại tệp và chạy lại scraper:
scrapy runspider scraper.py
Output
...
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein'}
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen'}
2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe'}
...
Hãy tiếp tục mở rộng trình thu thập dữ liệu của chúng ta bằng cách thêm các selector mới để lấy liên kết đến trang thông tin về tác giả và các thẻ (tags) liên quan đến câu trích dẫn. Khi quan sát mã HTML của từng câu trích dẫn trên trang, bạn sẽ thấy rằng:
- Liên kết đến trang tiểu sử tác giả nằm trong một thẻ
<a>
với thuộc tínhhref
, được đặt bên trong phần tử có class làquote
. - Các thẻ (tags) liên quan đến mỗi câu trích dẫn nằm trong phần tử
div
với classtags
, trong đó mỗi thẻ là một phần tử<a>
với classtag
.
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': '<https://quotes.toscrape.com>' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
Lưu thay đổi và chạy lại trình thu thập dữ liệu:
scrapy runspider scraper.py
Output
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein', 'about': '<https://quotes.toscrape.com/author/Albert-Einstein>', 'tags': ['inspirational', 'life', 'live', 'miracle', 'miracles']}
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen', 'about': '<https://quotes.toscrape.com/author/Jane-Austen>', 'tags': ['aliteracy', 'books', 'classic', 'humor']}
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com>
{'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe', 'about': '<https://quotes.toscrape.com/author/Marilyn-Monroe>', 'tags': ['be-yourself', 'inspirational']}
Bây giờ, hãy biến trình thu thập dữ liệu này thành một spider thực thụ có khả năng theo dõi các liên kết và di chuyển qua nhiều trang.Cho đến thời điểm hiện tại, scraper của bạn chỉ xử lý nội dung từ một trang duy nhất trang đầu tiên của danh sách trích dẫn. Nhưng nếu bạn truy cập trang quotes.toscrape.com, bạn sẽ thấy có các liên kết Next ở cuối mỗi trang, cho phép bạn tiếp tục duyệt các trích dẫn tiếp theo. Để mở rộng khả năng thu thập, chúng ta cần dạy spider cách tìm và theo dõi những liên kết này.
Bước 3 Thu thập nhiều trang
Bạn đã trích xuất thành công dữ liệu từ trang đầu tiên, nhưng scraper của bạn vẫn chưa đi xa hơn để thu thập thông tin từ các trang tiếp theo. Mục tiêu thực sự của một spider là phát hiện và lần theo các liên kết đến những trang khác, từ đó tiếp tục thu thập dữ liệu.
Nếu bạn để ý, cả đầu và cuối mỗi trang trên trang web quotes.toscrape.com đều có một dấu mũi tên nhỏ (>) đây chính là liên kết điều hướng đến trang tiếp theo trong danh sách kết quả. Trong mã HTML, liên kết này được đặt bên trong một thẻ <li>
với lớp next
, như sau:
quotes.toscrape.com...
<nav>
<ul class="pager">
<li class="next">
<a href="/page/2/">Next <span aria-hidden="true">→</span></a>
</li>
</ul>
</nav>
...
Tất cả những gì chúng ta phải làm là yêu cầu trình thu thập dữ liệu theo liên kết đó nếu nó tồn tại.
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
NEXT_SELECTOR = '.next a::attr("href")'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': '<https://quotes.toscrape.com>' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
next_page = response.css(NEXT_SELECTOR).extract_first()
if next_page:
yield scrapy.Request(response.urljoin(next_page))
Đầu tiên, chúng ta cần định nghĩa một selector để tìm liên kết đến “trang kế tiếp”. Sau đó, ta trích xuất kết quả đầu tiên khớp với selector đó và kiểm tra xem liên kết có tồn tại hay không. Nếu có, ta sẽ tạo một scrapy.Request
mới, đối tượng này báo cho Scrapy biết rằng nó cần gửi thêm một yêu cầu HTTP tới đường dẫn vừa tìm được và tiếp tục quá trình phân tích nội dung ở đó.
Nhờ cơ chế này, spider sẽ tự động truy cập từng trang một cách tuần tự: khi truy cập trang kế tiếp, nó lại tiếp tục tìm đường dẫn tới trang tiếp theo nữa. Quá trình này sẽ lặp lại cho đến khi không còn tìm thấy liên kết nào nữa. Đây chính là nguyên lý cốt lõi của web crawling: phát hiện và lần theo các liên kết.
Trong ví dụ của chúng ta, việc thu thập dữ liệu diễn ra theo chuỗi tuyến tính đơn giản từ trang đầu tiên, sang trang thứ hai, rồi trang thứ ba, v.v. Tuy nhiên, với một spider mạnh hơn, bạn hoàn toàn có thể mở rộng quy trình này để truy cập các liên kết khác như trang chủ đề, trang kết quả tìm kiếm, hay bất kỳ URL nào mà bạn quan tâm. Điều này khiến Scrapy trở thành một công cụ cực kỳ linh hoạt và mạnh mẽ cho mọi nhu cầu khai thác dữ liệu từ web.
Bây giờ, khi bạn lưu lại đoạn mã và chạy lại spider, bạn sẽ thấy rằng chương trình không còn dừng lại sau khi duyệt xong trang đầu tiên nữa. Thay vào đó, spider sẽ tiếp tục truy cập qua tất cả các trang kế tiếp, cho đến khi thu thập đủ toàn bộ 100 câu trích dẫn được chia đều trên 10 trang.
Mặc dù đây chỉ là một lượng dữ liệu tương đối nhỏ trong bối cảnh thực tế, nhưng bạn đã nắm được quy trình tự động phát hiện và lần theo các trang mới để thu thập thông tin. Đây là nền tảng quan trọng để xây dựng những công cụ khai thác dữ liệu quy mô lớn và hiệu quả hơn trong tương lai. Sau đây là mã hoàn chỉnh của chúng ta cho hướng dẫn này:
import scrapy
class QuoteSpider(scrapy.Spider):
name = 'quote-spdier'
start_urls = ['<https://quotes.toscrape.com>']
def parse(self, response):
QUOTE_SELECTOR = '.quote'
TEXT_SELECTOR = '.text::text'
AUTHOR_SELECTOR = '.author::text'
ABOUT_SELECTOR = '.author + a::attr("href")'
TAGS_SELECTOR = '.tags > .tag::text'
NEXT_SELECTOR = '.next a::attr("href")'
for quote in response.css(QUOTE_SELECTOR):
yield {
'text': quote.css(TEXT_SELECTOR).extract_first(),
'author': quote.css(AUTHOR_SELECTOR).extract_first(),
'about': '<https://quotes.toscrape.com>' +
quote.css(ABOUT_SELECTOR).extract_first(),
'tags': quote.css(TAGS_SELECTOR).extract(),
}
next_page = response.css(NEXT_SELECTOR).extract_first()
if next_page:
yield scrapy.Request(
response.urljoin(next_page),
)
Giờ đây, bạn đã có một spider hoàn chỉnh có thể tự động thu thập dữ liệu từ nhiều trang trên một website. Bạn đã học cách xác định cấu trúc HTML, sử dụng các selector để trích xuất dữ liệu, và làm thế nào để lần theo các liên kết để mở rộng phạm vi thu thập. Đây là bước đệm tuyệt vời để bạn chuyển sang những dự án phức tạp hơn hoặc tích hợp quy trình này vào các ứng dụng thực tế.
Tổng kết
Chúng ta vừa cùng nhau hoàn thành một hành trình thú vị trong thế giới của web scraping với Scrapy và Python 3. Từ việc tạo một scraper cơ bản, hiểu cách sử dụng selectors, đến việc biến scraper đó thành một spider có thể tự động lần theo các trang, bạn đã nắm được những kiến thức cốt lõi để bắt đầu xây dựng các công cụ thu thập dữ liệu mạnh mẽ từ web. Dù đây chỉ là một ví dụ đơn giản với một trang thử nghiệm, nhưng quy trình bạn vừa trải qua có thể dễ dàng mở rộng cho những dự án thực tế hơn, từ việc thu thập dữ liệu sản phẩm, tổng hợp tin tức đến phân tích mạng xã hội hay tự động hóa các tác vụ dữ liệu lặp đi lặp lại. Scrapy mang đến sự mạnh mẽ, linh hoạt và hiệu quả nhưng sức mạnh thật sự nằm ở cách bạn vận dụng nó để giải quyết vấn đề của riêng mình. Vì vậy, đừng ngần ngại thử nghiệm thêm các tính năng như pipelines, middleware hay xuất dữ liệu ra định dạng JSON hoặc CSV. Chúc bạn thành công với những dự án thu thập dữ liệu tiếp theo.