🎯 Mục tiêu bài học
Sau bài học này, bạn sẽ:
✅ Thu thập dữ liệu từ website bằng requests + BeautifulSoup
✅ Gọi API và xử lý JSON response
✅ Xây dựng Data App với Streamlit (widgets, charts, layout)
✅ Quản lý state và caching trong Streamlit
✅ Build & deploy một Dashboard hoàn chỉnh
Thời gian: 3 giờ | Độ khó: Intermediate | Yêu cầu: Pandas (Bài 06), Visualization (Bài 08)
📖 Bảng Thuật Ngữ Quan Trọng
| Thuật ngữ | Tiếng Việt | Mô tả |
|---|---|---|
| Web Scraping | Thu thập dữ liệu web | Trích xuất dữ liệu từ HTML pages |
| API | Giao diện lập trình | Endpoint trả dữ liệu dạng JSON |
| HTTP Request | Yêu cầu HTTP | GET (đọc), POST (gửi) dữ liệu |
| HTML Parsing | Phân tích HTML | Trích xuất thông tin từ HTML |
| BeautifulSoup | — | Thư viện Python parse HTML |
| Streamlit | — | Framework xây Data App bằng Python |
| Session State | Trạng thái phiên | Lưu biến giữa các lần rerun |
| Caching | Bộ nhớ đệm | Lưu kết quả tránh chạy lại |
| Widget | Thành phần giao diện | Button, slider, selectbox… |
| Deploy | Triển khai | Đưa app lên internet |
Checkpoint
Web Scraping và API là 2 cách chính để thu thập dữ liệu. Streamlit biến Python script thành web app chỉ trong vài phút!
🌐 Web Scraping với Requests + BeautifulSoup
Web Scraping là gì? Là kỹ thuật tự động thu thập dữ liệu từ website. Khi dữ liệu không có sẵn dưới dạng CSV/Excel, bạn có thể viết Python để "cạo" (scrape) dữ liệu từ trang web và chuyển thành DataFrame.
HTML là gì? Là ngôn ngữ định dạng trang web. Mọi website đều được viết bằng HTML — gồm các tag như <h1>, <p>, <table>, <a>. BeautifulSoup giúp bạn "parse" (phân tích) HTML để lấy dữ liệu cần thiết.
Cài đặt
1pip install requests beautifulsoup4 lxmlHTTP Requests cơ bản
1import requests23# GET request4response = requests.get('https://httpbin.org/get')5print(response.status_code) # 200 = OK6print(response.headers['Content-Type'])7print(response.text) # Raw HTML/JSON string89# Với headers (giả lập browser)10headers = {11 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0'12}13response = requests.get('https://example.com', headers=headers)1415# POST request16data = {'username': 'test', 'password': '123'}17response = requests.post('https://httpbin.org/post', data=data)Parse HTML với BeautifulSoup
1from bs4 import BeautifulSoup2import requests34url = 'https://quotes.toscrape.com/'5response = requests.get(url)6soup = BeautifulSoup(response.text, 'lxml')78# Tìm elements9title = soup.find('title').text10print(f"Page title: {title}")1112# Tìm tất cả quotes13quotes = soup.find_all('div', class_='quote')14for quote in quotes:15 text = quote.find('span', class_='text').text16 author = quote.find('small', class_='author').text17 print(f"{text} — {author}")Trích xuất bảng HTML → DataFrame
1import pandas as pd2from bs4 import BeautifulSoup3import requests45url = 'https://en.wikipedia.org/wiki/List_of_countries_by_GDP_(nominal)'6response = requests.get(url)7soup = BeautifulSoup(response.text, 'lxml')89# Cách 1: pandas read_html (đơn giản nhất)10tables = pd.read_html(url)11df = tables[0] # Bảng đầu tiên1213# Cách 2: Parse thủ công14table = soup.find('table', class_='wikitable')15rows = []16for tr in table.find_all('tr')[1:]: # Skip header17 cells = [td.text.strip() for td in tr.find_all(['td', 'th'])]18 rows.append(cells)1920df = pd.DataFrame(rows)Crawl nhiều trang (Pagination)
1import time23all_quotes = []4for page in range(1, 11):5 url = f'https://quotes.toscrape.com/page/{page}/'6 response = requests.get(url)7 soup = BeautifulSoup(response.text, 'lxml')8 9 quotes = soup.find_all('div', class_='quote')10 if not quotes:11 break12 13 for q in quotes:14 all_quotes.append({15 'text': q.find('span', class_='text').text,16 'author': q.find('small', class_='author').text,17 'tags': [tag.text for tag in q.find_all('a', class_='tag')]18 })19 20 time.sleep(1) # Delay 1s — LUÔN respect server!2122df = pd.DataFrame(all_quotes)23print(f"Scraped {len(df)} quotes")Web Scraping Ethics:
- Luôn kiểm tra
robots.txt(ví dụ: example.com/robots.txt) - Thêm
time.sleep()giữa các requests (1-3 giây) - Không overload server — rate limiting!
- Đọc Terms of Service trước khi scrape
- Ưu tiên dùng API nếu có sẵn
Checkpoint
Hãy thử scrape 5 quotes từ quotes.toscrape.com và lưu vào DataFrame!
🔌 Thu Thập Dữ Liệu từ API
REST API cơ bản
1import requests2import pandas as pd34# Public API — no authentication5response = requests.get('https://api.github.com/users/octocat')6data = response.json() # Parse JSON7print(data['login'], data['public_repos'])89# API with parameters10params = {11 'q': 'python data science',12 'sort': 'stars',13 'order': 'desc',14 'per_page': 1015}16response = requests.get('https://api.github.com/search/repositories', params=params)17repos = response.json()['items']1819df = pd.DataFrame([{20 'name': r['full_name'],21 'stars': r['stargazers_count'],22 'language': r['language']23} for r in repos])API với Authentication
1# Bearer token2headers = {'Authorization': 'Bearer YOUR_API_KEY'}3response = requests.get('https://api.example.com/data', headers=headers)45# API key as parameter6params = {'api_key': 'YOUR_KEY', 'city': 'Hanoi'}7response = requests.get('https://api.openweathermap.org/data/2.5/weather', params=params)8weather = response.json()Error Handling
1def safe_request(url, max_retries=3):2 """Request with retry logic"""3 for attempt in range(max_retries):4 try:5 response = requests.get(url, timeout=10)6 response.raise_for_status() # Raise exception for 4xx/5xx7 return response.json()8 except requests.exceptions.Timeout:9 print(f"Timeout - retry {attempt + 1}/{max_retries}")10 except requests.exceptions.HTTPError as e:11 print(f"HTTP Error: {e}")12 break13 except requests.exceptions.RequestException as e:14 print(f"Error: {e}")15 break16 return NoneTip: Nhiều nguồn data miễn phí: GitHub API, OpenWeather, JSONPlaceholder, CoinGecko, News API. Ưu tiên API hơn scraping khi có thể!
Checkpoint
Gọi GitHub API lấy 5 repos trending Python và tạo DataFrame với columns: name, stars, language.
🚀 Streamlit — Hello World & Widgets
Cài đặt và chạy
1pip install streamlit2streamlit run app.pyHello World
1# app.py2import streamlit as st34st.set_page_config(page_title="My App", page_icon="📊", layout="wide")56st.title("Hello Streamlit! 👋")7st.write("This is my first Streamlit app")89st.header("This is a header")10st.markdown("**Bold** and *italic* text")11st.code("print('Hello World')", language="python")12st.latex(r"E = mc^2")Input Widgets
1import streamlit as st23# Text4name = st.text_input("Nhập tên", "MinAI")5st.write(f"Hello, {name}!")67# Numbers8age = st.number_input("Tuổi", min_value=0, max_value=120, value=25)9value = st.slider("Chọn giá trị", 0, 100, 50)1011# Selection12option = st.selectbox("Chọn màu", ["Red", "Green", "Blue"])13options = st.multiselect("Chọn trái cây", ["Apple", "Banana", "Cherry"])14choice = st.radio("Lựa chọn", ["Option 1", "Option 2"])1516# Boolean17agree = st.checkbox("Tôi đồng ý")1819# Date20date = st.date_input("Chọn ngày")2122# File23uploaded = st.file_uploader("Tải file CSV", type=["csv"])24if uploaded:25 df = pd.read_csv(uploaded)26 st.dataframe(df)Buttons
1# Click button2if st.button("Click me!"):3 st.write("Đã click!")45# Download6st.download_button("Download CSV", data="a,b\n1,2", file_name="data.csv")Checkpoint
Tạo file app.py với title, text input cho tên, slider cho tuổi, và hiển thị "Hello [name], bạn [age] tuổi!"
📊 Streamlit — Hiển Thị Data & Charts
DataFrames
1import streamlit as st2import pandas as pd34df = pd.DataFrame({5 "Name": ["Alice", "Bob", "Charlie"],6 "Age": [25, 30, 35],7 "Salary": [50000, 60000, 70000]8})910st.table(df) # Static table11st.dataframe(df) # Interactive (sortable)12edited_df = st.data_editor(df) # Editable!1314# KPI Metrics15col1, col2, col3 = st.columns(3)16col1.metric("Revenue", "$10,000", "+5%")17col2.metric("Users", "1,234", "-2%")18col3.metric("Rating", "4.5", "+0.2")Charts
1import streamlit as st2import plotly.express as px3import matplotlib.pyplot as plt4import seaborn as sns56# Streamlit native charts7st.line_chart(chart_data)8st.bar_chart(chart_data)9st.scatter_chart(chart_data, x="A", y="B")1011# Plotly (interactive — recommended!)12fig = px.scatter(df, x="gdpPercap", y="lifeExp",13 color="continent", size="pop", hover_name="country")14st.plotly_chart(fig, use_container_width=True)1516# Matplotlib/Seaborn17fig, ax = plt.subplots()18sns.histplot(df["col"], kde=True, ax=ax)19st.pyplot(fig)📐 Streamlit — Layout & Organization
Columns
1col1, col2 = st.columns([2, 1]) # 2:1 ratio2with col1:3 st.header("Main Content")4 st.line_chart([1, 2, 3, 4])5with col2:6 st.header("Sidebar")7 st.write("Extra info")Tabs
1tab1, tab2, tab3 = st.tabs(["📈 Chart", "🗃 Data", "📝 About"])2with tab1:3 st.plotly_chart(fig)4with tab2:5 st.dataframe(df)6with tab3:7 st.write("About this app")Sidebar
1st.sidebar.title("Settings")2option = st.sidebar.selectbox("Select", ["A", "B", "C"])3value = st.sidebar.slider("Value", 0, 100, 50)Expander & Forms
1with st.expander("Chi tiết"):2 st.write("Nội dung ẩn")34with st.form("my_form"):5 name = st.text_input("Tên")6 submitted = st.form_submit_button("Submit")7 if submitted:8 st.write(f"Hello {name}")⚡ Streamlit — State & Caching
Session State
1import streamlit as st23# Khởi tạo state4if 'count' not in st.session_state:5 st.session_state.count = 067if st.button('Tăng'):8 st.session_state.count += 1910if st.button('Reset'):11 st.session_state.count = 01213st.write(f"Count: {st.session_state.count}")Caching
1@st.cache_data2def load_data(url):3 """Chỉ chạy 1 lần, cache kết quả"""4 return pd.read_csv(url)56@st.cache_resource7def load_model():8 """Cache ML model / DB connection"""9 import pickle10 with open("model.pkl", "rb") as f:11 return pickle.load(f)1213# Sử dụng — tự động cache!14df = load_data("data.csv")15model = load_model()@st.cache_data cho data (DataFrame, list). @st.cache_resource cho resources (model, DB connection). Luôn cache các hàm load nặng!
🏗️ Complete Dashboard Example
1# dashboard.py2import streamlit as st3import pandas as pd4import numpy as np5import plotly.express as px67st.set_page_config(page_title="Sales Dashboard", page_icon="📊", layout="wide")8st.title("📊 Sales Dashboard")910# Load data11@st.cache_data12def load_data():13 np.random.seed(42)14 return pd.DataFrame({15 'Date': pd.date_range('2024-01-01', periods=365),16 'Sales': np.random.randint(100, 1000, 365),17 'Region': np.random.choice(['North', 'South', 'East', 'West'], 365),18 'Product': np.random.choice(['A', 'B', 'C'], 365)19 })2021df = load_data()2223# Sidebar filters24st.sidebar.header("Filters")25regions = st.sidebar.multiselect("Region", df['Region'].unique(), df['Region'].unique())26products = st.sidebar.multiselect("Product", df['Product'].unique(), df['Product'].unique())2728df_filtered = df[(df['Region'].isin(regions)) & (df['Product'].isin(products))]2930# KPIs31c1, c2, c3, c4 = st.columns(4)32c1.metric("Total Sales", f"${df_filtered['Sales'].sum():,.0f}")33c2.metric("Avg Sales", f"${df_filtered['Sales'].mean():,.0f}")34c3.metric("Max Sales", f"${df_filtered['Sales'].max():,.0f}")35c4.metric("Transactions", f"{len(df_filtered):,}")3637st.markdown("---")3839# Charts40col1, col2 = st.columns(2)41with col1:42 st.subheader("Sales by Region")43 fig = px.pie(df_filtered, values='Sales', names='Region', hole=0.4)44 st.plotly_chart(fig, use_container_width=True)4546with col2:47 st.subheader("Sales Trend")48 daily = df_filtered.groupby('Date')['Sales'].sum().reset_index()49 fig = px.line(daily, x='Date', y='Sales')50 st.plotly_chart(fig, use_container_width=True)5152# Data table53st.subheader("Raw Data")54st.dataframe(df_filtered, use_container_width=True)55csv = df_filtered.to_csv(index=False)56st.download_button("Download CSV", csv, "sales.csv", "text/csv")Deploy to Streamlit Cloud
Cấu trúc Streamlit App
- Push lên GitHub
- Vào share.streamlit.io
- Connect repo → Select
app.py→ Deploy
Checkpoint
Build một Dashboard mini: load CSV, sidebar filters, 2 KPI metrics, 1 Plotly chart, 1 data table. Chạy thử streamlit run app.py!
📝 Tổng Kết
Data Collection
| Method | Use When | Libraries |
|---|---|---|
| Web Scraping | No API, data in HTML tables | requests, BeautifulSoup |
| API | Service provides REST API | requests |
pd.read_html() | Simple HTML tables | pandas |
Streamlit Quick Reference
1import streamlit as st23# Display4st.title("Title")5st.dataframe(df)6st.plotly_chart(fig)7st.metric("KPI", value, delta)89# Input10st.text_input(), st.selectbox(), st.slider()11st.button(), st.file_uploader()1213# Layout14st.columns(), st.tabs(), st.sidebar, st.expander()1516# Performance17@st.cache_data, @st.cache_resource18st.session_stateBài tiếp theo: Ôn tập tổng hợp toàn bộ khóa học — từ Python cơ bản đến Streamlit! 📚
Câu hỏi tự kiểm tra
- Web scraping với
requests+BeautifulSoupgồm những bước cơ bản nào? response.status_codetrả về 200 và 404 có ý nghĩa gì? Tại sao cần kiểm tra status code?@st.cache_datatrong Streamlit dùng để làm gì? Tại sao nó giúp app chạy nhanh hơn?- Khi deploy Streamlit app lên cloud, cần chuẩn bị những file nào trong project?
🎉 Tuyệt vời! Bạn đã hoàn thành bài học Data Crawling & Streamlit!
Tiếp theo: Ôn tập tổng hợp toàn bộ khóa học để chuẩn bị cho Mini Project và bài kiểm tra cuối khóa!
