Vnstock Logo

Ví dụ Thực tế

Mở rộng

Mục lục

Tài liệu bản cũ: vnstock_news v2.2.2

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.

Ví dụ ứng dụng thực tế
Phần này gồm 4 kịch bản theo dõi và sắp xếp tin tức cho nghiên cứu cá nhân: theo dõi RSS định kỳ, kết hợp nguồn RSS và Sitemap, làm sạch văn bản và chuẩn hóa múi giờ, và ghi danh mục tin vào cơ sở dữ liệu SQLite trên máy của bạn.

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):

Python
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:

Python
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):

Python
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:

Python
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)

Lưu ý về bản quyền và nhịp truy cập
Bạn tự kiểm tra 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.