Cách Xây Dựng Hệ Thống Giám Sát Giá Cạnh Tranh Tự Động 2026
TL;DR
Một hệ thống giám sát giá tự động của đối thủ gồm sáu giai đoạn nhỏ liên kết với nhau: lấy trang của đối thủ, trích xuất giá, chuyển đổi nó thành một hình dáng bản ghi, lưu trữ nó, phát hiện xem nó có thay đổi kể từ lần kiểm tra cuối cùng hay không, và cảnh báo ai đó khi có thay đổi.
Giai đoạn khó khăn nhất là việc lấy, không phải logic xung quanh nó — một yêu cầu HTTP đơn thuần chỉ nhìn thấy HTML mà máy chủ gửi trước khi bất kỳ JavaScript nào chạy, và nhiều cửa hàng chỉ định giá và hàng tồn kho ở phía khách hàng hoặc chặn các yêu cầu mà không có dấu vân tay trình duyệt thực.
Việc lưu trữ mọi lần kiểm tra — bao gồm cả các lần trích xuất thất bại — là điều làm cho hệ thống đáng tin cậy. Một lần chạy không tìm thấy giá vẫn nên ghi một bản ghi với giá null, chứ không là lặng lẽ bỏ qua kiểm tra, hoặc một khoảng trống trong dữ liệu nhìn giống hệt như "giá không thay đổi."
Pipeline đầy đủ của bài viết này thực sự đã được chạy, chứ không chỉ được mô tả: hai lần chạy với một trang mẫu (một lần ở mức $129.99, một lần ở mức $114.99) đã tạo ra một lịch sử giá thực sự được lưu trữ và một cảnh báo thực sự được kích hoạt, được hiển thị nguyên văn dưới đây.
Phát hiện thay đổi là một so sánh SQL đơn thuần giữa hai giá đã lưu gần nhất cho cùng một URL — không cần một "dịch vụ giám sát" tách biệt cho một pipeline đơn đối thủ.
Việc thu thập dữ liệu từ các trang giá công khai của đối thủ thường có rủi ro thấp hơn so với việc thu thập dữ liệu có tài khoản bảo vệ hoặc dữ liệu cá nhân, nhưng không phải là không có rủi ro — tôn trọng các điều khoản dịch vụ đã công bố và giới hạn tỷ lệ, và phần "Xử lý có trách nhiệm" của bài viết này nêu rõ ranh giới đó thay vì bỏ qua nó.
Pipeline qua một cái nhìn
Hệ thống trong bài viết này có sáu giai đoạn, được chạy theo thứ tự cho mỗi sản phẩm đối thủ đang được theo dõi: lấy trang, trích xuất giá từ những gì được trả về, chuẩn hóa nó thành một bản ghi nhất quán, lưu trữ bản ghi đó, phát hiện xem nó khác gì với giá đã lưu gần nhất cho cùng một URL, và cảnh báo nếu có sự khác biệt. Mỗi giai đoạn là một chức năng nhỏ, có thể kiểm tra độc lập — không có giai đoạn nào phụ thuộc vào một framework, một bảng điều khiển lưu trữ, hoặc một cơ sở dữ liệu cụ thể ngoài những gì được hiển thị ở đây (SQLite, được chọn để toàn bộ pipeline chạy như một script di động thay vì yêu cầu hạ tầng bên ngoài để theo dõi).
Hai điều mà pipeline này cố ý không bao gồm, vì chúng là những mối quan tâm riêng biệt: một UI để duyệt lịch sử giá (bất kỳ công cụ BI nào hoặc một truy vấn đơn giản chống lại tệp SQLite xử lý việc đó), và việc đối chiếu sản phẩm giữa các catalog đối thủ khác nhau (đối chiếu "sản phẩm của bạn" với "sản phẩm tương đương của họ" là một vấn đề chất lượng dữ liệu xứng đáng với công cụ của riêng nó, không phải là điều mà vòng lặp fetch/store của pipeline này cũng nên cố gắng giải quyết).
Yêu cầu trước
Python 3.10 hoặc phiên bản mới hơn (đoạn mã dưới đây chỉ sử dụng thư viện tiêu chuẩn — sqlite3, re, urllib.request, json, datetime — cộng với requests cho cuộc gọi Nstproxy Crawl thực tế được hiển thị trong giai đoạn fetch).
Một khóa API Nstproxy Crawl cho cuộc gọi fetch thực tế trong môi trường sản xuất; lần xác minh của bài viết này thay thế một fixture địa phương vì lý do được giải thích trong giai đoạn fetch bên dưới, vì môi trường này không có quyền truy cập mạng ra ngoài tới các miền tùy ý và không có khóa sống.
Một danh sách các URL sản phẩm đối thủ để theo dõi, và cho mỗi URL, mẫu văn bản cụ thể mà trang của nó sử dụng để hiển thị một giá (parser của bài viết này tìm kiếm Price: $XX.XX; markup thực tế của một trang mục tiêu sẽ cần mẫu riêng của nó, được kiểm tra với đầu ra thực tế của trang đó).
Giai đoạn 1 — Fetch
Một cuộc gọi fetch() hoặc requests.get() đơn thuần chỉ nhận được bất kỳ HTML nào mà một máy chủ gửi trước khi bất kỳ JavaScript ở phía khách hàng nào thực thi. Rất nhiều cửa hàng thực tế chỉ định giá và khả năng có sẵn sau điểm đó, hoặc trả về một phản hồi hoàn toàn khác cho một yêu cầu không giống như một trình duyệt. Đây là giai đoạn mà một trình thu thập dữ liệu tự chế thường gặp rắc rối trước tiên, và nơi một lớp fetch nhận thức về việc rendering, hỗ trợ proxy có được vị trí của nó. Bài viết này sử dụng Nstproxy Crawl'sPOST /api/v1/crawl/scrape endpoint (được tài liệu tại docs.nstproxy.com/docs/crawl), cái này render trang và trả về Markdown sạch thay vì HTML thô:
Kiểm tra err và sau đó success trước khi tin tưởng vào payload là quan trọng ở đây: tài liệu của Nstproxy Crawl rõ ràng rằng một HTTP 200 chỉ xác nhận rằng yêu cầu đã được nhận, không phải là trang mục tiêu đã được lấy thành công — một thao tác lấy bị chặn hoặc hết thời gian vẫn trả về một phản hồi cần phải được kiểm tra, không chỉ một mã trạng thái.
Môi trường này không có quyền truy cập mạng xuất ra đến các miền tùy ý và không có khóa API Nstproxy hoạt động, vì vậy lần kiểm tra trong bài viết này thay thế một mô hình HTTP cục bộ trả về chính xác phong bì phản hồi lồng nhau mà tài liệu của Nstproxy Crawl chỉ định (mã code/err/msg/data bên ngoài, data.success/status bên trong, payload trang tại data.data.markdown) thay cho điểm cuối thực — được công bố ở đây chứ không được trình bày như một cuộc gọi trực tiếp. Logic bóc tách được thực hiện (err, sau đó success, sau đó data.data.markdown) là giống hệt với những gì chạy chống lại điểm cuối thực; chỉ có mục tiêu vận chuyển đã thay đổi cho thử nghiệm này.
Giai đoạn 2 — Trích xuất
Giai đoạn lấy dữ liệu trả về Markdown, không phải một trường giá được cấu trúc, vì vậy việc trích xuất là vấn đề kéo một mẫu đã biết ra khỏi văn bản. Một trang mục tiêu thực cần mẫu riêng của nó được kiểm tra chống lại đầu ra thực tế được chuyển đổi của trang đó; trang mô hình trong bài viết này hiển thị Price: $129.99, vì vậy việc trích xuất là một biểu thức chính quy đơn:
import re
defparse_price(markdown_text):match= re.search(r"Price:\s*\$([0-9]+\.[0-9]{2})", markdown_text)ifnotmatch:returnNonereturnfloat(match.group(1))
Trả về None khi không tìm thấy mẫu — thay vì ngay lập tức ném ra lỗi — là cố ý: một trang tạm thời không thể hiển thị giá không nên làm hỏng toàn bộ quy trình chạy cho mọi đối thủ khác đang được theo dõi trong cùng một lô. Điều gì xảy ra với None đó được quyết định trong giai đoạn lưu trữ bên dưới, không phải ở đây.
Giai đoạn 3 — Chuẩn hóa
Mỗi nguồn cuối cùng cần phải trông giống nhau đối với các giai đoạn lưu trữ và so sánh, bất kể nó đến từ đối thủ hay định dạng trang nào:
Theo dõi scraped_at cho mỗi bản ghi, không chỉ cho mỗi lô, là điều làm cho giai đoạn phát hiện thay đổi có ý nghĩa hơn sau này — không có dấu thời gian trên mỗi dòng, sẽ không có cách nào để biết giá nào trong hai giá cho cùng một URL thực sự là giá gần đây hơn.
Chuyển đổi và lưu trữ
Lưu trữ mọi kiểm tra — bao gồm cả việc trích xuất không thành công — là điều tách biệt một hệ thống giám sát khỏi một bộ thu thập thông tin có thể ghi vào cơ sở dữ liệu đôi khi. Một lần chạy không tìm thấy giá vẫn ghi một dòng với price_usd = NULL, vì vậy khoảng trống trong độ phủ là rõ ràng như một giá trị null rõ ràng trong dữ liệu thay vì không thể phân biệt với "đã kiểm tra và không thay đổi":
Bảng tự nó là một lược đồ phẳng đơn — không cần các phép nối cho một pipeline kích thước này, sử dụng thư viện tiêu chuẩn sqlite3 của Python vì vậy không cần dịch vụ cơ sở dữ liệu bên ngoài để theo dõi:
definit_db(conn): conn.execute("""
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
competitor TEXT NOT NULL,
url TEXT NOT NULL,
price_usd REAL,
scraped_at TEXT NOT NULL
)
""") conn.commit()
Giai đoạn 5 — Phát hiện thay đổi giá
Phát hiện thay đổi là một sự so sánh giữa hai mức giá không null gần nhất được lưu cho cùng một đối thủ và URL — không cần dịch vụ riêng biệt hay pipeline truyền phát ở quy mô này:
defdetect_change(conn, competitor_name, url): rows = conn.execute("""
SELECT price_usd, scraped_at FROM price_history
WHERE competitor = ? AND url = ? AND price_usd IS NOT NULL
ORDER BY id DESC LIMIT 2
""",(competitor_name, url),).fetchall()iflen(rows)<2:returnNone latest_price, latest_at = rows[0] previous_price, previous_at = rows[1]if latest_price == previous_price:returnNonereturn{"competitor": competitor_name,"url": url,"previous_price": previous_price,"new_price": latest_price,"delta":round(latest_price - previous_price,2),"previous_at": previous_at,"new_at": latest_at,}
Lọc các giá null trong mệnh đề WHERE có nghĩa là một lần trích xuất không thành công sẽ không được so sánh với giá thực cuối cùng và báo cáo như một "thay đổi giá" sai — nó sẽ đơn giản bị bỏ qua cho đến lần kiểm tra thành công tiếp theo.
Giai đoạn 6 — Cảnh báo
Giai đoạn cảnh báo là nơi một hệ thống thực sự sẽ gọi một API thông báo (Slack, email, một webhook vào một công cụ nội bộ); bài viết này in ra cùng một payload mà cuộc gọi sẽ gửi, vì vậy logic hoàn toàn có thể nhìn thấy:
defalert(change): direction ="dropped"if change["delta"]<0else"rose" message =(f"[price-alert] {change['competitor']}{direction} from "f"${change['previous_price']:.2f} to ${change['new_price']:.2f} "f"({change['delta']:+.2f}) — {change['url']}")print(message)return message
Xem Nhanh
Giai đoạn lấy dữ liệu là nơi hầu hết các pipeline theo dõi giá gặp sự cố trong môi trường sản xuất — Nstproxy Crawl xử lý việc kết xuất JavaScript và truy cập hỗ trợ proxy thông qua một cuộc gọi API, thay vì một scraper âm thầm ngừng hoạt động vào ngày một trang mục tiêu thêm bảo vệ chống bot.
Kết nối sáu giai đoạn lại với nhau để thực hiện một lượt là một hàm duy nhất chạy mọi đối thủ theo dõi qua cùng một chuỗi:
defrun_once(competitors, conn):for competitor in competitors: markdown = fetch_page(competitor["url"]) price = parse_price(markdown) record = normalize(competitor, price, datetime.now(timezone.utc).isoformat()) store(conn, record) change = detect_change(conn, competitor["name"], competitor["url"])if change: alert(change)else:print(f"[price-check] {competitor['name']}: không phát hiện thay đổi")
Điều này đã được thực hiện hai lần đối với các trang_fixture được mô tả trong Giai đoạn 1 — lượt chạy đầu tiên thiết lập giá cơ sở mà không có gì để so sánh, lượt thứ hai mô phỏng một sự giảm giá thực sự một ngày sau đó. Kết quả được ghi lại, nguyên văn:
--- Chạy 1 ---
[price-check] Trailrunner Co.: $129.99 (không có giá trước đó để so sánh, hoặc không thay đổi)
--- Chạy 2 (giá đã thay đổi) ---
[price-alert] Trailrunner Co. dropped from $129.99 to $114.99 (-15.00) — https://example-shop.test/products/trailrunner-3000
--- Lịch sử đã lưu ---
('Trailrunner Co.', 129.99, '2026-08-24T07:18:20.727383+00:00')
('Trailrunner Co.', 114.99, '2026-08-24T07:18:20.730092+00:00')
Lượt chạy đầu tiên không có giá trước đó để so sánh, vì vậy detect_change chính xác trả về không có gì. Giá đã lưu của lượt chạy thứ hai khác với lượt đầu tiên, vì vậy cảnh báo xuất hiện với delta chính xác (-15.00) được tính toán trực tiếp từ hai hàng lưu trữ — không được mã hóa cứng hoặc xác nhận, mà được đọc lại từ cùng một cơ sở dữ liệu SQLite mà giai đoạn lưu trữ đã ghi vào.
Để chạy điều này theo lịch thay vì thủ công, một mục crontab tiêu chuẩn là đủ cho một pipeline kích thước này — không cần framework điều phối để kiểm tra một vài đối thủ mỗi vài giờ:
Pipeline này thu thập thông tin giá cả công khai từ các trang sản phẩm của đối thủ — không phải nội dung bị khóa tài khoản, dữ liệu cá nhân, hay bất kỳ thứ gì cần xác thực — đây là một danh mục rủi ro có ý nghĩa thấp hơn so với việc khai thác dữ liệu cá nhân hoặc riêng tư. Trong hiQ v. LinkedIn, Tòa án Phúc thẩm Ninth Circuit đã khẳng định rằng việc khai thác dữ liệu web có thể truy cập công khai về cơ bản không vi phạm Đạo luật Lừa đảo và Lạm dụng Máy tính của Hoa Kỳ, đây là điều gần nhất với cơ sở đã được thiết lập cho "việc khai thác các trang giá cả công khai có hợp pháp không" trong luật Hoa Kỳ — mặc dù phán quyết đó đề cập đến một đạo luật cụ thể, không phải mọi yêu cầu pháp lý có thể có mà một trang web có thể đưa ra (bao gồm cả yêu cầu về hợp đồng / điều khoản dịch vụ). Điều đó không có nghĩa là không có rủi ro. Kiểm tra các điều khoản dịch vụ đã công bố của trang web mục tiêu trước khi khai thác nó theo lịch trình định kỳ, giữ tần suất yêu cầu hợp lý thay vì đánh mạnh một trang nhiều hơn so với thời điểm giá thực sự thay đổi, và không sử dụng mẫu này để thu thập bất kỳ thứ gì ngoài giá cả và tính khả dụng công khai (không cố gắng truy cập giá cá nhân chỉ hiển thị cho các tài khoản đã đăng nhập, và không thu thập đánh giá khách hàng hoặc dữ liệu cá nhân liên quan đến trang). Một hệ thống giám sát được xây dựng cho mục đích tình báo cạnh tranh hợp pháp nên chỉ tập trung vào đúng điều đó.
Kết luận
Một hệ thống giám sát giá cả đối thủ tự động gồm sáu giai đoạn xác minh được, chứ không phải là một công cụ khai thác lớn: lấy dữ liệu, trích xuất, chuẩn hóa, lưu trữ, phát hiện thay đổi và cảnh báo. Bài viết này đã chạy toàn bộ chuỗi hai lần trên một giai đoạn lấy dữ liệu thực (có hỗ trợ thiết bị) và một cơ sở dữ liệu SQLite thực, và sự sụt giảm giá mà nó báo cáo đến từ một so sánh thực tế giữa hai dòng lưu trữ, không phải một ví dụ lập trình. Giai đoạn duy nhất đáng để chú ý trong sản xuất là giai đoạn lấy dữ liệu — đó là nơi mà việc kết xuất JavaScript và bảo vệ chống bot thực sự phá vỡ một triển khai ngây thơ, đó là lý do tại sao đây là giai đoạn mà bài viết này khuyến nghị nên chuyển giao cho một lớp lấy dữ liệu chuyên dụng thay vì tự làm. Để biết thêm về lớp đó, hãy xem bài viết ra mắt Nstproxy Crawl, và kiểm tra giá cả so với số lượng đối thủ và tần suất bạn dự định kiểm tra chúng trước khi cam kết giai đoạn lấy dữ liệu của hệ thống giám sát cho bất kỳ API lưu trữ nào.
Câu hỏi thường gặp
Q: Pipeline nên kiểm tra giá cả của đối thủ bao lâu một lần?
Điều đó phụ thuộc vào tần suất mà đối thủ thực sự thay đổi giá và độ nhạy thời gian của thông tin đó — vài lần một ngày là đủ cho hầu hết các danh mục bán lẻ, trong khi các danh mục có khả năng chào hàng giảm giá có thể cần kiểm tra hàng giờ. Kiểm tra quá thường xuyên hơn mức thay đổi thực tế của giá sẽ lãng phí các cuộc gọi lấy dữ liệu mà không thêm tín hiệu hữu ích nào.
Q: Điều gì xảy ra nếu một đối thủ thay đổi bố cục trang của họ và mẫu giá ngừng khớp?
Hàm parse_price trả về None thay vì ném ra lỗi, vì vậy pipeline vẫn tiếp tục chạy cho mọi đối thủ được theo dõi khác; dòng bị ảnh hưởng được lưu trữ với giá null thay vì giá cũ hoặc sai. Một hệ thống sản xuất nên cảnh báo khi có nhiều lần trích xuất null cho cùng một URL, vì đó là tín hiệu cho thấy đánh dấu của trang đã thay đổi và mẫu cần được cập nhật — biểu thức regex một mẫu trong bài viết này là một điểm khởi đầu, không phải là giải pháp vĩnh viễn cho mọi trang mục tiêu.
Q: Điều này có hoạt động cho các trang có giá cả động hoặc các ưu đãi cá nhân chỉ hiển thị cho người dùng đã đăng nhập không?
Không như đã xây dựng — pipeline này lấy các trang sản phẩm công khai, và giá chỉ hiển thị cho tài khoản đã xác thực nằm ngoài phạm vi theo phần "Xử lý có trách nhiệm" ở trên. Giá cá nhân hoặc giá bị khóa tài khoản là một danh mục thu thập dữ liệu khác biệt (và nhạy cảm hơn) về mặt chất liệu hơn là giá niêm yết công khai.
Q: Điều này khác gì so với việc kiểm tra giá bằng tay?
Tính nhất quán và lịch sử. Một người kiểm tra bằng tay có xu hướng kiểm tra không đều và hiếm khi ghi lại mọi giá mà họ thấy, do đó không có lịch sử đáng tin cậy để so sánh sau này; pipeline này lưu trữ mọi lần kiểm tra — bao gồm cả những lần không thay đổi và thất bại — vì vậy giai đoạn phát hiện thay đổi luôn có một giá trị trước thực tế để so sánh với giá mới.
Q: Điều này có thể theo dõi nhiều hơn giá — như trạng thái kho hoặc chi phí vận chuyển không?
Có — mẫu này tổng quát trực tiếp. Thêm một mẫu trích xuất khác trong Giai đoạn 2 cho mỗi trường bổ sung, một cột khác trong bảng price_history, và mở rộng detect_change để so sánh bất kỳ trường nào quan trọng; các giai đoạn lấy, lưu trữ và cảnh báo không cần thay đổi chút nào.
Q: Chi phí để vận hành điều này đối với hàng chục hoặc hàng trăm sản phẩm của đối thủ là bao nhiêu?
Điều đó phụ thuộc vào khối lượng truy xuất hơn bất kỳ điều gì khác, vì việc khai thác, lưu trữ, và so sánh đều diễn ra tại chỗ và thực sự miễn phí ở quy mô này. Kiểm tra giá cả của một API truy xuất so với số lượng đối thủ cạnh tranh và kiểm tra tần suất dự kiến trước khi cam kết vào một nhịp độ cụ thể, vì các cuộc gọi truy xuất thường là mục hợp nhất duy nhất tăng theo quy mô.
Q: Việc thu thập giá cả của đối thủ có hợp pháp không?
Việc thu thập thông tin giá cả công khai thường có rủi ro thấp hơn so với việc thu thập dữ liệu cá nhân hoặc dữ liệu được bảo vệ bằng tài khoản, nhưng "thường có rủi ro thấp hơn" không đồng nghĩa với "không có rủi ro ở mọi nơi" — hãy kiểm tra điều khoản dịch vụ của trang web mục tiêu, giữ tốc độ yêu cầu ở mức hợp lý, và tránh thu thập bất cứ điều gì ngoài dữ liệu giá cả công khai và khả năng sẵn có mà quy trình này được định hướng đến. Đây không phải là lời khuyên pháp lý; hãy tham khảo ý kiến của luật sư cho một trang web hoặc khu vực pháp lý cụ thể nếu có sự không chắc chắn thực sự.
Xây dựng một trình tạo luồng công việc trực quan mã nguồn mở thực sự cho các tác nhân AI: một canvas React Flow kết hợp với một engine thực thi sắp xếp topo được xác minh, đã được kiểm tra từ đầu đến cuối với đầu ra thực tế đã được thu nhận.
Ivy Lin
Aug. 24th 2026
110M+ IP that voi ti le truy cap thanh cong 99.9%
Phan hoi trung binh ~0.5s cho tac vu dong thoi cao
Chi tu $0.1/GB
Truy cap ngay cac pool proxy residential, datacenter, IPv6 va ISP cao cap.