Theo dõi giá đối thủ thương mại điện tử bằng scraper tùy chỉnh
Hướng dẫn xây dựng scraper theo dõi giá đối thủ: cần thu thập dữ liệu gì, cách trích xuất, đối chiếu sản phẩm, lưu lịch sử và tuân thủ robots.txt.
Long Nguyen
Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu
Scraper theo dõi giá đối thủ cần làm gì?
Một scraper tùy chỉnh để theo dõi giá đối thủ trong thương mại điện tử không chỉ là một đoạn script, mà là cả một quy trình. Nó truy cập trang sản phẩm của đối thủ, trích xuất giá và tình trạng còn hàng, đối chiếu từng trang với một SKU của bạn, lưu lại mọi lần ghi nhận và thông báo khi có dữ liệu thay đổi hoặc quy trình gặp lỗi.
Việc truy cập và phân tích dữ liệu là phần dễ nhất. Độ đáng tin cậy của dữ liệu phụ thuộc vào khả năng đối chiếu, lưu lịch sử và phát hiện lỗi:
- Truy cập URL sản phẩm một cách phù hợp, theo lịch định sẵn.
- Trích xuất giá bán, đơn vị tiền tệ, giá niêm yết, tình trạng còn hàng và phí vận chuyển.
- Đối chiếu từng URL của đối thủ với SKU tương ứng của bạn.
- Lưu trữ mọi lần ghi nhận, không chỉ mức giá mới nhất.
- Cảnh báo khi giá thay đổi đáng kể hoặc scraper gặp lỗi.
Phần còn lại của hướng dẫn sẽ đi theo đúng trình tự này.
Scraper tùy chỉnh hay công cụ theo dõi giá: Khi nào nên tự xây dựng?
Công cụ theo dõi giá dạng dịch vụ thường là cách khởi đầu nhanh hơn. Scraper tùy chỉnh phù hợp khi bài toán của bạn không đáp ứng được các giả định có sẵn của công cụ.
| Tiêu chí | Scraper tùy chỉnh | Công cụ theo dõi giá dạng dịch vụ |
|---|---|---|
| Trang web đối thủ | Nhiều trang ngách, trang theo khu vực hoặc có cấu trúc khác thường | Một số nhà bán lẻ phổ biến đã được công cụ hỗ trợ |
| Đối chiếu sản phẩm | Quy tắc của bạn: GTIN, MPN, mã linh kiện nội bộ, sản phẩm combo | Cơ chế đối chiếu của công cụ, ít khả năng tùy chỉnh |
| Tích hợp | Kết nối trực tiếp với cơ sở dữ liệu, danh mục sản phẩm hoặc quy tắc định giá lại của bạn | Xuất dữ liệu hoặc dùng API trong giới hạn của công cụ |
| Lịch sử và quyền sở hữu dữ liệu | Toàn bộ lịch sử được lưu trong cơ sở dữ liệu của bạn | Phụ thuộc vào gói dịch vụ và thời gian lưu trữ |
| Cấu trúc chi phí | Tốn thời gian kỹ thuật ban đầu, sau đó cần bảo trì | Phí thuê bao định kỳ, ít cần nguồn lực kỹ thuật |
| Bảo trì | Bạn chịu trách nhiệm: thay đổi bố cục, chặn truy cập, giám sát | Nhà cung cấp chịu trách nhiệm |
| Thời gian có dữ liệu đầu tiên | Lâu hơn | Nhanh hơn |
Một cách phân chia hợp lý với nhiều nhà bán hàng là dùng công cụ cho một vài đối thủ lớn mà công cụ hỗ trợ tốt, đồng thời dùng scraper cho nhóm trang ngách còn lại và mọi dữ liệu cần đưa vào logic định giá riêng của bạn.
Nếu quyết định tự xây dựng nhưng không muốn tự vận hành crawler, dịch vụ phần mềm tùy chỉnh của Netalith có thể đảm nhiệm việc thu thập và xử lý dữ liệu, bao gồm các quy trình thu thập giá như mô tả trong bài viết này.
Nên thu thập những loại giá và trường dữ liệu nào?
Đừng chỉ lưu một con số mang tên “price”. Tài liệu merchant listing của Google phân biệt ba loại giá: giá hiện tại, giá gạch ngang được hiển thị khi giảm giá và giá dành cho thành viên chương trình khách hàng thân thiết. Các loại giá này được mã hóa trong đánh dấu Offer và UnitPriceSpecification, dùng priceType cho giá gạch ngang và validForMemberTier cho giá thành viên. Trộn lẫn các loại giá là cách chắc chắn tạo ra so sánh sai.
| Trường dữ liệu | Vì sao quan trọng | Nguồn thường gặp |
|---|---|---|
| Giá hiện tại | Mức giá khách hàng phải trả ngay lúc này | price, hoặc price specification không có priceType |
| Giá gạch ngang | Phân biệt mức giảm giá thực tế với giá “đã từng” được hiển thị cố định | Price specification có priceType đặt thành StrikethroughPrice |
| Giá thành viên | Giá ưu đãi cho thành viên không phải là giá công khai | validForMemberTier |
| Đơn vị tiền tệ | Không bao giờ so sánh các con số thô giữa những đơn vị tiền tệ khác nhau | priceCurrency |
| Tình trạng còn hàng | Đối thủ hết hàng không phải là mối đe dọa cạnh tranh thực sự về giá | availability |
| Phí vận chuyển và thời gian giao hàng | Cần thiết để so sánh tổng giá đến tay khách hàng | shippingDetails, hoặc nội dung hiển thị trên trang |
| Thời hạn khuyến mãi | Cho biết chương trình ưu đãi kết thúc khi nào | validFrom, priceValidUntil, validThrough |
| Đơn giá | So sánh theo 100 ml hoặc mỗi kilogram thay vì theo cả gói | referenceQuantity |
Có hai lưu ý thực tế. Khi một cửa hàng bán hàng bằng nhiều đơn vị tiền tệ, hướng dẫn của Google là dùng một URL cho mỗi đơn vị tiền tệ; vì vậy, hãy định danh các lần ghi nhận dựa trên URL và đơn vị tiền tệ. Ngoài ra, dữ liệu đánh dấu có thể chậm hơn nội dung hiển thị hoặc chưa bao gồm thuế, nên hãy kiểm tra mẫu giá đã trích xuất với những gì khách hàng thực sự nhìn thấy.
Cách trích xuất giá: Ưu tiên dữ liệu có cấu trúc, sau đó mới đến HTML
Hãy thử các phương pháp trích xuất theo thứ tự từ rẻ và ổn định nhất đến tốn kém hơn:
- Dữ liệu có cấu trúc của sản phẩm (JSON-LD hoặc microdata) trong mã nguồn trang. Ổn định, ít tốn tài nguyên và thường có cả GTIN, MPN.
- Bộ chọn CSS hoặc XPath riêng cho từng trang với những website không có dữ liệu đánh dấu hữu ích.
- Trang đã render bằng trình duyệt headless, chỉ dùng khi giá được chèn bằng JavaScript.
Google khuyến nghị đặt đánh dấu Product trong HTML ban đầu và cảnh báo rằng đánh dấu được tạo bằng JavaScript có thể khiến quá trình thu thập dữ liệu cho Shopping kém thường xuyên và kém đáng tin cậy hơn đối với những dữ liệu thay đổi nhanh như giá và tình trạng còn hàng. Trên thực tế, nhiều trang sản phẩm của đối thủ có sẵn giá trong phản hồi HTML thuần, vì vậy chỉ cần HTTP fetch kết hợp với trình phân tích JSON-LD đã có thể xử lý phần lớn mục tiêu trước khi bạn cần đến trình duyệt.
Bộ trích xuất dưới đây đọc JSON-LD của Product, xử lý @graph, hỗ trợ một offer đơn lẻ hoặc một danh sách, chuyển sang price specification khi không có giá cấp cao nhất và lưu riêng giá gạch ngang. Bộ trích xuất cố ý bỏ qua AggregateOffer vì khoảng giá không đại diện cho giá của một người bán cụ thể.
import json
from decimal import Decimal, InvalidOperation
from bs4 import BeautifulSoup
def to_decimal(value):
"""schema.org prices use a dot decimal and no thousands separators."""
try:
return Decimal(str(value).strip())
except (InvalidOperation, AttributeError):
return None
def iter_nodes(obj):
"""Yield every dict in a JSON-LD payload, including @graph members."""
if isinstance(obj, list):
for item in obj:
yield from iter_nodes(item)
elif isinstance(obj, dict):
if "@graph" in obj:
yield from iter_nodes(obj["@graph"])
yield obj
def has_type(node, name):
t = node.get("@type")
return name in (t if isinstance(t, list) else [t])
def extract_offers(html):
"""Return one record per Offer found in Product JSON-LD on the page."""
soup = BeautifulSoup(html, "html.parser")
records = []
for tag in soup.find_all("script", type="application/ld+json"):
try:
data = json.loads(tag.string or "")
except json.JSONDecodeError:
continue
for node in iter_nodes(data):
if not has_type(node, "Product"):
continue
offers = node.get("offers") or []
if isinstance(offers, dict):
offers = [offers]
for offer in offers:
if has_type(offer, "AggregateOffer"):
continue # a price range, not one seller's price
specs = offer.get("priceSpecification") or []
if isinstance(specs, dict):
specs = [specs]
price, currency = offer.get("price"), offer.get("priceCurrency")
if price is None: # active price may sit inside a spec
active = next(
(s for s in specs
if "priceType" not in s and "validForMemberTier" not in s),
None,
)
if active:
price, currency = active.get("price"), active.get("priceCurrency")
strike = next(
(s for s in specs
if "StrikethroughPrice" in str(s.get("priceType", ""))),
None,
)
records.append({
"gtin": node.get("gtin13") or node.get("gtin14")
or node.get("gtin12") or node.get("gtin"),
"mpn": node.get("mpn"),
"sku": node.get("sku"),
"price": to_decimal(price),
"currency": currency,
"list_price": to_decimal(strike.get("price")) if strike else None,
"availability": str(offer.get("availability", "")).rsplit("/", 1)[-1],
})
return records
Bộ trích xuất này chưa xử lý microdata, các biến thể sản phẩm (trang biến thể có thể liên kết đến một nhóm thông qua isVariantOf) và những website hoàn toàn không có dữ liệu đánh dấu. Các trường hợp đó sẽ chuyển sang tầng 2 hoặc 3. Hãy đặt một bộ trích xuất cho mỗi tầng phía sau cùng một cấu trúc đầu ra, đồng thời ghi lại bộ trích xuất nào đã tạo ra từng dòng dữ liệu để có thể truy nguyên khi chất lượng giảm.
Cách đối chiếu sản phẩm đối thủ với SKU của bạn
Việc đối chiếu quyết định một so sánh giá có ý nghĩa hay không. Giá thấp hơn trên sai sản phẩm còn tệ hơn việc không có dữ liệu. Hãy xếp hạng các phương pháp theo độ tin cậy và để độ tin cậy quyết định mức tự động hóa được phép.
| Phương pháp đối chiếu | Độ tin cậy | Cách sử dụng phù hợp |
|---|---|---|
| GTIN, EAN hoặc UPC trùng khớp tuyệt đối | Cao | Cảnh báo và định giá lại tự động |
| MPN kết hợp với thương hiệu | Trung bình đến cao | Cảnh báo; chỉ tự động hóa sau khi kiểm tra mẫu |
| Tiêu đề gần đúng kết hợp với thuộc tính sản phẩm | Thấp | Chỉ đưa vào hàng đợi để con người xem xét |
Những bẫy vẫn có thể xảy ra dù đã đối chiếu đúng mã định danh:
- Quy cách đóng gói. Một gói 2 sản phẩm bị so với một sản phẩm đơn lẻ. Hãy quy đổi về đơn giá khi trang cung cấp số lượng tham chiếu.
- Tình trạng sản phẩm. Hàng mới bị so với hàng tân trang hoặc đã qua sử dụng; hãy kiểm tra
itemConditionnếu có. - Biến thể. Các biến thể về kích thước hoặc màu sắc có thể có mức giá khác nhau dù dùng chung tên sản phẩm.
- Sản phẩm combo. Một bộ sản phẩm bao gồm mặt hàng của bạn cùng các phụ kiện.
Hãy lưu phương pháp đối chiếu trên từng mapping như trong schema bên dưới. Khi một mapping sau đó được xác định là sai, bạn có thể tìm và kiểm tra mọi dòng dữ liệu từng sử dụng cùng phương pháp đó.
Nên crawl bao lâu một lần và lưu lịch sử giá thế nào?
Tần suất crawl nên dựa trên tốc độ biến động giá, không phải một lịch cron chung cho tất cả. Hãy xem các mức dưới đây là điểm bắt đầu và điều chỉnh dựa trên tốc độ thay đổi quan sát được ở từng đối thủ.
| Nhóm SKU | Ví dụ | Tần suất khởi điểm |
|---|---|---|
| Quan trọng về giá | Sản phẩm bán chạy, có nhiều đối thủ trực tiếp | Mỗi ngày hoặc vài lần mỗi ngày |
| Tiêu chuẩn | Danh mục có sản lượng trung bình | Mỗi ngày |
| Nhóm sản phẩm ít phổ biến | Sản phẩm bán chậm, mặt hàng độc đáo | Mỗi tuần |
Hãy lưu một bản ghi bổ sung cho mỗi lần truy cập thành công, kể cả khi giá không thay đổi, để phân biệt “không có thay đổi” với “scraper không chạy”. Lưu bộ trích xuất và mã trạng thái HTTP trên từng dòng. Một schema PostgreSQL tối thiểu:
CREATE TABLE competitor_offer (
id BIGSERIAL PRIMARY KEY,
competitor_id INT NOT NULL,
url TEXT NOT NULL,
our_sku TEXT,
match_method TEXT, -- gtin | mpn_brand | manual
UNIQUE (competitor_id, url)
);
CREATE TABLE price_observation (
offer_id BIGINT NOT NULL REFERENCES competitor_offer(id),
observed_at TIMESTAMPTZ NOT NULL,
price NUMERIC(12,2),
list_price NUMERIC(12,2),
currency CHAR(3),
availability TEXT,
shipping NUMERIC(12,2),
extractor TEXT, -- jsonld | css | rendered
http_status SMALLINT,
PRIMARY KEY (offer_id, observed_at)
);
Hãy suy ra các sự kiện “giá thay đổi” từ những lần ghi nhận liên tiếp thay vì ghi đè vào một cột giá hiện tại. Lịch sử giúp bạn trả lời những câu hỏi như đối thủ thường chạy khuyến mãi bao lâu một lần, hoặc một lần giảm giá chỉ kéo dài một ngày hay không.
robots.txt, điều khoản sử dụng và giới hạn pháp lý
Hãy xây dựng crawler tuân thủ RFC 9309, Robots Exclusion Protocol, một tài liệu thuộc quy trình xây dựng tiêu chuẩn của IETF. Những điểm quan trọng đối với scraper giá gồm:
- Tiêu chuẩn nêu rõ các quy tắc này không phải là hình thức cấp phép truy cập. Tôn trọng robots.txt là phép lịch sự và mức cơ bản cần tuân thủ, không phải sự chấp thuận về mặt pháp lý.
- Nếu robots.txt trả về mã trạng thái 4xx, crawler có thể truy cập mọi tài nguyên. Nếu không thể truy cập do lỗi 5xx hoặc lỗi mạng, crawler phải mặc định không được truy cập bất kỳ tài nguyên nào.
- Hãy lưu bộ nhớ đệm robots.txt, nhưng không quá 24 giờ trừ khi tệp này không thể truy cập.
- Quy tắc khớp cụ thể nhất sẽ được ưu tiên, và quy tắc allow tương đương sẽ thắng disallow.
- Hãy sử dụng product token xuất hiện trong User-Agent và mô tả mục đích của crawler, tốt nhất là kèm URL liên hệ.
- Tiêu chuẩn chỉ định nghĩa các quy tắc allow và disallow, vì vậy bạn phải tự chịu trách nhiệm về nhịp độ yêu cầu: giới hạn số kết nối đồng thời trên mỗi host và tăng thời gian chờ khi có lỗi.
Ngoài robots.txt, có ba nguyên tắc giúp dự án đứng vững hơn. Hãy đọc điều khoản sử dụng của từng đối thủ. Không crawl các trang yêu cầu đăng nhập và không tìm cách vượt CAPTCHA hoặc cơ chế chống bot. Nếu robots.txt hoặc điều khoản của đối thủ không cho phép truy cập trang sản phẩm, hãy xem đó là không được phép và tìm một nguồn hợp pháp khác, chẳng hạn API chính thức của sàn, nguồn feed mà bạn được cấp phép sử dụng hoặc kiểm tra thủ công theo mẫu.
Cách pháp luật xử lý hoạt động scraping khác nhau tùy quốc gia và tùy loại dữ liệu được thu thập, vì vậy đây không phải là tư vấn pháp lý. Hãy tham khảo ý kiến chuyên môn trước khi crawl trên quy mô lớn hoặc tại một khu vực pháp lý mới. Ngoài ra, theo dõi giá công khai khác với việc phối hợp giá với đối thủ; hãy sử dụng dữ liệu như đầu vào cho các quyết định định giá độc lập của riêng bạn.
Vì sao scraper gặp lỗi và cách phát hiện?
Lỗi gây tốn kém nhất không phải là một vụ crash. Đó là scraper vẫn chạy nhưng âm thầm ghi nhận sai giá. Hãy giám sát scraper cẩn thận như cách bạn giám sát giá.
| Triệu chứng | Nguyên nhân có thể | Cách phát hiện |
|---|---|---|
| Nhiều giá null từ một đối thủ | Thay đổi bố cục hoặc dữ liệu đánh dấu | Đo tỷ lệ null theo từng đối thủ và từng lần chạy, kèm ngưỡng cảnh báo |
| Giá tăng gấp mười lần hoặc giảm về 0 | Lỗi phân tích, giá “từ”, hoặc chọn nhầm phần tử | Đặt biên kiểm tra hợp lý dựa trên lần ghi nhận trước; giữ các giá trị bất thường để xem xét |
| Số lượng phản hồi 403 hoặc 429 tăng đột biến | Giới hạn tần suất hoặc bị chặn | Thống kê mã trạng thái theo từng host; giảm tốc độ và tăng thời gian chờ |
| Mọi sản phẩm đều có cùng một mức giá | Đã lấy trang chấp thuận cookie hoặc trang trung gian thay vì trang sản phẩm | Kiểm tra dấu vân tay trang; loại bỏ các dấu hiệu trung gian đã biết |
| Giá hiển thị bằng đơn vị tiền tệ không mong đợi | Website cung cấp giá theo khu vực | Khóa locale hoặc dùng URL theo từng đơn vị tiền tệ; xác thực tiền tệ trên từng dòng |
| Giá trích xuất khác với giá hiển thị | Dữ liệu đánh dấu đã cũ hoặc chưa bao gồm thuế | Kiểm tra mẫu theo lịch với trang đã render |
Khi một đối thủ bắt đầu chặn bạn, phản ứng phù hợp là giảm tốc độ và giảm tần suất. Chuyển sang các chiến thuật né tránh sẽ làm tăng cả chi phí pháp lý lẫn chi phí bảo trì.
Biến dữ liệu giá thành quyết định định giá lại
Giá thô của đối thủ nên cung cấp dữ liệu cho các quy tắc, chứ không thay thế phán đoán kinh doanh. Một số rào chắn giúp tự động hóa không làm tổn hại biên lợi nhuận:
- So sánh tổng giá đến tay khách hàng (giá sản phẩm cộng phí vận chuyển), không chỉ giá niêm yết.
- Đặt mức sàn dựa trên chi phí cộng biên lợi nhuận tối thiểu và tuân thủ mọi mức giá quảng cáo tối thiểu mà bạn bị ràng buộc.
- Bỏ qua đối thủ hết hàng và các mapping có độ tin cậy thấp.
- Yêu cầu con người phê duyệt những thay đổi vượt quá một tỷ lệ phần trăm nhất định.
- Ghi lại mọi thay đổi tự động cùng các lần ghi nhận đã kích hoạt thay đổi đó.
Hãy bắt đầu chỉ với cảnh báo. Sau khi tỷ lệ đối chiếu ổn định và cảnh báo lỗi không xuất hiện trong vài tuần, bạn có thể cho phép các sản phẩm còn hàng, được đối chiếu bằng GTIN, tự động định giá lại trong phạm vi mức sàn; mọi trường hợp khác vẫn nên chờ xem xét.
Nếu muốn xây dựng scraper dựa trên chính danh sách đối thủ, danh mục sản phẩm và quy tắc định giá của bạn, hãy gửi các website và SKU quan trọng qua biểu mẫu nhận báo giá miễn phí của Netalith để cùng xác định phạm vi dự án.
CÂU HỎI THƯỜNG GẶP
Câu hỏi thường gặp
Cào giá của đối thủ có hợp pháp không?
Điều này phụ thuộc vào quốc gia, điều khoản sử dụng của website, loại dữ liệu bạn truy cập và cách bạn thực hiện. Giá trên các trang công khai thường được theo dõi, nhưng điều khoản sử dụng, robots.txt, yêu cầu đăng nhập và cơ chế chống bot đều rất quan trọng; robots.txt không phải là giấy phép truy cập. Không crawl nội dung yêu cầu đăng nhập hoặc vượt qua cơ chế bảo vệ, đồng thời hãy tham khảo ý kiến pháp lý trước khi crawl trên quy mô lớn.
Nên crawl giá của đối thủ bao lâu một lần?
Hãy điều chỉnh tần suất theo tốc độ biến động giá. Điểm bắt đầu phổ biến là mỗi ngày hoặc thường xuyên hơn với các SKU quan trọng về giá, và mỗi tuần với nhóm sản phẩm ít phổ biến; sau đó điều chỉnh dựa trên tốc độ thay đổi quan sát được ở từng đối thủ.
Nên tự xây scraper hay mua công cụ theo dõi giá?
Hãy mua khi bạn chỉ theo dõi một vài nhà bán lẻ phổ biến và cần dữ liệu nhanh. Hãy tự xây dựng khi theo dõi nhiều website ngách hoặc theo khu vực, cần cơ chế đối chiếu sản phẩm riêng, hoặc phải đưa giá trực tiếp vào cơ sở dữ liệu và quy tắc định giá lại của bạn. Nhiều nhà bán hàng kết hợp cả hai cách.
Làm thế nào để đối chiếu sản phẩm của đối thủ với SKU của mình?
Ưu tiên GTIN, EAN hoặc UPC trùng khớp tuyệt đối, sau đó đến MPN kết hợp với thương hiệu. Chỉ dùng đối chiếu tiêu đề gần đúng để đề xuất sản phẩm cho con người xem xét. Hãy chú ý đến quy cách đóng gói, tình trạng sản phẩm và biến thể, đồng thời lưu lại cách mỗi mapping được tạo ra.
Nếu đối thủ chặn scraper thì phải làm gì?
Hãy giảm tốc độ, hạ tần suất và kiểm tra xem bạn có tuân thủ robots.txt của họ không. Nếu website vẫn không cho phép truy cập, hãy dùng một nguồn hợp pháp khác như API chính thức, feed được cấp phép hoặc kiểm tra thủ công theo mẫu, thay vì chuyển sang các chiến thuật né tránh.