iT邦幫忙

2025 iThome 鐵人賽

DAY 14
0
Modern Web

每天一點 API:打造我的生活小工具系列 第 14

Day14 — API 資料清理:打理資料的第一步

  • 分享至 

  • xImage
  •  

API 回來的資料不一定完整,可能會有缺失值、重複值,甚至格式不一致。

因此今天我要來學習基礎的資料清理方法,並完成一個小實作,把 API 的資料處理乾淨後再存檔。

為什麼要清理資料?

1. 缺失值問題

有時候 API 回傳的資料不完整,可能漏掉欄位,或只給空字串、null。
如果是關鍵欄位沒有資料,那這筆資料可能沒辦法用。

2. 重複值問題

API 來的資料可能會重複,特別是多個來源合併的時候。
如果不去除重複,計算結果或查詢會不準確。

3. 資料一致性問題

比如 Email 地址大小寫不統一,文字欄位多了空白,這會影響後續分析。
清理後,資料格式比較一致,也比較好用。

常見的資料清理方法

1. 缺失值處理(Missing Values)

有些資料欄位沒填資料,叫做缺失值。

  • 刪除法:如果關鍵欄位沒資料,可以把這整筆資料刪掉。

  • 填補法:如果是非必要欄位沒資料,可以用空字串、None(空值),或是預設的數值替代。

2. 重複值處理(Duplicates)

同一筆資料有可能出現多次,特別是從不同來源整合時。

  • 可以用唯一鍵欄位(像 ID 或 Email)判斷,刪除重複,只留第一筆。

3. 資料標準化(Normalization)

把資料格式統一,讓分析時不會出錯。

常見的作法有:

  • .strip() 方法去除文字前後多餘空白。

  • 把 Email 都轉成小寫,避免一樣的 Email 被當作不同。

  • 統一欄位名稱,避免有時是「Email」,有時是「e-mail」。

小實作:清理從 API 取得的資料

步驟 1:抓取 API 資料

  • 用 JSONPlaceholder 這個免費的測試 API 抓「users」資料。

  • 把原始資料存成 json 檔(users_raw.json),這樣可以保留未處理過的原始檔。

import os, json, csv
import requests

API = "https://jsonplaceholder.typicode.com/users"
OUT_DIR = "output"

#  工具函式 
def fetch_users(timeout=10):
    r = requests.get(API, timeout=timeout)
    r.raise_for_status()
    return r.json()  # list[dict]

def save_json(data, path):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)

步驟 2:扁平化巢狀資料

  • API 回傳的資料有巢狀結構,例如位址裡有 city,公司有 name。

  • address.city 變成單一欄位 city,company.name 變成單一欄位 company,方便後續處理。

def flatten_user(u: dict) -> dict:
    """把巢狀欄位攤平:address.city -> city, company.name -> company"""
    def pick(d, path, default=None):
        cur = d
        for k in path.split("."):
            if isinstance(cur, dict) and k in cur:
                cur = cur[k]
            else:
                return default
        return cur
    return {
        "id": u.get("id"),
        "name": u.get("name"),
        "username": u.get("username"),
        "email": u.get("email"),
        "city": pick(u, "address.city"),
        "zipcode": pick(u, "address.zipcode"),
        "phone": u.get("phone"),
        "website": u.get("website"),
        "company": pick(u, "company.name"),
    }

步驟 3:資料清理

  • 去除缺失值:關鍵欄位(id、name、email)不能缺,缺了要刪除整筆資料。

  • 標準化:去除欄位多餘的空白字元,email 統一轉成小寫。

  • 去除重複值:用 email 當「唯一鍵」,移除重複的資料(只保留第一筆)。

def is_empty(v) -> bool:
    if v is None:
        return True
    if isinstance(v, str) and v.strip() == "":
        return True
    return False

def normalize_record(r: dict) -> dict:
    """字串去空白;email 小寫"""
    out = {}
    for k, v in r.items():
        if isinstance(v, str):
            v = v.strip()
        out[k] = v
    if out.get("email"):
        out["email"] = out["email"].lower()
    return out

def drop_missing_required(rows, required=("id", "name", "email")):
    cleaned = []
    for r in rows:
        if all(not is_empty(r.get(k)) for k in required):
            cleaned.append(r)
    return cleaned

def deduplicate(rows, key="email"):
    """以 key 去重,保留第一筆"""
    seen = set()
    unique = []
    for r in rows:
        k = r.get(key)
        if k not in seen:
            seen.add(k)
            unique.append(r)
    return unique

步驟 4:匯出

  • 把清理後的資料存成users_clean.csv,之後可以給 Excel 或其他分析工具用。

  • 也存成users_clean.json,方便程式繼續使用。

def to_csv(rows, path, fieldnames):
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        w = csv.DictWriter(f, fieldnames=fieldnames)
        w.writeheader()
        w.writerows(rows)

# 主流程 
def main():
    os.makedirs(OUT_DIR, exist_ok=True)

    # 抓資料
    try:
        users = fetch_users()
    except requests.exceptions.RequestException as e:
        print("API 錯誤:", e)
        return

    # 存原始資料(未清理)
    raw_path = os.path.join(OUT_DIR, "users_raw.json")
    save_json(users, raw_path)
    print(f"已存原始檔:{raw_path}({len(users)} 筆)")

    # 模擬一點「髒資料」(為了示範清理,實務上這步不需要)
    dirty = list(users)
    if users:
        import copy
        dup = copy.deepcopy(users[0])
        dup["name"] = "  " + dup["name"] + "  "  # 多餘空白
        dup["email"] = dup["email"].upper()      # 大寫,會被標準化成小寫
        dirty.append(dup)                         # 製造重複
        miss = copy.deepcopy(users[1])
        miss["id"] = 9999
        miss["email"] = "   "                    # 空白視為缺失
        dirty.append(miss)
    # 扁平化 → 標準化
    flat = [flatten_user(u) for u in dirty]
    normalized = [normalize_record(r) for r in flat]
    # 去關鍵欄位缺失(id、name、email)
    no_missing = drop_missing_required(normalized, required=("id","name","email"))
    # 以 email 去重(也可改用 id)
    unique = deduplicate(no_missing, key="email")

    # 匯出清理後結果
    fields = ["id","name","username","email","city","zipcode","phone","website","company"]
    clean_csv = os.path.join(OUT_DIR, "users_clean.csv")
    clean_json = os.path.join(OUT_DIR, "users_clean.json")
    to_csv(unique, clean_csv, fields)
    save_json(unique, clean_json)

    print("—— 結果統計 ——")
    print(f"原始筆數:{len(users)}")
    print(f"加入髒資料後:{len(dirty)}")
    print(f"扁平化後:{len(flat)}")
    print(f"清掉缺失(id/name/email)後:{len(no_missing)}")
    print(f"去重(email)後:{len(unique)}")
    print(f"已輸出:{clean_csv} 與 {clean_json}")

if __name__ == "__main__":
    main()

執行結果:

已存原始檔:output\users_raw.json(10 筆)
—— 結果統計 ——
原始筆數:10
加入髒資料後:12
扁平化後:12
清掉缺失(id/name/email)後:11
去重(email)後:10
已輸出:output\users_clean.csv 與 output\users_clean.json

今日總結

  • 學會了基礎資料清理:缺失值處理、重複值處理、資料標準化。

  • 理解到API 資料不一定完整,要先做整理才好用。

  • 完成了小實作:先抓 API,接著存 raw,然後清理,最後匯出乾淨版本。


上一篇
Day13 — API 資料怎麼存?CSV、JSON、SQLite 練習
下一篇
Day15 — 從巢狀到平面:新聞 API 資料整理練習
系列文
每天一點 API:打造我的生活小工具17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言