Ví dụ Thực tế
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.
Kịch Bản 1: Cập Nhật Tin Tức Định Kỳ Qua RSS Feed
Sử dụng lớp Crawler() kiểm tra các kênh RSS Feed kết hợp thư viện schedule giúp bạn thiết lập quá trình theo dõi tin tức phát hành theo chu kỳ (ví dụ mỗi 30-60 phút):
import time
import schedule
import pandas as pd
from vnstock_news import Crawler
def monitor_financial_news():
print("Đang tiến hành quét các bản tin mới nhất qua RSS...")
# 1. Khởi tạo đối tượng với nguồn tin hỗ trợ RSS (ví dụ: Dân Trí, CafeBiz, VnExpress)
client = Crawler(site_name="dantri")
# 2. Lấy 10 bài viết mới nhất
articles = client.get_articles_from_feed(limit_per_feed=10)
if articles:
df = pd.DataFrame(articles)
print(f"Ghi nhận {len(df)} bài viết mới:")
for idx, row in df.head(3).iterrows():
print(f" - [{row.get('publish_time')}] {row.get('title')} ({row.get('url')})")
else:
print("Không có bản tin mới trong lượt quét này.")
# Lên lịch tự động chạy mỗi 30 phút một lần
schedule.every(30).minutes.do(monitor_financial_news)
if __name__ == "__main__":
# Chạy lần đầu
monitor_financial_news()
# Duyệt vòng lặp chờ theo lịch
print("Chương trình theo dõi tin tức đang chạy...")
while True:
schedule.run_pending()
time.sleep(10)Kịch Bản 2: Kết Hợp Nguồn RSS và Sitemap XML
Lượng bài lưu trữ trên RSS feed thường giới hạn trong số lượng bài viết mới phát hành gần nhất. Việc kết hợp gộp dữ liệu giữa RSS Feed và Sitemap XML giúp bổ sung các bài viết đã bị đẩy khỏi feed RSS:
import os
import pandas as pd
from vnstock_news import Crawler
def fetch_combined_rss_and_sitemap_news():
site = "cafebiz"
client = Crawler(site_name=site)
# Bước 1: Lấy bài viết từ RSS Feed
print("1. Đang quét RSS Feed...")
rss_articles = client.get_articles_from_feed(limit_per_feed=20)
rss_df = pd.DataFrame(rss_articles) if rss_articles else pd.DataFrame()
# Bước 2: Lấy bài viết từ Sitemap XML
print("2. Đang quét Sitemap XML...")
sitemap_articles = client.get_articles(limit=50)
sitemap_df = pd.DataFrame(sitemap_articles) if sitemap_articles else pd.DataFrame()
# Bước 3: Gộp dữ liệu và loại bỏ các bài viết trùng URL
if not rss_df.empty or not sitemap_df.empty:
if not sitemap_df.empty and 'loc' in sitemap_df.columns:
sitemap_df = sitemap_df.rename(columns={'loc': 'url'})
combined_df = pd.concat([rss_df, sitemap_df], ignore_index=True)
total_before = len(combined_df)
combined_df = combined_df.drop_duplicates(subset=['url'])
total_after = len(combined_df)
os.makedirs("output", exist_ok=True)
out_csv = "output/combined_market_news.csv"
combined_df.to_csv(out_csv, index=False, encoding="utf-8-sig")
print(f"Hoàn tất gộp. Loại bỏ {total_before - total_after} bài trùng.")
print(f"Tổng số bài viết thu được: {total_after}")
print(f"Dữ liệu lưu tại: {out_csv}")
if __name__ == "__main__":
fetch_combined_rss_and_sitemap_news()Kịch Bản 3: Tiền Xử Lý Dữ Liệu Văn Bản và Chuẩn Hóa Múi Giờ
Văn bản đọc từ các trang tin cần được loại bỏ thẻ HTML thừa, chuẩn hóa khoảng trắng và chuyển đổi múi giờ về múi giờ Việt Nam (Asia/Ho_Chi_Minh UTC+7):
import re
import pandas as pd
from collections import Counter
def clean_vietnamese_text(text):
"""Xóa thẻ HTML và khoảng trắng thừa"""
if pd.isna(text) or not text:
return ""
text = re.sub(r'<[^>]+>', '', str(text))
text = re.sub(r'[\r\n\t]+', ' ', text)
text = re.sub(r'\s+', ' ', text).strip()
return text
def normalize_dataframe_timestamps(df, datetime_col="publish_time"):
"""Chuẩn hóa thời gian về múi giờ Việt Nam (UTC+7)"""
if datetime_col in df.columns:
df[datetime_col] = pd.to_datetime(df[datetime_col], errors='coerce')
if df[datetime_col].dt.tz is None:
df[datetime_col] = df[datetime_col].dt.tz_localize('UTC')
df[datetime_col] = df[datetime_col].dt.tz_convert('Asia/Ho_Chi_Minh')
return df
def extract_top_keywords(df, text_column="title", top_n=15):
"""Thống kê các từ xuất hiện nhiều nhất trong tiêu đề"""
words = []
for text in df[text_column].dropna():
cleaned = clean_vietnamese_text(text).lower()
tokens = [w for w in re.findall(r'\w+', cleaned) if len(w) >= 3]
words.extend(tokens)
counts = Counter(words)
return counts.most_common(top_n)
if __name__ == "__main__":
raw_data = [
{
"title": "Cổ phiếu Vietcombank (VCB) tăng vọt vượt đỉnh lịch sử",
"content": "<p>Thị trường chứng khoán chứng kiến <b>lực cầu mạnh</b>...</p>",
"publish_time": "2026-08-07T02:30:00Z"
},
{
"title": "Ngân hàng Nhà nước giảm lãi suất điều hành hỗ trợ doanh nghiệp",
"content": "<div>Động thái hạ lãi suất giúp tăng thanh khoản...</div>",
"publish_time": "2026-08-07T04:15:00Z"
}
]
df = pd.DataFrame(raw_data)
# 1. Làm sạch văn bản
df['title'] = df['title'].apply(clean_vietnamese_text)
df['content'] = df['content'].apply(clean_vietnamese_text)
# 2. Chuẩn hóa múi giờ
df = normalize_dataframe_timestamps(df)
print("Dữ liệu sau khi chuẩn hóa múi giờ:")
print(df[['title', 'publish_time']])
# 3. Trích xuất từ khóa
top_words = extract_top_keywords(df, text_column="title")
print("\nDanh sách từ khóa phổ biến:")
for word, freq in top_words:
print(f" - {word:15s}: {freq} lần")Kịch Bản 4: Ghi Danh Mục Tin Vào SQLite
Danh mục tin (tiêu đề, tóm tắt, thời gian, đường dẫn) đọc bằng vnstock_news có thể được ghi vào cơ sở dữ liệu SQLite trên máy của bạn để tra cứu cá nhân; nội dung bài viết thì bạn đọc tại trang gốc, bằng câu lệnh INSERT OR REPLACE để cập nhật dữ liệu theo khóa chính url:
import sqlite3
import pandas as pd
from vnstock_news import Crawler
def init_news_database(db_path="financial_news.db"):
"""Tạo bảng cơ sở dữ liệu và chỉ mục"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS articles (
url TEXT PRIMARY KEY,
title TEXT NOT NULL,
short_description TEXT,
publish_time TEXT,
author TEXT,
source TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_publish_time ON articles(publish_time)")
cursor.execute("CREATE INDEX IF NOT EXISTS idx_source ON articles(source)")
conn.commit()
conn.close()
def upsert_articles_to_db(articles_list, source_name="dantri", db_path="financial_news.db"):
"""Ghi dữ liệu vào SQLite"""
if not articles_list:
return 0
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
upsert_query = """
INSERT OR REPLACE INTO articles
(url, title, short_description, publish_time, author, source)
VALUES (?, ?, ?, ?, ?, ?)
"""
records = []
for a in articles_list:
records.append((
a.get("url"),
a.get("title"),
a.get("short_description"),
str(a.get("publish_time")),
a.get("author"),
source_name
))
cursor.executemany(upsert_query, records)
conn.commit()
inserted_count = cursor.rowcount
conn.close()
return inserted_count
if __name__ == "__main__":
db_file = "financial_news.db"
init_news_database(db_file)
client = Crawler(site_name="dantri")
articles = client.get_articles_from_feed(limit_per_feed=15)
if articles:
count = upsert_articles_to_db(articles, source_name="dantri", db_path=db_file)
print(f"Đã chèn hoặc cập nhật {count} bản ghi vào CSDL SQLite: {db_file}")
conn = sqlite3.connect(db_file)
df_db = pd.read_sql_query("SELECT url, title, publish_time, source FROM articles LIMIT 5", conn)
conn.close()
print("\nMẫu dữ liệu trong CSDL:")
print(df_db)robots.txt và điều kiện sử dụng của từng trang tin trước khi đọc, và giữ khoảng nghỉ giữa các yêu cầu để không ảnh hưởng tới máy chủ của trang. Nội dung bài viết và hình ảnh thuộc về toà soạn. Các kịch bản trên chỉ làm việc với tiêu đề, tóm tắt, thời gian và đường dẫn để bạn đọc bài tại trang gốc; thông tin đọc được chỉ dùng cho nghiên cứu, học tập cá nhân, không đăng lại hay phân phối. Muốn dùng nội dung vào mục đích khác, bạn cần thoả thuận riêng với toà soạn sở hữu nội dung.