Tùy biến Nâng cao
Mục lục
Bản mặc định hiện là vnstock_news v3.1.1, gộp bản Cộng đồng và bản Mở rộng vào một thư viện. Trang này giữ lại cho máy còn cài vnstock_news v2.2.2: mã viết theo trang này vẫn chạy với bản đó. Đã cài bản mới thì đọc trang tương ứng của v3.1.1.
Máy đang cài bản nào: chạy python -c "import vnstock; print(vnstock.__version__)", ra số bắt đầu bằng 5. là thế hệ 5.
1. Cấu Trúc SiteConfig và Sitemap Động (DynamicSitemapResolver)
Cấu hình của từng website nguồn được lưu trữ tại bảng từ điển SITES_CONFIG trong vnstock_news.config.sites. Mỗi cấu hình bao gồm các thông tin:
name: Tên hiển thị của website (ví dụ:"Báo CafeF").domain: Tên miền chính (ví dụ:"cafef.vn").rss: Danh sách đường dẫn RSS Feed (urls: [...]).sitemap_url: Đường dẫn tới Sitemap XML chính.sitemap: Cấu hình Sitemap động khi website chia sitemap theo thời gian (pattern_typedạng"monthly"hoặc"incremental").config: Danh sách bộ chọn CSS/XPath để bóc tách tiêu đề, sapo, nội dung, thời gian xuất bản, tác giả và chuyên mục.
Xử lý Sitemap Động
Một số website tin tức (như Tiền Phong, Nhân Dân) phân chia sitemap theo từng tháng hoặc dạng lũy tiến.
Lớp DynamicSitemapResolver trong vnstock_news hỗ trợ tự động xác định đường dẫn Sitemap theo thời gian:
from vnstock_news.config.sitemap_resolver import DynamicSitemapResolver
from vnstock_news.config.sites import SITES_CONFIG
# 1. Khởi tạo resolver ở chế độ ghi log
resolver = DynamicSitemapResolver(debug=True)
# 2. Lấy cấu hình của website có sitemap động (ví dụ: Tiền Phong)
tienphong_config = SITES_CONFIG["tienphong"]
# 3. Tính toán URL sitemap phù hợp với thời điểm hiện tại (năm - tháng)
resolved_url = resolver.get_sitemap_url("tienphong", tienphong_config)
print(f"Sitemap URL được tạo: {resolved_url}")
# Kết quả trả về dạng: https://tienphong.vn/sitemaps/news-2026-10.xmlnews-2026-08.xml) hoặc theo ngày, bạn có thể thiết lập thuộc tính sitemap với pattern_type: "monthly" hoặc "incremental" cùng chuỗi định dạng format tương ứng.
2. Thiết Lập Bộ Chọn CSS Dự Phòng
Khi cấu hình giao diện bài viết giữa các chuyên mục không đồng nhất, vnstock_news hỗ trợ truyền danh sách nhiều bộ chọn (CSS Selectors) theo thứ tự ưu tiên. Thư viện sẽ lần lượt thử nghiệm các bộ chọn trong danh sách cho đến khi tìm thấy dữ liệu.
Các bộ chọn chuẩn trong config
Dictionary config hỗ trợ các khóa sau:
title_selector: Tiêu đề bài viết.short_desc_selector: Đoạn tóm tắt / Sapo.content_selector: Nội dung chính bài viết.publish_time_selector: Thời gian xuất bản.author_selector: Tên tác giả.category_selector: Chuyên mục.tags_selector: Từ khóa / Tags.image_url_selector: Ảnh đại diện bài viết.
Cấu hình bộ chọn dự phòng và thuộc tính thẻ
Một bộ chọn có thể cấu hình tìm kiếm theo thẻ HTML (tag), tên lớp (class) hoặc lấy trực tiếp thuộc tính thẻ (attr như content, datetime, src):
custom_advanced_selectors = {
# Tiêu đề: Thử class "detail-title" trước, nếu không có chuyển sang thẻ meta
"title_selector": [
{"tag": "h1", "class": "detail-title"},
{"tag": "meta", "attr": "property", "value": "og:title", "extract": "content"}
],
# Sapo bài viết (phần tóm tắt ở đầu)
"short_desc_selector": [
{"tag": "h2", "class": "sapo-detail"},
{"tag": "div", "class": "sapo"}
],
# Thời gian xuất bản: Lấy thuộc tính datetime trong thẻ <time> hoặc content của meta
"publish_time_selector": [
{"tag": "time", "attr": "datetime"},
{"tag": "meta", "attr": "property", "value": "article:published_time", "extract": "content"},
{"tag": "span", "class": "post-time"}
],
# Tác giả
"author_selector": {"tag": "span", "class": "author-name"}
}3. Tự Định Nghĩa Nguồn Tin Mới (custom_config)
Bạn có thể tự định nghĩa đối tượng custom_config để theo dõi tin từ các trang web chưa có sẵn trong cấu hình mặc định, nếu điều kiện sử dụng của trang đó cho phép.
custom_config, bạn không truyền tham số site_name (để site_name=None). Nếu truyền đồng thời cả site_name và custom_config, thư viện sẽ ưu tiên sử dụng cấu hình mặc định của site_name.
Ví dụ: Đọc tin từ website mới bằng AsyncBatchCrawler
import asyncio
import pandas as pd
from vnstock_news import AsyncBatchCrawler
async def fetch_custom_unsupported_site():
# 1. Định nghĩa cấu hình cho website mới
custom_site_profile = {
"site_name": "Trang tin tức custom",
"domain": "trangtin.example",
"config": {
"title_selector": {"tag": "h1", "class": "detail-title"},
"short_desc_selector": {"tag": "h2", "class": "detail-sapo"},
"publish_time_selector": {"tag": "div", "class": "detail-time"},
"author_selector": {"tag": "div", "class": "author-name"}
}
}
# 2. Khởi tạo AsyncBatchCrawler với custom_config (không truyền site_name)
client = AsyncBatchCrawler(
custom_config=custom_site_profile,
max_concurrency=3,
debug=False
)
# 3. Chỉ định nguồn truy xuất (RSS Feed hoặc Sitemap XML)
sources = ["https://trangtin.example/rss.xml"]
# 4. Thực thi truy xuất dữ liệu
df = await client.fetch_articles_async(
sources=sources,
top_n=5
)
if not df.empty:
print(f"Truy xuất thành công {len(df)} bài viết từ nguồn tùy chỉnh:")
print(df[["title", "publish_time", "author"]].head())
else:
print("Không lấy được dữ liệu.")
if __name__ == "__main__":
asyncio.run(fetch_custom_unsupported_site())4. Phân Tích Xu Hướng Từ Khóa Với TrendingAnalyzer
Lớp TrendingAnalyzer hỗ trợ thống kê tần suất xuất hiện của các cụm từ (n-gram) trong nội dung bài viết để phân tích xu hướng tin tức.
TrendingAnalyzer cần được nhập từ module tương ứng: from vnstock_news.trending.analyzer import TrendingAnalyzer.
Các phương thức chính của TrendingAnalyzer
update_trends(text, ngram_range=[2, 3, 4]): Cập nhật tần suất xuất hiện của các cụm từ trong đoạn văn bản.get_top_trends(top_n=20): Trả về danh sách các cụm từ xuất hiện nhiều nhất cùng tần suất.reset_trends(): Đặt lại bộ đếm tần suất.
Ví dụ phân tích cụm từ xu hướng
import asyncio
from vnstock_news.trending.analyzer import TrendingAnalyzer
from vnstock_news import AsyncBatchCrawler, SITES_CONFIG
async def analyze_market_trending_phrases():
# 1. Tải bài viết từ CafeF
site = "cafef"
sitemap_url = SITES_CONFIG[site]["sitemap_url"]
client = AsyncBatchCrawler(site_name=site, max_concurrency=3)
df = await client.fetch_articles_async(sources=[sitemap_url], top_n=30)
if df.empty:
print("Không có dữ liệu bài viết để phân tích.")
return
# 2. Khởi tạo TrendingAnalyzer
analyzer = TrendingAnalyzer(min_token_length=3)
# 3. Cập nhật cụm từ (2, 3, 4 từ) từ tiêu đề và tóm tắt
for _, row in df.iterrows():
title = str(row.get("title", ""))
desc = str(row.get("short_description", ""))
full_text = f"{title} {desc}"
analyzer.update_trends(full_text, ngram_range=[2, 3, 4])
# 4. Lấy top 15 cụm từ xuất hiện nhiều nhất
top_phrases = analyzer.get_top_trends(top_n=15)
print("Danh sách cụm từ phổ biến:")
for rank, (phrase, count) in enumerate(top_phrases.items(), 1):
print(f" {rank:2d}. Cụm từ: '{phrase:25s}' - Số lần: {count:3d}")
if __name__ == "__main__":
asyncio.run(analyze_market_trending_phrases())5. Kiểm Tra Cấu Hình Trên Nhiều Trang Tin
Đoạn mã dưới đây hỗ trợ kiểm tra cấu hình bộ chọn trên nhiều website tin tức khác nhau, đồng thời kiểm tra tính đầy đủ của các trường thông tin chính (title, publish_time, author):
import asyncio
import pandas as pd
import warnings
from vnstock_news import AsyncBatchCrawler, SITES_CONFIG
warnings.filterwarnings("ignore")
async def run_multi_site_pipeline_and_health_check():
health_reports = []
collected = []
# Liệt kê rõ từng trang thay vì lấy theo thứ tự danh mục, để chỉ chạy trên
# các trang bạn đã kiểm tra robots.txt và điều kiện sử dụng.
for site in ["cafef", "cafebiz", "vietstock", "vneconomy", "vnexpress"]:
print(f"Đang kiểm tra trang: [{site.upper()}] ...")
cfg = SITES_CONFIG[site]
client = AsyncBatchCrawler(site_name=site, max_concurrency=2)
sources = []
if "rss" in cfg and cfg["rss"].get("urls"):
sources = [cfg["rss"]["urls"][0]]
elif "sitemap_url" in cfg:
sources = [cfg["sitemap_url"]]
if not sources:
health_reports.append({"site": site, "status": "FAIL (No Source)"})
continue
try:
df = await client.fetch_articles_async(sources=sources, top_n=3)
if df.empty:
health_reports.append({"site": site, "status": "EMPTY"})
else:
df["source_site"] = site
collected.append(df)
row = df.iloc[0]
def _is_valid(val):
if pd.isna(val) or val is None: return False
if isinstance(val, str) and not val.strip(): return False
return True
report = {
"site": site,
"status": "SUCCESS",
"title": "✅" if _is_valid(row.get("title")) else "❌",
"publish_time": "✅" if _is_valid(row.get("publish_time")) else "❌",
"author": "✅" if _is_valid(row.get("author")) else "❌"
}
health_reports.append(report)
except Exception as e:
health_reports.append({"site": site, "status": "ERROR", "error": str(e)})
# Hiển thị kết quả kiểm tra
df_health = pd.DataFrame(health_reports)
print("\nKết quả kiểm tra dữ liệu:")
print(df_health.to_string(index=False))
if __name__ == "__main__":
asyncio.run(run_multi_site_pipeline_and_health_check())