Skip to content

📊 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.pysave_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 Sheet
  • apply_all_section_style() — จัดรูปแบบ All Section (สีชมพู)

utils/accumulated_excel_style_utils.py

  • apply_header_style() — จัดรูปแบบ Header ใน Accumulated Report
  • apply_data_style() — จัดรูปแบบ Data Row

utils/extract_utils.py

  • extract_management_company() — ค้นหาชื่อบริษัทจัดการจากข้อมูล Management
  • extract_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.xlsxdataOutput/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_DIROUTPUT_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 ปกติ