Đây là khâu "lấy nguyên liệu". Chất lượng báo cáo cuối phụ thuộc trực tiếp vào việc bạn tìm và chọn nguồn tốt. Rác vào thì rác ra (garbage in, garbage out).
Ba bước con của khâu thu thập
- Query expansion — biến 1 câu hỏi thành nhiều truy vấn.
- Search & rank — gọi search API, chấm điểm nguồn.
- Fetch & clean — tải trang được chọn, bóc lấy text chính.
Bước 1: Query expansion
Một truy vấn hiếm khi đủ. Câu hỏi "phí rút tiền của ví điện tử đối thủ" nên tách thành nhiều query cụ thể theo từng đối thủ và từng khía cạnh.
def expand_queries(goal, competitors):
qs = []
for c in competitors:
qs.append(f"{c} phí rút tiền 2026")
qs.append(f"{c} biểu phí chính thức site:{c}.vn")
return qs
Mẹo: thêm năm hiện tại và site: domain chính thức để ưu tiên nguồn gốc thay vì bài viết lại.
Bước 2: Search và chấm điểm nguồn
Không phải link nào cũng đáng đọc. Chấm điểm để đọc nguồn tốt trước, tiết kiệm token:
def score_source(item, official_domains):
score = 0
url = item["url"].lower()
if any(d in url for d in official_domains):
score += 5 # trang chính thức
if "2026" in item.get("title", ""):
score += 2 # mới
if any(b in url for b in ["blogspot", "pinterest"]):
score -= 3 # nguồn rác
return score
Ưu tiên: trang chính thức > báo uy tín > blog phân tích > diễn đàn. Với CI, trang tuyển dụng và trang giá của chính đối thủ là vàng.
Bước 3: Fetch và làm sạch
Snippet ngắn dễ gây hiểu sai. Tải nguyên trang rồi bóc phần nội dung chính (bỏ menu, quảng cáo):
import httpx, trafilatura
def fetch_clean(url, timeout=10):
try:
html = httpx.get(url, timeout=timeout, follow_redirects=True).text
text = trafilatura.extract(html) or ""
return text[:8000] # cắt để kiểm soát token
except Exception as e:
return "" # fail an toàn, không làm sập loop
Luôn cắt độ dài ([:8000]) để một trang dài không nuốt hết ngân sách token.
Xử lý trang chặn bot / render JS
- Nếu
fetch_cleantrả rỗng nhiều lần với một domain → có thể trang chặn bot hoặc render bằng JS. - Khi đó dùng Playwright (headless) cho riêng domain đó, đừng dùng cho tất cả (chậm và tốn).
- Tôn trọng
robots.txtvà đặtUser-Agenttrung thực; thêm delay giữa các request để không dội máy chủ.
Khung tư duy chọn nguồn: 3C
- Credible — ai xuất bản? Có phải nguồn gốc không?
- Current — thông tin của năm nào?
- Consistent — có ít nhất 2 nguồn nói giống nhau không?
Sai lầm thường gặp
- Chỉ dùng snippet rồi kết luận — snippet cắt xén, dễ hiểu sai ngữ cảnh.
- Đọc mọi link không lọc — đốt token vào nguồn rác.
- Không cắt độ dài trang — một trang 50k từ làm nổ ngân sách.
- Fetch làm sập cả loop khi 1 URL lỗi — thiếu try/except.
- Tin số liệu từ 1 nguồn duy nhất — không đối chiếu.
- Bỏ qua ngày tháng — lấy nhầm biểu phí của 2 năm trước.
Checklist khâu thu thập
- [ ] Mỗi đối thủ có ít nhất 2-3 query
- [ ] Nguồn được chấm điểm và sắp xếp
- [ ] Fetch có try/except, có cắt độ dài
- [ ] Ưu tiên domain chính thức
- [ ] Mỗi dữ kiện quan trọng có ≥2 nguồn
Ví dụ: một vòng thu thập hoàn chỉnh
def collect(goal, competitors, official_domains):
queries = expand_queries(goal, competitors)
hits = []
for q in queries:
for item in tv.search(q, max_results=5)["results"]:
item["score"] = score_source(item, official_domains)
hits.append(item)
hits.sort(key=lambda x: x["score"], reverse=True)
texts = {}
for item in hits[:12]: # chỉ đọc 12 nguồn tốt nhất
t = fetch_clean(item["url"])
if t:
texts[item["url"]] = t
return texts
Lưu ý ba quyết định kiểm soát chi phí trong đoạn này: giới hạn max_results=5 mỗi query, chỉ fetch 12 nguồn điểm cao nhất, và bỏ qua trang fetch rỗng. Ba giới hạn nhỏ này quyết định phần lớn chi phí một lần chạy — hãy chỉnh chúng theo ngân sách thay vì đọc mọi thứ.
Bài 4: biến đống text thô này thành dữ liệu có cấu trúc để so sánh.