Cách xây dựng một trình thu thập dữ liệu sản phẩm của Amazon bằng Node.js

theanh

Administrator
Nhân viên
Bạn đã bao giờ rơi vào tình huống cần phải hiểu rõ thị trường của một sản phẩm cụ thể chưa? Có thể bạn đang tung ra một số phần mềm và cần biết cách định giá sản phẩm đó. Hoặc có thể bạn đã có sản phẩm của riêng mình trên thị trường và muốn xem nên thêm những tính năng nào để có lợi thế cạnh tranh. Hoặc có thể bạn chỉ muốn mua một thứ gì đó cho bản thân và muốn đảm bảo rằng bạn nhận được giá trị tốt nhất cho số tiền bỏ ra.

Tất cả những tình huống này đều có một điểm chung: bạn cần dữ liệu chính xác để đưa ra quyết định đúng đắn. Trên thực tế, chúng còn có một điểm chung khác. Tất cả các tình huống đều có thể được hưởng lợi từ việc sử dụng trình thu thập dữ liệu web.

Thu thập dữ liệu web là hoạt động trích xuất lượng lớn dữ liệu web thông qua việc sử dụng phần mềm. Vì vậy, về bản chất, đây là một cách tự động hóa quy trình tẻ nhạt là nhấn "sao chép" rồi "dán" 200 lần. Tất nhiên, một con bot có thể làm điều đó trong thời gian bạn đọc câu này, vì vậy nó không chỉ bớt nhàm chán mà còn nhanh hơn rất nhiều.

Nhưng câu hỏi hóc búa là: tại sao ai đó lại muốn thu thập dữ liệu từ các trang của Amazon?

Bạn sắp tìm ra câu trả lời rồi! Nhưng trước hết, tôi muốn làm rõ một điều ngay bây giờ — mặc dù hành động thu thập dữ liệu công khai là hợp pháp, Amazon có một số biện pháp để ngăn chặn hành động này trên các trang của họ. Vì vậy, tôi khuyên bạn luôn phải chú ý đến trang web khi thu thập dữ liệu, cẩn thận không làm hỏng trang web và tuân thủ các nguyên tắc đạo đức.

Đọc sách được đề xuất: “Hướng dẫn thu thập dữ liệu trang web động có đạo đức bằng Node.js và Puppeteer” của Andreas Altheimer

Tại sao bạn nên trích xuất dữ liệu sản phẩm của Amazon​

Là nhà bán lẻ trực tuyến lớn nhất hành tinh, có thể nói rằng nếu bạn muốn mua thứ gì đó, bạn có thể mua được trên Amazon. Vì vậy, không cần phải nói thì bạn cũng biết trang web này là một kho báu dữ liệu lớn như thế nào.
Chúng ta hãy bắt đầu với tình huống đầu tiên. Trừ khi bạn đã thiết kế một sản phẩm mới thực sự sáng tạo, thì khả năng là bạn đã có thể tìm thấy thứ gì đó ít nhất là tương tự trên Amazon. Việc thu thập các trang sản phẩm đó có thể mang lại cho bạn dữ liệu vô giá như:
  • Chiến lược định giá của đối thủ cạnh tranh
    Vì vậy, bạn có thể điều chỉnh giá của mình để cạnh tranh và hiểu cách những người khác xử lý các giao dịch khuyến mại;
  • Ý kiến của khách hàng
    Để xem cơ sở khách hàng tương lai của bạn quan tâm nhất đến điều gì và cách cải thiện trải nghiệm của họ;
  • Các tính năng phổ biến nhất
    Để xem đối thủ cạnh tranh của bạn cung cấp những gì để biết chức năng nào là quan trọng và chức năng nào có thể để lại sau đó.
Về bản chất, Amazon có mọi thứ bạn cần để phân tích sâu về thị trường và sản phẩm. Bạn sẽ chuẩn bị tốt hơn để thiết kế, ra mắt và mở rộng dòng sản phẩm của mình với dữ liệu đó.

Kịch bản thứ hai có thể áp dụng cho cả doanh nghiệp và người dùng thông thường. Ý tưởng này khá giống với những gì tôi đã đề cập trước đó. Bạn có thể thu thập giá, tính năng và đánh giá của tất cả các sản phẩm bạn có thể chọn và do đó, bạn sẽ có thể chọn sản phẩm mang lại nhiều lợi ích nhất với mức giá thấp nhất. Rốt cuộc, ai mà không thích một giao dịch tốt?

Không phải tất cả các sản phẩm đều xứng đáng được chú ý đến mức độ chi tiết này, nhưng nó có thể tạo ra sự khác biệt lớn đối với những giao dịch mua đắt tiền. Thật không may, mặc dù lợi ích rất rõ ràng, nhưng cũng có nhiều khó khăn đi kèm với việc thu thập dữ liệu từ Amazon.

Những thách thức khi thu thập dữ liệu sản phẩm từ Amazon​

Không phải tất cả các trang web đều giống nhau. Theo nguyên tắc chung, trang web càng phức tạp và phổ biến thì càng khó thu thập dữ liệu. Bạn còn nhớ khi tôi nói rằng Amazon là trang thương mại điện tử nổi bật nhất không? Vâng, điều đó khiến nó trở nên cực kỳ phổ biến và khá phức tạp.

Trước hết, Amazon biết cách các bot thu thập dữ liệu hoạt động, vì vậy trang web đã có các biện pháp đối phó. Cụ thể, nếu trình thu thập dữ liệu tuân theo một mẫu có thể dự đoán được, gửi yêu cầu theo các khoảng thời gian cố định, nhanh hơn con người hoặc với các tham số gần như giống hệt nhau, Amazon sẽ nhận thấy và chặn IP. Proxy có thể giải quyết vấn đề này, nhưng tôi không cần chúng vì chúng ta sẽ không thu thập quá nhiều trang trong ví dụ.

Tiếp theo, Amazon cố tình sử dụng các cấu trúc trang khác nhau cho các sản phẩm của họ. Nghĩa là, nếu bạn kiểm tra các trang cho các sản phẩm khác nhau, rất có thể bạn sẽ tìm thấy sự khác biệt đáng kể về cấu trúc và thuộc tính của chúng. Lý do đằng sau điều này khá đơn giản. Bạn cần điều chỉnh mã của trình thu thập dữ liệu cho một hệ thống cụ thể và nếu bạn sử dụng cùng một tập lệnh trên một loại trang mới, bạn sẽ phải viết lại một số phần của trang đó. Vì vậy, về cơ bản, họ khiến bạn phải làm việc nhiều hơn cho dữ liệu.

Cuối cùng, Amazon là một trang web lớn. Nếu bạn muốn thu thập một lượng lớn dữ liệu, việc chạy phần mềm thu thập dữ liệu trên máy tính có thể mất quá nhiều thời gian so với nhu cầu của bạn. Vấn đề này càng được củng cố hơn nữa bởi thực tế là nếu chạy quá nhanh, trình thu thập dữ liệu của bạn sẽ bị chặn. Vì vậy, nếu bạn muốn có nhiều dữ liệu nhanh chóng, bạn sẽ cần một trình thu thập dữ liệu thực sự mạnh mẽ.

Được rồi, nói về vấn đề như vậy là đủ rồi, hãy tập trung vào giải pháp!

Cách xây dựng trình thu thập dữ liệu web cho Amazon​

Để đơn giản, chúng ta sẽ thực hiện từng bước để viết mã. Bạn có thể thoải mái làm việc song song với hướng dẫn.

Tìm kiếm dữ liệu chúng ta cần​

Vì vậy, đây là một tình huống: Tôi sẽ chuyển đến một nơi mới trong vài tháng nữa và tôi sẽ cần một vài giá mới để đựng sách và tạp chí. Tôi muốn biết tất cả các lựa chọn của mình và có được thỏa thuận tốt nhất có thể. Vì vậy, hãy đến thị trường Amazon, tìm kiếm "shelves" và xem chúng ta nhận được gì.

URL cho tìm kiếm này và trang chúng ta sẽ thu thập là tại đây.



Được rồi, chúng ta hãy xem lại những gì chúng ta có ở đây. Chỉ cần liếc qua trang, chúng ta có thể có được một bức tranh tốt về:
  • các kệ trông như thế nào;
  • gói hàng bao gồm những gì;
  • khách hàng đánh giá chúng như thế nào;
  • giá của chúng;
  • liên kết đến sản phẩm;
  • gợi ý về một lựa chọn thay thế rẻ hơn cho một số mặt hàng.
Nhiều hơn những gì chúng ta có thể yêu cầu!

Sắm các công cụ cần thiết​

Hãy đảm bảo rằng chúng ta đã cài đặt và định cấu hình tất cả các công cụ sau trước khi tiếp tục bước tiếp theo.
  • Chrome
    Chúng ta có thể tải xuống từ tại đây.
  • VSCode
    Làm theo hướng dẫn trên trang này để cài đặt trên thiết bị cụ thể của bạn.
  • Node.js
    Trước khi bắt đầu sử dụng Axios hoặc Cheerio, chúng ta cần cài đặt Node.js và Node Package Manager. Cách dễ nhất để cài đặt Node.js và NPM là lấy một trong các trình cài đặt từ nguồn chính thức của Node.Js và chạy nó.
Bây giờ, hãy tạo một dự án NPM mới. Tạo một thư mục mới cho dự án và chạy lệnh sau:
Mã:
npm init -y
Để tạo trình thu thập dữ liệu web, chúng ta cần cài đặt một số phụ thuộc trong dự án của mình:
  • Cheerio
    Một thư viện mã nguồn mở giúp chúng ta trích xuất thông tin hữu ích bằng cách phân tích cú pháp đánh dấu và cung cấp API để thao tác dữ liệu kết quả. Cheerio cho phép chúng ta chọn các thẻ của tài liệu HTML bằng cách sử dụng bộ chọn: $("div"). Bộ chọn cụ thể này giúp chúng ta chọn tất cả các phần tử trên một trang. Để cài đặt Cheerio, vui lòng chạy lệnh sau trong thư mục dự án:
Mã:
npm install cheerio
  • Axios
    Một thư viện JavaScript được sử dụng để tạo các yêu cầu HTTP từ Node.js.
Mã:
npm install axios

Kiểm tra nguồn trang​

Trong các bước sau, chúng ta sẽ tìm hiểu thêm về cách thông tin được sắp xếp trên trang. Ý tưởng là để hiểu rõ hơn về những gì chúng ta có thể trích xuất từ nguồn của mình.

Các công cụ dành cho nhà phát triển giúp chúng ta khám phá Mô hình đối tượng tài liệu (DOM) của trang web một cách tương tác. Chúng ta sẽ sử dụng các công cụ dành cho nhà phát triển trong Chrome, nhưng bạn có thể sử dụng bất kỳ trình duyệt web nào mà bạn cảm thấy thoải mái.

Chúng ta hãy mở nó bằng cách nhấp chuột phải vào bất kỳ đâu trên trang và chọn tùy chọn "Kiểm tra":



Thao tác này sẽ mở ra một cửa sổ mới chứa mã nguồn của trang. Như chúng tôi đã nói trước đây, chúng tôi đang tìm cách thu thập thông tin trên mọi kệ.



Như chúng ta có thể thấy từ ảnh chụp màn hình ở trên, các vùng chứa chứa tất cả dữ liệu có các lớp sau:
Mã:
sg-col-4-of-12 s-result-item s-asin sg-col-4-of-16 sg-col sg-col-4-of-20
Trong bước tiếp theo, chúng ta sẽ sử dụng Cheerio để chọn tất cả các phần tử chứa dữ liệu chúng ta cần.

Lấy dữ liệu​

Sau khi cài đặt tất cả các phần phụ thuộc được trình bày ở trên, hãy tạo một tệp index.js mới và nhập các dòng mã sau:
Mã:
const axios = require("axios");const cheerio = require("cheerio");const fetchShelves = async () => { try { const response = await axios.get('https://www.amazon.com/s?crid=36QNR0DBY6M7J&k=shelves&ref=glow_cls&refresh=1&sprefix=s%2Caps%2C309'); const html = response.data; const $ = cheerio.load(html); const shelves = []; $('div.sg-col-4-of-12.s-result-item.s-asin.sg-col-4-of-16.sg-col.sg-col-4-of-20').each((_idx, el) => { const shelf = $(el) const title = shelf.find('span.a-size-base-plus.a-color-base.a-text-normal').text() shelves.push(title) }); return shelves; } catch (lỗi) { throw lỗi; }};fetchShelves().then((shelves) => console.log(shelves));
Như chúng ta có thể thấy, chúng ta nhập các phụ thuộc cần thiết vào hai dòng đầu tiên, sau đó chúng ta tạo một hàm fetchShelves(), sử dụng Cheerio, để lấy tất cả các phần tử chứa thông tin sản phẩm của chúng ta từ trang.

Nó lặp lại từng phần tử và đẩy chúng vào một mảng trống để có được kết quả được định dạng tốt hơn.

Hàm fetchShelves() sẽ chỉ trả về tiêu đề của sản phẩm tại thời điểm này, vì vậy hãy lấy phần thông tin còn lại mà chúng ta cần. Vui lòng thêm các dòng mã sau đây sau dòng mà chúng ta đã định nghĩa biến title.
Mã:
const image = shelf.find('img.s-image').attr('src')const link = shelf.find('a.a-link-normal.a-text-normal').attr('href')const reviews = shelf.find('div.a-section.a-spacing-none.a-spacing-top-micro > div.a-row.a-size-small').children('span').last().attr('aria-label')const stars = shelf.find('div.a-section.a-spacing-none.a-spacing-top-micro > div > span').attr('aria-label')const price = shelf.find('span.a-price > span.a-offscreen').text() let element = { title, image, link: `https://amazon.com${link}`, price, } if (reviews) { element.reviews = reviews } if (stars) { element.stars = stars }
Và thay thế shelves.push(title) bằng shelves.push(element).

Bây giờ chúng ta đang chọn tất cả thông tin cần thiết và thêm chúng vào một đối tượng mới có tên là element. Sau đó, mỗi phần tử được đẩy vào mảng shelves để có được danh sách các đối tượng chỉ chứa dữ liệu mà chúng ta đang tìm kiếm.

Đây là cách một đối tượng shelf trông như thế nào trước khi được thêm vào danh sách của chúng ta:
Mã:
{ title: 'Kệ treo tường SUPERJARE, Bộ 2, Giá trưng bày, Giá lưu trữ cho Phòng/Bếp/Văn phòng - Màu trắng', hình ảnh: 'https://m.media-amazon.com/images/I/61fTtaQNPnL._AC_UL320_.jpg', liên kết: 'https://amazon.com/gp/slredirect/picassoRedirect.html/ref=pa_sp_btf_aps_sr_pg1_1?ie=UTF8&adId=A03078372WABZ8V6NFP9L&url=%2FSUPERJARE-Mounted-Floating-Shelves-Display%2Fdp%2FB07H4NRT36%2Fref%3Dsr_1_59_sspa%3Fc loại bỏ%3D36QNR0DBY6M7J%26dchild%3D1%26keywords%3Dshelves%26qid%3D1627970918%26refresh%3D1%26sprefix%3Ds%252Caps%252C309%26sr%3D8-59-spons%26psc%3D1&qualifier=1627970918&id=3373422987100422&widgetName=sp_btf', giá: '$32.99', đánh giá: '6,171', sao: '4.7 trên 5 sao' }

Định dạng dữ liệu​

Bây giờ chúng ta đã có thể lấy được dữ liệu cần thiết, tốt nhất là lưu dữ liệu dưới dạng tệp .CSV để dễ đọc hơn. Sau khi lấy được tất cả dữ liệu, chúng ta sẽ sử dụng mô-đun fs do Node.js cung cấp và lưu tệp mới có tên saved-shelves.csv vào thư mục của dự án. Nhập mô-đun fs ở đầu tệp và sao chép hoặc viết theo các dòng mã sau:
Mã:
let csvContent = shelves.map(element => { return Object.values(element).map(item => `"${item}"`).join(',')}).join("\n")fs.writeFile('saved-shelves.csv', "Title, Image, Link, Price, Reviews, Stars" + '\n' + csvContent, 'utf8', function (err) { if (err) { console.log('Đã xảy ra lỗi - tệp không được lưu hoặc bị hỏng.') } else{ console.log('Tệp đã được lưu!') }})
Như chúng ta có thể thấy, trên ba dòng đầu tiên, chúng ta định dạng dữ liệu đã thu thập trước đó theo nối tất cả các giá trị của một đối tượng shelve bằng dấu phẩy. Sau đó, sử dụng mô-đun fs, chúng ta tạo một tệp có tên là saved-shelves.csv, thêm một hàng mới chứa các tiêu đề cột, thêm dữ liệu chúng ta vừa định dạng và tạo một hàm gọi lại xử lý các lỗi.

Kết quả sẽ trông giống như thế này:


Mẹo thưởng!​

Thu thập ứng dụng trang đơn​

Nội dung động đang trở thành tiêu chuẩn hiện nay, vì các trang web phức tạp hơn bao giờ hết. Để cung cấp trải nghiệm người dùng tốt nhất có thể, các nhà phát triển phải áp dụng các cơ chế tải khác nhau cho nội dung động, khiến công việc của chúng tôi trở nên phức tạp hơn một chút. Nếu bạn không biết điều đó có nghĩa là gì, hãy tưởng tượng một trình duyệt không có giao diện người dùng đồ họa. May mắn thay, có ✨Puppeteer✨ — thư viện Node kỳ diệu cung cấp API cấp cao để điều khiển phiên bản Chrome qua Giao thức DevTools. Tuy nhiên, nó cung cấp cùng chức năng như trình duyệt, nhưng phải được điều khiển theo chương trình bằng cách nhập một vài dòng mã. Hãy cùng xem cách thức hoạt động của nó.

Trong dự án đã tạo trước đó, hãy cài đặt thư viện Puppeteer bằng cách chạy npm install puppeteer, tạo tệp puppeteer.js mới và sao chép hoặc viết theo các dòng mã sau:
Mã:
const puppeteer = require('puppeteer')(async () => { try { const chrome = await puppeteer.launch() const page = await chrome.newPage() await page.goto('https://www.reddit.com/r/Kanye/hot/') await page.waitForSelector('.rpBJOHq2PR60pnwJlUyP0', { timeout: 2000 }) const body = await page.evaluate(() => { return document.querySelector('body').innerHTML }) console.log(body) await chrome.close() } catch (error) { console.log(error) }})()
Trong ví dụ trên, chúng tôi tạo một phiên bản Chrome và mở một trang trình duyệt mới cần phải truy cập liên kết này. Trong dòng sau, chúng tôi yêu cầu trình duyệt không có giao diện chờ cho đến khi phần tử có lớp rpBJOHq2PR60pnwJlUyP0 xuất hiện trên trang. Chúng tôi cũng đã chỉ định thời gian trình duyệt phải chờ để tải trang (2000 mili giây).

Sử dụng phương thức evaluate trên biến page, chúng tôi đã hướng dẫn Puppeteer thực thi các đoạn mã Javascript trong ngữ cảnh của trang ngay sau khi phần tử cuối cùng được tải. Điều này sẽ cho phép chúng tôi truy cập nội dung HTML của trang và trả về phần thân của trang dưới dạng đầu ra. Sau đó, chúng tôi đóng phiên bản Chrome bằng cách gọi phương thức close trên biến chrome. Công việc kết quả sẽ bao gồm tất cả mã HTML được tạo động. Đây là cách Puppeteer có thể giúp chúng tôi tải nội dung HTML động.

Nếu bạn không cảm thấy thoải mái khi sử dụng Puppeteer, hãy lưu ý rằng có một số lựa chọn thay thế ngoài kia, như NightwatchJS, NightmareJS hoặc CasperJS. Chúng có đôi chút khác biệt, nhưng cuối cùng, quy trình khá giống nhau.

Thiết lập tiêu đề user-agent

user-agent là tiêu đề yêu cầu cho trang web bạn đang truy cập biết về bạn, cụ thể là trình duyệt và hệ điều hành của bạn. Tiêu đề này được sử dụng để tối ưu hóa nội dung cho thiết lập của bạn, nhưng các trang web cũng sử dụng tiêu đề này để xác định các bot gửi hàng tấn yêu cầu — ngay cả khi nó thay đổi IPS.

Dưới đây là giao diện của tiêu đề user-agent:
Mã:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, như Gecko) Chrome/93.0.4577.82 Safari/537.36
Để không bị phát hiện và chặn, bạn nên thường xuyên thay đổi tiêu đề này. Hãy cẩn thận hơn để không gửi tiêu đề trống hoặc lỗi thời vì điều này không bao giờ được xảy ra với người dùng thông thường và bạn sẽ nổi bật.

Giới hạn tốc độ​

Các trình thu thập dữ liệu web có thể thu thập nội dung cực kỳ nhanh, nhưng bạn nên tránh chạy ở tốc độ tối đa. Có hai lý do cho điều này:
  1. Quá nhiều yêu cầu trong thời gian ngắn có thể làm chậm máy chủ của trang web hoặc thậm chí làm sập máy, gây rắc rối cho chủ sở hữu và những người truy cập khác. Về cơ bản, nó có thể trở thành một cuộc tấn công DoS.
  2. Nếu không có proxy luân phiên, điều này cũng giống như việc lớn tiếng thông báo rằng bạn đang sử dụng bot vì không có con người nào gửi hàng trăm hoặc hàng nghìn yêu cầu mỗi giây.
Giải pháp là tạo ra độ trễ giữa các yêu cầu của bạn, một hoạt động được gọi là "giới hạn tốc độ". (Cũng khá đơn giản để triển khai!)

Trong ví dụ Puppeteer được cung cấp ở trên, trước khi tạo biến body, chúng ta có thể sử dụng phương thức waitForTimeout do Puppeteer cung cấp để đợi vài giây trước khi thực hiện yêu cầu khác:
Mã:
await page.waitForTimeout(3000);
Trong đó ms là số giây bạn muốn đợi.

Ngoài ra, nếu chúng ta muốn làm điều tương tự đối với ví dụ axios, chúng ta có thể tạo một lời hứa gọi phương thức setTimeout() để giúp chúng ta đợi đủ số mili giây mong muốn:
Mã:
fetchShelves.then(result => new Promise(resolve => setTimeout(() => resolve(result), 3000)))
Theo cách này, bạn có thể tránh gây quá nhiều áp lực lên máy chủ mục tiêu và cũng mang đến cách tiếp cận nhân văn hơn đối với việc thu thập dữ liệu web.

Lời kết​

Và đó là hướng dẫn từng bước để tạo trình thu thập dữ liệu web cho dữ liệu sản phẩm của Amazon! Nhưng hãy nhớ rằng, đây chỉ là một tình huống. Nếu bạn muốn thu thập dữ liệu từ một trang web khác, bạn sẽ phải thực hiện một vài điều chỉnh để có được bất kỳ kết quả có ý nghĩa nào.

Đọc thêm​

Nếu bạn vẫn muốn xem thêm về hoạt động thu thập dữ liệu web, đây là một số tài liệu đọc hữu ích dành cho bạn:

Đọc thêm​

  • Xây dựng và Docker hóa ứng dụng Node.js bằng kiến trúc không trạng thái với sự trợ giúp của Kinsta
  • Xây dựng trình đọc RSS tĩnh để chống lại FOMO bên trong bạn
  • Hướng dẫn đơn giản về mô hình ngôn ngữ thế hệ tăng cường để truy xuất
  • Chế độ chặt chẽ: Tại sao trình duyệt tạo ra kết quả hiệu suất khác nhau
 
Back
Bên trên