📊 LHB-StockPrice — Project Documentation
1. ภาพรวม (Overview)
LHB-StockPrice เป็นโปรเจกต์ Python สำหรับ Scrape ข้อมูลหุ้น/กองทุนจากเว็บไซต์ SET (Stock Exchange of Thailand) โดยรัน Flow การทำงานต่าง ๆ ตามลำดับ
โปรเจกต์นี้ถูกออกแบบมาเพื่อทำงาน 4 ภารกิจหลัก: 1. ดึงข้อมูลโปรไฟล์กองทุน (Fund Profile) จาก SET API 2. สร้าง Email Sheet พร้อมราคา Bid ล่าสุด 3. ดึงข้อมูล Dividend (XD) ของแต่ละกองทุน 4. สร้าง Accumulated Report (รายงานสะสม) ของ Corporate Actions (XD/XN)
2. โครงสร้างโปรเจกต์ (Project Structure)
LHB-StockPrice/
├── main.py # Entry point — รัน 4 Flows ตามลำดับ
├── config.json # ตั้งค่า Path, Playwright
├── requirements.txt # Dependencies
├── instruction.md # เอกสารอธิบายโปรเจกต์ (ไฟล์นี้)
│
├── config/ # ตั้งค่าต่าง ๆ
│ ├── settings.py # ค่าคงที่พื้นฐาน (Path, API URL, Retry)
│ ├── config_loader.py # โหลด config.json
│ ├── global_config.py # Global Config (Input/Output/Playwright)
│ ├── accumulated_config.py # Config สำหรับ Accumulated Report
│ ├── dividend_sheet_config.py # Config สำหรับ Dividend Sheet (Group, Column, Color)
│ └── email_sheet_config.py # Config สำหรับ Email Sheet (Group, Column, Color)
│
├── flows/ # Workflow หลัก
│ ├── stock_profile_flow.py # Flow ที่ 1: Scrape Stock Profile
│ ├── email_sheet_flow.py # Flow ที่ 2: สร้าง Email Sheet + ราคา Bid
│ ├── dividend_paid_flow.py # Flow ที่ 3: ดึงข้อมูล Dividend (XD)
│ └── accumulated_report_flow.py # Flow ที่ 4: สร้าง Accumulated Report
│
├── tasks/ # หน่วยย่อยของ Flow
│ ├── session_tasks.py # เริ่มต้น Session
│ ├── excel_tasks.py # โหลด/บันทึก Excel (Stock Profile)
│ ├── scrape_tasks.py # Scrape Stock Profile
│ ├── stock_price_tasks.py # Scrape ราคา Bid ด้วย Playwright
│ ├── email_excel_tasks.py # จัดการ Excel สำหรับ Email Sheet
│ ├── dividend_excel_tasks.py # จัดการ Excel สำหรับ Dividend Sheet
│ ├── dividend_tasks.py # Scrape Dividend Data
│ ├── accumulated_excel_tasks.py # โหลด Symbol สำหรับ Accumulated Report
│ └── accumulated_writer_tasks.py # เขียนข้อมูล Corporate Actions ลง Excel
│
├── services/ # Business Logic / API Calls
│ ├── session_service.py # จัดการ Session requests (Initialize/Refresh)
│ ├── browser_service.py # จัดการ Browser Playwright
│ ├── set_api_service.py # เรียก SET API (Company Profile + Fund Profile)
│ ├── set_corporate_action_service.py # เรียก SET API Corporate Actions
│ ├── set_dividend_service.py # เรียก SET API Dividend (XD)
│ └── set_price_service.py # Scrape ราคา Bid จาก HTML (Regex)
│
├── models/ # Data Models
│ └── stock_result.py # StockResult Dataclass
│
├── utils/ # Utility Functions
│ ├── date_utils.py # จัดการวันที่ (Format, Convert)
│ ├── delay_utils.py # Human-like Delay (Random Sleep)
│ ├── excel_style_utils.py # จัดรูปแบบ Excel (Email Sheet)
│ ├── dividend_excel_style_utils.py # จัดรูปแบบ Excel (Dividend Sheet)
│ ├── accumulated_excel_style_utils.py # จัดรูปแบบ Excel (Accumulated Report)
│ ├── extract_utils.py # Extract Management Company / Trustee
│ └── file_utils.py # จัดการไฟล์ (สร้าง Workbook, Directory)
│
├── dataInput/ # ไฟล์ Input Excel
│ ├── mockupInput.xlsx # ไฟล์ต้นฉบับที่มี Symbol หุ้น/กองทุน
│ └── Bak/ # Backup
│
├── dataOutput/ # Output ที่สร้างขึ้น
│ ├── All_Stock_XN_XD.xlsx # Dividend Sheet Output
│ ├── Result_Email_LastBid_*.xlsx # Email Sheet Output
│ ├── Bak/ # Backup
│ └── symbolReport/ # Accumulated Report Output
│ └── Result_Accumulated_*.xlsx
│
├── debug/ # Scripts สำหรับ Debug
│ ├── debug_api.py
│ ├── debug_dividend.py
│ └── debug_xd_data.py
│
├── test/ # Test Scripts
│ ├── readMemory.py
│ ├── reverseSETapi.py
│ └── test.py
│
└── logs/ # Log Files
3. ข้อมูลนำเข้า (Input)
3.1 ไฟล์ config.json
ไฟล์กำหนดค่าหลักของโปรเจกต์ อยู่ใน root directory:
{
"input_file": "D:\\Projects\\LHB-StockPrice\\dataInput\\mockupInput.xlsx",
"output_root": "D:\\Projects\\LHB-StockPrice\\dataOutput",
"accumulated_output_dir": "D:\\Projects\\LHB-StockPrice\\dataOutput\\symbolReport",
"playwright": {
"headless": false,
"timeout": 30000,
"wait_after_load_ms": 3000,
"chrome_paths": [
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
]
}
}
| Field | คำอธิบาย |
|---|---|
input_file |
Path ไฟล์ Excel ต้นทางที่มีรายการ Symbol |
output_root |
Root directory สำหรับ Output ทั่วไป |
accumulated_output_dir |
Directory สำหรับ Accumulated Report |
playwright.headless |
เปิด/ปิดโหมด Headless ของ Browser |
playwright.timeout |
Timeout ของ Playwright (ms) |
playwright.wait_after_load_ms |
ระยะเวลารอหลังจากหน้าโหลดเสร็จ |
playwright.chrome_paths |
รายการ Path ของ Chrome executable |
3.2 ไฟล์ mockupInput.xlsx
เป็นไฟล์ Excel ต้นทางที่อยู่ใน dataInput/ มีโครงสร้างดังนี้:
| Column | หัวข้อ | รายละเอียด |
|---|---|---|
| A | group_name |
ชื่อกลุ่มกองทุน (เช่น LHB, LHFG, LH FUND, LHS) |
| B | symbol |
รหัสหลักทรัพย์ (เช่น DIF, FUTURERT, LHRREIT) |
แถวที่ 1 เป็น Header (หัวตาราง)
ข้อมูลเริ่มตั้งแต่แถวที่ 2 เป็นต้นไป
4. 4 Workflow Flows หลัก
โปรแกรมจะรัน 4 Flows ตามลำดับ จาก main.py:
Flow ที่ 1: STOCK_PROFILE_FLOW (flows/stock_profile_flow.py)
วัตถุประสงค์: ดึงข้อมูลโปรไฟล์กองทุนจาก SET API และบันทึกลง Excel
ผังการทำงาน:
init_session()
│
▼
load_excel() ──► คัดลอก mockupInput.xlsx → dataOutput/Result_Email_LastBid_{timestamp}.xlsx
│
▼
Loop ทุกแถว (row 2 → max_row):
│
├── อ่าน stock_symbol จาก Column B
│
├── ทุก 30 รายการ → refresh_session() ← ล้าง Cookies แล้ว Initialize ใหม่
│
├── scrape_stock(symbol)
│ │
│ └──► SET API: /api/set/company/{symbol}/profile
│ └── fund_name (ชื่อกองทุน)
│ SET API: /api/set/stock/{symbol}/fund-profile
│ ├── amCompanies[0].name (ชื่อบริษัทจัดการ)
│ └── trustees[0] (ผู้ดูแลผลประโยชน์)
│
└── save_row()
├── Column C = FundName
├── Column D = CompanyFullname
└── Column E = Trustee
human_delay(3, 7) ← หน่วงเวลา 3-7 วินาที
Output: dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet "Input" — Column C, D, E)
การจัดการ Session Refresh: เมื่อ HTTP Status เป็น 401, 403, หรือ 429 จะทำการ Refresh Session อัตโนมัติ
Flow ที่ 2: EMAIL_SHEET_FLOW (flows/email_sheet_flow.py)
วัตถุประสงค์: สร้าง Excel Sheet สำหรับอีเมล พร้อมราคา Bid ล่าสุดของแต่ละกองทุน โดยใช้ Playwright เปิด Browser จริง
การทำงาน: ใช้ไฟล์ dataOutput/Result_Email_LastBid_{timestamp}.xlsx ร่วมกับ Flow 1 และ Flow 3 โดยตรวจสอบว่ามีไฟล์อยู่แล้วหรือไม่ (ensure_output_file())
ผังการทำงาน:
load_group_symbols() ──► อ่าน Symbol จาก Input → {group: [symbols]}
│
▼
ensure_output_file() ──► ใช้ไฟล์ที่มีอยู่แล้ว หรือคัดลอกใหม่ (Fallback)
│
▼
create_email_sheet() ──► สร้าง Sheet "Email" พร้อมหัวตาราง
│
▼
Loop แต่ละ Group:
│
└── Loop แต่ละ Symbol:
│
└── scrape_last_bid(symbol) ← ใช้ Playwright
│
└──► เปิด URL: https://www.set.or.th/en/market/product/stock/quote/{symbol}/price
│
▼
รอ 3 วินาที (wait_after_load_ms)
│
▼
ค้นหา element: "Bid Price / Volume (Shares)"
│
▼
ดึงข้อความจาก parent element
│
▼
Regex สกัดเฉพาะตัวเลขราคา Bid (เช่น "4.40" จาก "4.40 / 300")
│
▼
write_email_sheet() ──► เขียนข้อมูล {symbol, bid} ลง Sheet
โครงสร้าง Sheet "Email":
| Col | กลุ่ม LHB (สีเทา) | Col | กลุ่ม LHFG (สีเหลือง) | Col | กลุ่ม LH FUND (สีเขียว) | Col | กลุ่ม LHS (สีฟ้า) |
|---|---|---|---|---|---|---|---|
| B | Symbol | E | Symbol | H | Symbol | K | Symbol |
| C | Last Bid | F | Last Bid | I | Last Bid | L | Last Bid |
Column A, D, G, J ถูกตั้งค่าความกว้าง 5 เป็น Space คั่นระหว่างกลุ่ม
Output: dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet "Email")
Flow ที่ 3: DIVIDEND_PAID_FLOW (flows/dividend_paid_flow.py)
วัตถุประสงค์: ดึงข้อมูล Dividend (XD) ล่าสุดของแต่ละกองทุน
ผังการทำงาน:
load_group_symbols() ──► อ่าน Symbol จาก Input → {group: [symbols]}
│
▼
create_dividend_sheet() ──► สร้าง Sheet "Dividend Paid"
│
├── Group Sections (LHB, LHFG, LH FUND, LHS)
│ └── แต่ละกลุ่ม: Symbol, XD-Date, Book Closing Date, Dividend
│
└── All Section (รวมทุก Symbol)
└── Symbol, XD-Date, Book Closing Date, Dividend, FundName, CompanyFullname
│
▼
Loop แต่ละ Group:
│
└── Loop แต่ละ Symbol:
│
├── initialize_session(symbol) ← เริ่มต้น Session
│
└── get_latest_xd(symbol)
│
└──► SET API: /api/set/stock/{symbol}/corporate-action?lang=en
│
▼
ค้นหา caType == "XD" → ใช้รายการแรก
│
▼
ดึงค่า: xdate, bookCloseDate, dividend
│
▼
write_dividend_sheet()
│
├── Group Section: เขียน Symbol, XD-Date, Book Closing Date, Dividend
│
└── All Section: เขียนเพิ่ม FundName, CompanyFullname
│
└── เรียก get_stock_profile() เพื่อดึงข้อมูลโปรไฟล์ (ใช้ Cache)
การจัดวาง Group Sections:
| Col | กลุ่ม | สี | คอลัมน์ |
|---|---|---|---|
| 2-5 | LHB | เทา D9D9D9 |
Symbol, XD-Date, Book Closing, Dividend |
| 7-10 | LHFG | เหลือง FFF2CC |
Symbol, XD-Date, Book Closing, Dividend |
| 12-15 | LH FUND | เขียว E2F0D9 |
Symbol, XD-Date, Book Closing, Dividend |
| 17-20 | LHS | ฟ้า DDEBF7 |
Symbol, XD-Date, Book Closing, Dividend |
| 22-27 | ALL | ชมพู F4CCCC |
Symbol, XD-Date, Book Closing, Dividend, FundName, CompanyFullname |
Output: dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet: "Dividend Paid")
Flow ที่ 4: ACCUMULATED_REPORT_FLOW (flows/accumulated_report_flow.py)
วัตถุประสงค์: สร้างรายงานสะสม Corporate Actions (XD/XN) แยกตาม Symbol
ผังการทำงาน:
load_input_symbols() ──► อ่าน Symbol จาก Input
│ └── ข้ามกลุ่มที่มีคำว่า "CANCEL"
▼
Loop แต่ละ Symbol:
│
├── สร้างไฟล์: Result_Accumulated_{group}_{symbol}_{timestamp}.xlsx
│
├── create_workbook() → สร้าง Workbook เปล่า
│ └── Sheets: XD, XN
│
├── initialize_sheets() → ใส่ Header
│ ├── XD: Search Date, Stock, X-Date, Book Closing Date, Record Date,
│ │ Payment Date, Type, Dividend Per Share, Unit, Operation Period,
│ │ Source of Dividend
│ │
│ └── XN: Search Date, Stock, X-Date, Book Closing Date, Record Date,
│ Payment Date, Capital Return per Share, Unit
│
├── get_corporate_actions(symbol)
│ │
│ └──► SET API: /api/set/stock/{symbol}/corporate-action?lang=en
│
└── write_corporate_actions()
│
├── ถ้า caType == "XD" → เขียนใน Sheet XD
│ └── ข้อมูล: search_date, symbol, xdate, bookCloseDate,
│ recordDate, paymentDate, dividendType, dividend,
│ currency, operation_period, sourceOfDividend
│
└── ถ้า caType == "XN" → เขียนใน Sheet XN
└── ข้อมูล: search_date, symbol, xdate, bookCloseDate,
recordDate, paymentDate, returnAmount, currency
Output: dataOutput/symbolReport/Result_Accumulated_{group}_{symbol}_{timestamp}.xlsx
- แต่ละ Symbol จะได้ไฟล์แยกกัน
- ภายในไฟล์มี 2 Sheets: XD และ XN
5. Services (Business Logic)
5.1 services/session_service.py
จัดการ requests.Session ส่วนกลางที่ใช้ร่วมกันในหลาย Services:
| ฟังก์ชัน | คำอธิบาย |
|---|---|
initialize_session(symbol="SCB") |
เรียกหน้า SET 4 หน้าเพื่อสร้าง Session Cookies: home → market → stock → quote/{symbol}/price |
refresh_session() |
ล้าง Cookies ทั้งหมด และ Initialize ใหม่ |
get_api_headers(symbol) |
สร้าง Headers พร้อม Referer URL ที่ถูกต้องตาม Symbol |
Headers ที่ใช้:
{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "th-TH,th;q=0.9,en-US;q=0.8,en;q=0.7",
"Connection": "keep-alive",
"Referer": "https://www.set.or.th/"
}
5.2 services/browser_service.py
ใช้ Playwright สำหรับจัดการ Browser:
| ฟังก์ชัน | คำอธิบาย |
|---|---|
find_chrome_executable() |
ค้นหา Chrome executable จาก paths ใน config |
create_browser() |
เปิด Browser (Headless หรือ Normal) พร้อม Page |
ค่าที่ Return:
{
"playwright": playwright_instance,
"browser": browser_instance,
"page": page_instance,
"wait_after_load_ms": 3000 # จาก config
}
5.3 services/set_api_service.py
เรียก SET API สำหรับข้อมูลโปรไฟล์:
| API Endpoint | รายละเอียด |
|---|---|
GET /api/set/company/{symbol}/profile?lang=th |
ดึงข้อมูล Company Profile → name (FundName) |
GET /api/set/stock/{symbol}/fund-profile?lang=th |
ดึงข้อมูล Fund Profile → amCompanies[0].name (CompanyFullname), trustees[0] (Trustee) |
Retry Logic:
- จำนวน Retry: 3 ครั้ง (กำหนดใน config/settings.py)
- เงื่อนไข Refresh Session: HTTP 401, 403, 429
- Human Delay: 2-5 วินาทีก่อนแต่ละ Request
5.4 services/set_corporate_action_service.py
| ฟังก์ชัน | คำอธิบาย |
|---|---|
ensure_session() |
ตรวจสอบและเริ่มต้น Session ถ้ายังไม่เคย Initialize |
get_corporate_actions(symbol) |
เรียก /api/set/stock/{symbol}/corporate-action?lang=en |
รองรับ Response 2 รูปแบบ:
1. Array ([...]) → ใช้โดยตรง
2. Object ({"corporateActions": [...]}) → ดึงจาก key corporateActions
5.5 services/set_dividend_service.py
| ฟังก์ชัน | คำอธิบาย |
|---|---|
get_latest_xd(symbol) |
ค้นหา XD ล่าสุดจาก Corporate Actions |
ขั้นตอน:
1. Initialize Session
2. เรียก /api/set/stock/{symbol}/corporate-action?lang=en
3. วน Loop หา caType == "XD" → ใช้รายการแรกที่เจอ
4. ดึงค่า: xdate, bookCloseDate, dividend
5. จัดรูปแบบวันที่ด้วย format_date() → DD-MM-YYYY
ค่าที่ Return:
{
"symbol": "DIF",
"xdate": "15-03-2024",
"book_close_date": "16-03-2024",
"dividend": "0.25"
}
5.6 services/set_price_service.py
ใช้ requests + Regex สำหรับ Scrape ราคา Bid (ไม่ใช้ Playwright):
| ฟังก์ชัน | คำอธิบาย |
|---|---|
get_last_bid_price(symbol) |
Scrape ราคา Bid จากหน้า SET Quote |
Pattern การค้นหา:
1. Primary: bids:[{volume:...,price:"..."}] → จับคู่ราคา Bid
2. Secondary: price:"..." → Fallback ถ้า Primary ไม่เจอ
หมายเหตุ: Service นี้มี Session และ Headers ของตัวเอง แยกจาก service อื่น
6. Tasks
Tasks เป็นหน่วยการทำงานย่อยที่ถูกเรียกใช้โดย Flows:
6.1 Task Table
| Task Name | ไฟล์ | หน้าที่ | ใช้ใน Flow |
|---|---|---|---|
init_session |
session_tasks.py |
เริ่มต้น Session HTTP | Stock Profile |
load_excel |
excel_tasks.py |
โหลด Excel Input (คัดลอกไป temp) | Stock Profile |
save_row |
excel_tasks.py |
บันทึกข้อมูล Profile (FundName, Company, Trustee) ลง Excel | Stock Profile |
scrape_stock |
scrape_tasks.py |
เรียก SET API Scrape Stock Profile | Stock Profile |
scrape_last_bid |
stock_price_tasks.py |
ใช้ Playwright Scrape ราคา Bid แล้วใช้ Regex สกัดเฉพาะตัวเลข (เช่น "4.40" จาก "4.40 / 300") | Email Sheet |
load_group_symbols |
email_excel_tasks.py |
โหลด Symbol แยกตาม Group | Email Sheet |
create_email_sheet |
email_excel_tasks.py |
สร้าง Sheet "Email" พร้อมหัวตารางและ Style | Email Sheet |
write_email_sheet |
email_excel_tasks.py |
เขียนข้อมูลราคา Bid ลง Excel | Email Sheet |
load_group_symbols |
dividend_excel_tasks.py |
โหลด Symbol สำหรับ Dividend (แยกตาม Group) | Dividend Paid |
create_dividend_sheet |
dividend_excel_tasks.py |
สร้าง Sheet "Dividend Paid" พร้อม Section | Dividend Paid |
write_dividend_sheet |
dividend_excel_tasks.py |
เขียนข้อมูล Dividend + Profile ลง Excel | Dividend Paid |
scrape_latest_xd |
dividend_tasks.py |
Scrape ข้อมูล XD จาก SET API | Dividend Paid |
load_input_symbols |
accumulated_excel_tasks.py |
โหลด Symbol (ข้ามกลุ่มที่มีคำว่า CANCEL) | Accumulated Report |
initialize_sheets |
accumulated_writer_tasks.py |
ตั้งค่า Sheets (XD/XN) พร้อม Header และ Style | Accumulated Report |
write_corporate_actions |
accumulated_writer_tasks.py |
เขียนข้อมูล Corporate Actions (XD/XN) ลง Excel | Accumulated Report |
7. Data Flow Diagram
mockupInput.xlsx
│
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ STOCK PROFILE │ │ EMAIL SHEET │ │ DIVIDEND │
│ FLOW │ │ FLOW │ │ PAID FLOW │
│ │ │ │ │ │
│ SET API │ │ Playwright │ │ SET API │
│ /profile │ │ /quote/{sym} │ │ /corp-action │
│ /fund-profile │ │ │ │ │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
│ ▼ │
│ ┌───────────────────┐ │
│ │ Result_Email_ │ │
└────────────► LastBid_*.xlsx ◄───────┘
│ │
│ Sheet "Input" │
│ (C, D, E) │
│ Sheet "Email" │
│ Sheet "Div Paid" │
└────────┬──────────┘
│
│
┌────────┴──────────┐
│ ACCUMULATED │
│ REPORT FLOW │
│ │
│ SET API │
│ /corp-action │
└────────┬──────────┘
│
▼
┌───────────────────┐
│ Result_Accumulated│
│ _{group}_{sym} │
│ _*.xlsx │
│ (XD / XN Sheets) │
└───────────────────┘
8. Configuration Details
8.1 config/settings.py
ค่าคงที่พื้นฐานและไฟล์ Output ร่วมของโปรเจกต์:
INPUT_FILE = GLOBAL_INPUT_FILE
# ไฟล์ Output กลาง — Flow 1, 2, 3 ใช้ร่วมกัน
_timestamp = datetime.now().strftime("%d%m%Y_%H%M%S")
OUTPUT_FILE = OUTPUT_ROOT / f"Result_Email_LastBid_{_timestamp}.xlsx"
SHEET_NAME = "Input"
API_URL = "https://www.set.or.th/api/set/company/{symbol}/profile?lang=th"
RETRY_COUNT = 3
SESSION_REFRESH_EVERY = 30 # Refresh ทุก 30 รายการ
หมายเหตุ: OUTPUT_FILE ชี้ไปที่ dataOutput/Result_Email_LastBid_{timestamp}.xlsx โดยตรง (เปลี่ยนจากเดิมที่ใช้ temp/fillMockupInput.xlsx)
8.2 config/dividend_sheet_config.py
GROUP_CONFIG = {
"LHB": {"start_col": 2, "color": "D9D9D9"}, # เทา
"LHFG": {"start_col": 7, "color": "FFF2CC"}, # เหลือง
"LH FUND":{"start_col": 12, "color": "E2F0D9"}, # เขียว
"LHS": {"start_col": 17, "color": "DDEBF7"}, # ฟ้า
}
ALL_SECTION = {"start_col": 22} # ชมพู
8.3 config/email_sheet_config.py
GROUP_CONFIG = {
"LHB": {"symbol_col": 2, "bid_col": 3, "color": "D9D9D9"},
"LHFG": {"symbol_col": 5, "bid_col": 6, "color": "FFF2CC"},
"LH FUND":{"symbol_col": 8, "bid_col": 9, "color": "E2F0D9"},
"LHS": {"symbol_col": 11, "bid_col": 12, "color": "DDEBF7"},
}
8.4 config/accumulated_config.py
INPUT_FILE = INPUT_FILE # จาก global_config
OUTPUT_DIR = ACCUMULATED_OUTPUT_DIR # dataOutput/symbolReport
9. Dependencies (requirements.txt)
| Library | เวอร์ชันขั้นต่ำ | 用途 |
|---|---|---|
requests |
>=2.32.0 | HTTP Requests ไปยัง SET API |
beautifulsoup4 |
>=4.12.3 | HTML Parsing (สำรอง) |
lxml |
>=5.2.2 | XML/HTML Parser |
openpyxl |
>=3.1.5 | อ่าน/เขียน Excel (.xlsx) |
pandas |
>=2.2.2 | Data Processing |
playwright |
>=1.52.0 | Browser Automation สำหรับ Scrape ราคา Bid |
python-dateutil |
>=2.9.0 | จัดการวันที่ |
html5lib |
>=1.1 | Fast HTML Parsing (optional) |
tenacity |
>=8.2.3 | Retry Logic (optional) |
python-dotenv |
>=1.0.1 | Environment Config (optional) |
pydantic |
>=2.11.4 | Data Validation (optional) |
rich |
>=14.0.0 | CLI Output (optional) |
httpx |
>=0.28.1 | Async HTTP (optional) |
loguru |
>=0.7.2 | Logging (optional) |
10. วิธีการรัน (How to Run)
10.1 การติดตั้ง
# 1. ติดตั้ง Dependencies
cd D:\Projects\LHB-StockPrice
pip install -r requirements.txt
# 2. ติดตั้ง Browser สำหรับ Playwright
playwright install chromium
10.2 การรันโปรแกรม
python main.py
โปรแกรมจะรัน 4 Flows ตามลำดับ: 1. ✅ STOCK_PROFILE_FLOW 2. ✅ EMAIL_SHEET_FLOW 3. ✅ DIVIDEND_PAID_FLOW 4. ✅ ACCUMULATED_REPORT_FLOW
เมื่อรันเสร็จทั้งหมดจะแสดง [ALL FLOWS COMPLETED] และรอให้กด ENTER เพื่อปิด
10.3 การเตรียมข้อมูล Input
ก่อนรันต้องมีไฟล์ dataInput/mockupInput.xlsx ที่มีโครงสร้าง:
| A (group_name) | B (symbol) |
|---|---|
| LHB | DIF |
| LHB | FUTURERT |
| LHFG | LHRREIT |
| LH FUND | AIMCG |
| LHS | SSP |
11. Output Files
| Flow | Output File | รายละเอียด |
|---|---|---|
| Stock Profile | dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet "Input") |
ข้อมูล FundName, CompanyFullname, Trustee ใน Column C, D, E |
| Email Sheet | dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet "Email") |
ราคา Bid ล่าสุดแยกตาม Group |
| Dividend Paid | dataOutput/Result_Email_LastBid_{timestamp}.xlsx (Sheet "Dividend Paid") |
ข้อมูล XD แยกตาม Group + All Section พร้อม Profile |
| Accumulated Report | dataOutput/symbolReport/Result_Accumulated_{group}_{symbol}_{timestamp}.xlsx |
ไฟล์แยกต่อ Symbol มี 2 Sheets (XD, XN) |
12. หมายเหตุสำคัญ (Important Notes)
12.1 Session Management
- โปรเจกต์ใช้
requests.Session()เพื่อรักษา Cookies กับเซิร์ฟเวอร์ SET - Session จะถูก Initialize ก่อน โดยการเรียกหน้า SET 4 หน้าเพื่อให้ได้ Cookies ครบ
- ทุก 30 รายการ (Flow ที่ 1) จะทำ Refresh Session (ล้าง Cookies และ Initialize ใหม่)
- เมื่อเจอ HTTP 401/403/429 จะ Refresh Session อัตโนมัติ
12.2 Human-like Delay
- ทุกครั้งที่มีการเรียก API หรือ Scrape จะมีการ หน่วงเวลาแบบสุ่ม (Random Sleep)
- ช่วงเวลาปกติ: 2-5 วินาที (บางครั้ง 1-3, 3-7)
- เพื่อเลียนแบบพฤติกรรมมนุษย์และป้องกันการถูก Block
12.3 Playwright
- ใช้เฉพาะใน Flow ที่ 2 (Email Sheet) สำหรับ Scrape ราคา Bid
- โหมด Normal (
headless: false) จะเห็น Browser เปิด-ปิด - สามารถเปลี่ยนเป็น
headless: trueใน config.json เพื่อรันแบบไม่แสดงหน้าจอ
12.4 Error Handling
- แต่ละ Flow มี Try-Except จัดการ Error
- กรณีหาข้อมูลไม่พบ → จะบันทึกค่า
"-"แทน - Flow จะไม่หยุดทำงานแม้บาง Symbol จะ Error
12.5 Excel Styling
- มีการจัดรูปแบบ Excel อย่างละเอียด:
- สีพื้นแยกตาม Group
- ขอบตาราง (Border)
- การจัดตำแหน่งข้อความ (Center/Left)
- ความกว้างคอลัมน์ที่เหมาะสม
- Header Bold
- ความสูงแถว
12.6 Profile Cache
- ใน Flow ที่ 3 (Dividend Paid Flow) มีการ Cache ข้อมูล Profile (
profile_cache) เพื่อป้องกันการเรียก API ซ้ำสำหรับ Symbol ที่ซ้ำกัน - ใน Flow ที่ 1 (Stock Profile Flow) จะ Refresh Session ทุก 30 รายการ
13. Models
models/stock_result.py
@dataclass
class StockResult:
FundName: str = ""
CompanyFullname: str = ""
Trustee: str = ""
เป็น Data Class สำหรับเก็บผลลัพธ์การ Scrape Stock Profile ใช้ใน:
- services/set_api_service.py — คืนค่าเป็น StockResult
- tasks/excel_tasks.py — save_row() อ่านค่าจาก Object นี้
- tasks/dividend_excel_tasks.py — ตรวจสอบว่าเป็น Object หรือ Dict
14. Utilities
utils/date_utils.py
| ฟังก์ชัน | คำอธิบาย | รูปแบบ Output |
|---|---|---|
format_date() |
แปลง ISO Date → DD-MM-YYYY | 15-03-2024 |
format_long_date() |
แปลง ISO Date → DD Month YYYY | 15 March 2024 |
format_short_date() |
แปลง ISO Date → DD Mon YYYY | 15 Mar 2024 |
get_search_datetime() |
เวลาปัจจุบัน | 15 Mar 2024 14:30:00 |
get_filename_timestamp() |
Timestamp สำหรับชื่อไฟล์ | 15032024_143000 |
utils/delay_utils.py
| ฟังก์ชัน | คำอธิบาย |
|---|---|
human_delay(min_sec, max_sec) |
หน่วงเวลาแบบสุ่มในช่วงที่กำหนด |
utils/excel_style_utils.py
apply_group_style()— จัดรูปแบบกลุ่มใน Email Sheet (Border, Fill, Font, Column Width)
utils/dividend_excel_style_utils.py
apply_dividend_style()— จัดรูปแบบกลุ่มใน Dividend Sheetapply_all_section_style()— จัดรูปแบบ All Section (สีชมพู)
utils/accumulated_excel_style_utils.py
apply_header_style()— จัดรูปแบบ Header ใน Accumulated Reportapply_data_style()— จัดรูปแบบ Data Row
utils/extract_utils.py
extract_management_company()— ค้นหาชื่อบริษัทจัดการจากข้อมูล Managementextract_trustee()— ค้นหาชื่อ Trustee จากข้อมูล Management
utils/file_utils.py
ensure_directory()— สร้าง Directory ถ้ายังไม่มีcreate_workbook()— สร้าง Workbook เปล่าพร้อม 2 Sheets (XD, XN)
15. Debug & Test Scripts
| ไฟล์ | คำอธิบาย |
|---|---|
debug/debug_api.py |
ทดสอบการเรียก SET API |
debug/debug_dividend.py |
ทดสอบการดึงข้อมูล Dividend |
debug/debug_xd_data.py |
ทดสอบการดึงข้อมูล XD |
test/readMemory.py |
ทดสอบอ่าน Memory |
test/reverseSETapi.py |
ทดสอบ Reverse Engineering SET API |
test/test.py |
ทั่วๆ ไป |
16. Recent Changes
16.1 config.json — ลบ email_output_dir
ไฟล์: config.json
- การเปลี่ยนแปลง: ลบ key "email_output_dir" ออกจาก config.json
- เหตุผล: ปรับให้ใช้ output_root ร่วมกัน ลดความซ้ำซ้อนของ config
- ผลกระทบ: Output ของ Email Sheet (Result_Email_LastBid_*.xlsx) ยังคงถูกสร้างที่ dataOutput/ เหมือนเดิม ผ่าน output_root
16.2 config/settings.py — เปลี่ยน OUTPUT_FILE ชี้ไปที่ dataOutput/
ไฟล์: config/settings.py
- เปลี่ยนแปลง: OUTPUT_FILE เปลี่ยนจาก temp/fillMockupInput.xlsx → dataOutput/Result_Email_LastBid_{timestamp}.xlsx
- เหตุผล: Flow 1, 2, 3 ใช้ไฟล์ Output ร่วมกัน (ไฟล์เดียว) เพื่อลดความซ้ำซ้อน
- ผลกระทบ: ไม่มี temp directory (fillMockupInput.xlsx) อีกต่อไป
16.3 flows/email_sheet_flow.py — ลบการเรียก write_email_sheet_to_temp()
ไฟล์: flows/email_sheet_flow.py
- ลบออก: ลบการเรียก write_email_sheet_to_temp(final_result) และ import ที่เกี่ยวข้อง
- เหตุผล: ยกเลิกการเขียนไฟล์ temp ซ้ำซ้อน (temp/fillMockupInput.xlsx) เนื่องจาก Email Sheet Flow จะบันทึกเฉพาะที่ dataOutput/Result_Email_LastBid_*.xlsx เท่านั้น
16.4 tasks/email_excel_tasks.py — ลบ Task write_email_sheet_to_temp
ไฟล์: tasks/email_excel_tasks.py
- ลบออก: ลบฟังก์ชัน write_email_sheet_to_temp(group_data) ทั้งหมด
- เหตุผล: ฟังก์ชันนี้ถูกยกเลิกการใช้งานแล้ว เนื่องจากไม่ต้องการเขียนไฟล์ temp เพิ่มเติม
16.5 requirements.txt — เพิ่ม html5lib
ไฟล์: requirements.txt
- เพิ่มใหม่: html5lib>=1.1 (Optional - Fast HTML Parsing)
- คำอธิบาย: เพิ่ม library สำหรับการ parse HTML เป็นทางเลือกเสริม
16.6 tasks/stock_price_tasks.py — สกัดเฉพาะตัวเลขราคา Bid ด้วย Regex
ไฟล์: tasks/stock_price_tasks.py
- ปัญหาเดิม: scrape_last_bid() คืนค่าข้อความเต็มจากหน้าเว็บ เช่น "Bid Price / Volume (Shares)4.40 / 300" ทำให้ใน Excel แสดงข้อความยาว
- สิ่งที่ต้องการ: ต้องการเฉพาะตัวเลขราคา Bid เช่น "4.40" เท่านั้น
- วิธีแก้ไข: เพิ่ม import re และใช้ Regex r'(\d+\.?\d*)\s*/' สกัดเฉพาะตัวเลขราคา Bid ก่อน return
- Input: "Bid Price / Volume (Shares)4.40 / 300" → Output: "4.40"
- Input: "Bid Price / Volume (Shares)\n4.40 / 300" → Output: "4.40"
- กรณี Regex ไม่ match หรือ Error: คืนค่า "-" เหมือนเดิม
16.7 config/global_config.py — ลบ EMAIL_OUTPUT_DIR
ไฟล์: config/global_config.py
- ลบออก: ลบ constant EMAIL_OUTPUT_DIR ที่อ่านจาก CONFIG["email_output_dir"]
- เหตุผล: เปลี่ยนให้ email_sheet_config.py ใช้ OUTPUT_ROOT แทน
16.8 config/email_sheet_config.py — เปลี่ยนใช้ OUTPUT_ROOT
ไฟล์: config/email_sheet_config.py
- เปลี่ยนแปลง: เปลี่ยน import จาก EMAIL_OUTPUT_DIR → OUTPUT_ROOT
- ผลกระทบ: OUTPUT_DIR = OUTPUT_ROOT ซึ่งมีค่าเท่ากับ D:\\Projects\\LHB-StockPrice\\dataOutput เหมือนเดิม
16.9 requirements.txt — ลบ prefect dependency
ไฟล์: requirements.txt
- ลบออก: ลบบรรทัด prefect>=3.0.0 และ comment ที่เกี่ยวข้อง
- เหตุผล: ปรับปรุงโค้ดเพื่อไม่ต้องพึ่งพา Prefect อีกต่อไป
16.10 ลบ prefect imports, @flow, @task decorators
ไฟล์: flows/*.py, tasks/*.py
- ลบออก: ลบ from prefect import flow, from prefect import task, @flow(...), @task ทั้งหมด
- เหตุผล: ไม่จำเป็นต้องใช้ Prefect สำหรับ Workflow Orchestration แล้ว โปรแกรมรันด้วยฟังก์ชัน Python ปกติ