Multi-Channel Selling

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.

Ảnh đại diện Long Nguyen

Long Nguyen

Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu

• • 4 phút đọc •

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:

  1. Truy cập URL sản phẩm một cách phù hợp, theo lịch định sẵn.
  2. 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.
  3. Đối chiếu từng URL của đối thủ với SKU tương ứng của bạn.
  4. Lưu trữ mọi lần ghi nhận, không chỉ mức giá mới nhất.
  5. 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:

  1. 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.
  2. 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.
  3. 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 itemCondition nế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.

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.

Cập nhật cùng Netalith

Nhận kiến thức công nghệ, cập nhật sản phẩm và ưu đãi đặc biệt qua email.