Skip to content

Week 3 Python Example

在 PyCharm 中建立 generate_logs.py,產生模擬資料:

# generate_logs.py
import random
import time

METHODS = ["GET", "POST", "PUT", "DELETE"]
ENDPOINTS = ["/api/v1/predict", "/api/v1/health", "/api/v1/train", "/login"]
STATUS_CODES = [200, 200, 200, 200, 404, 500, 503]

def create_dummy_log_file(filename: str = "server.log", lines: int = 100_000):
    with open(filename, "w", encoding="utf-8") as f:
        for _ in range(lines):
            ip = f"192.168.1.{random.randint(1, 255)}"
            method = random.choice(METHODS)
            endpoint = random.choice(ENDPOINTS)
            status = random.choice(STATUS_CODES)
            latency = round(random.uniform(5.0, 1500.0), 2)  # 毫秒
            f.write(f"{ip} {method} {endpoint} {status} {latency}\n")

if __name__ == "__main__":
    print("產生模擬 Log 檔案中...")
    create_dummy_log_file()
    print("產生完成!")

以上程式執行後會產生 server.log 的模擬 log 檔案, 我們可以將此 dataset 拿來做資料分析, 內容列舉如下

192.168.1.171 POST /api/v1/train 500 616.19
192.168.1.129 POST /api/v1/train 503 1004.2
192.168.1.87 GET /api/v1/train 200 75.79
192.168.1.141 DELETE /api/v1/train 200 28.09

「各位以前寫程式,讀檔案通常就是 readAll() 或直接開一個超大陣列把全部資料塞進去對吧?在小作業裡完全沒問題。但今天我們這門課叫『巨量資料分析』,你未來在業界要處理的是 100 GB、甚至是 1 TB 的伺服器存取記錄。你的筆電記憶體頂多 16 GB 或 32 GB,如果你企圖一次把檔案全讀進來,作業系統只會毫不客氣地送你三個英文字母:OOM (Out Of Memory),程式直接當掉。

今天我們的目標,就是要用 Python 寫出一個**『記憶體消耗永遠只有幾 KB』**的 Log 分析器,幫往後課程的模型準備乾淨的特徵。」

今日任務
任務 1: 使用 Generator 逐行讀取,確保記憶體安全

請寫一個函數 stream_raw_logs(filepath)。
功能:傳入檔案路徑,它不要回傳一個裝滿百萬行文字的 list。
要求:使用 with open 開啟檔案,透過迴圈讀取時,不要用 return,改用 yield 把每一行文字「吐」給外面。
驗證標準:呼叫這個函數時,它應該是一個「生成器物件(generator object)」,不會一次把檔案讀完。


任務 2: 資料解析與型別轉換 (EAFP 容錯機制)

請寫一個函數 parse_log_line(line)。
輸入:單行字串,例如 "192.168.1.1 GET /api/v1/predict 200 45.2"。
輸出:解析後的結構化資料(字典 dict),包含 ip、method、endpoint、status(需為整數)、latency(需為浮點數)。
核心挑戰(容錯處理):
  如果這一行格式不對(欄位缺少、多餘,或者型別轉換失敗噴出 ValueError),不要讓程式崩潰中斷。
  請用 try...except 機制捕捉異常,如果解析失敗,就回傳 None,代表這是壞掉的髒資料。


任務 3: 資料過濾與特徵匯總 (銜接 AI 特徵萃取)

請寫一個主控流程函數 process_logs(filepath)。
要求:
1. 接上任務 1 的生成器,開始讀入每一行。
2. 用任務 2 的解析函數把每一行轉成字典,遇到 None 就直接跳過(continue)。
3. 在走訪的過程中,動態更新以下統計指標:
  總有效請求數(Total Requests)
  平均延遲時間(Average Latency,單位毫秒)
  伺服器錯誤率(Error Rate,HTTP 狀態碼 >= 400 所佔的百分比)
  各 API 路徑(Endpoint)各自被呼叫了幾次
4. 最後回傳一個整理好的字典(Feature Summary),這就是未來要送進 AI 異常偵測模型的特徵向量。

DEMO code, 為含有完整的 code

# log_parser.py
from typing import Generator, Dict, Any, Optional


# 任務 1: 使用 Generator 逐行讀取,確保記憶體安全
def stream_raw_logs(filepath: str) -> Generator[str, None, None]:
    """
    TODO: 使用 with open 開啟檔案,並利用 yield 逐行產出文字
    """

# 任務 2: 資料解析與型別轉換 (EAFP 容錯機制)
def parse_log_line(line: str) -> Optional[Dict[str, Any]]:
    """
    將單行 log 字串分割並轉為結構化字典。
    格式: ip method endpoint status latency
    若資料損壞或格式不符,利用 try-except 回傳 None
    """


# 任務 3: 資料過濾與特徵匯總 (銜接 AI 特徵萃取)
def process_logs(filepath: str) -> Dict[str, Any]:
    raw_stream = stream_raw_logs(filepath)

    # 利用推導式/生成器表達式清洗資料
    parsed_stream = (parse_log_line(line) for line in raw_stream)

    total_valid_requests = 0
    total_latency = 0.0
    error_status_count = 0
    endpoint_counts: Dict[str, int] = {}

    for record in parsed_stream:
        if record is None:
            continue

        total_valid_requests += 1
        total_latency += record["latency"]

        if record["status"] >= 400:
            error_status_count += 1

        ep = record["endpoint"]
        endpoint_counts[ep] = endpoint_counts.get(ep, 0) + 1

    avg_latency = total_latency / total_valid_requests if total_valid_requests > 0 else 0.0

    # 輸出可直接餵給後續 AI 模型(如孤立森林異常偵測)的彙整特徵
    return {
        "total_requests": total_valid_requests,
        "avg_latency": round(avg_latency, 2),
        "error_rate": round(error_status_count / total_valid_requests, 4) if total_valid_requests > 0 else 0.0,
        "endpoint_distribution": endpoint_counts
    }


if __name__ == "__main__":
    result = process_logs("server.log")
    print("=== 資料特徵彙整結果 ===")
    for k, v in result.items():
        print(f"{k}: {v}")

正確執行後可以看到如下結果

=== 資料特徵彙整結果 ===
total_requests: 100000
avg_latency: 753.35
error_rate: 0.4286
endpoint_distribution: {'/login': 24886, '/api/v1/health': 25116, '/api/v1/train': 25125, '/api/v1/predict': 24873}

解答完整的 code

# log_parser.py
from typing import Generator, Dict, Any, Optional


# 任務 1: 使用 Generator 逐行讀取,確保記憶體安全
def stream_raw_logs(filepath: str) -> Generator[str, None, None]:
    """
    TODO: 使用 with open 開啟檔案,並利用 yield 逐行產出文字
    """
    with open(filepath, "r", encoding="utf-8") as file:
        for line in file:
            yield line.strip()


# 任務 2: 資料解析與型別轉換 (EAFP 容錯機制)
def parse_log_line(line: str) -> Optional[Dict[str, Any]]:
    """
    將單行 log 字串分割並轉為結構化字典。
    格式: ip method endpoint status latency
    若資料損壞或格式不符,利用 try-except 回傳 None
    """
    try:
        parts = line.split()
        if len(parts) != 5:
            return None
        return {
            "ip": parts[0],
            "method": parts[1],
            "endpoint": parts[2],
            "status": int(parts[3]),
            "latency": float(parts[4])
        }
    except (ValueError, IndexError):
        return None


# 任務 3: 資料過濾與特徵匯總 (銜接 AI 特徵萃取)
def process_logs(filepath: str) -> Dict[str, Any]:
    raw_stream = stream_raw_logs(filepath)

    # 利用推導式/生成器表達式清洗資料
    parsed_stream = (parse_log_line(line) for line in raw_stream)

    total_valid_requests = 0
    total_latency = 0.0
    error_status_count = 0
    endpoint_counts: Dict[str, int] = {}

    for record in parsed_stream:
        if record is None:
            continue

        total_valid_requests += 1
        total_latency += record["latency"]

        if record["status"] >= 400:
            error_status_count += 1

        ep = record["endpoint"]
        endpoint_counts[ep] = endpoint_counts.get(ep, 0) + 1

    avg_latency = total_latency / total_valid_requests if total_valid_requests > 0 else 0.0

    # 輸出可直接餵給後續 AI 模型(如孤立森林異常偵測)的彙整特徵
    return {
        "total_requests": total_valid_requests,
        "avg_latency": round(avg_latency, 2),
        "error_rate": round(error_status_count / total_valid_requests, 4) if total_valid_requests > 0 else 0.0,
        "endpoint_distribution": endpoint_counts
    }


if __name__ == "__main__":
    result = process_logs("server.log")
    print("=== 資料特徵彙整結果 ===")
    for k, v in result.items():
        print(f"{k}: {v}")

Leave a comment

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *