วิธีประมวลผลไฟล์ JSON ขนาดใหญ่ใน Python ด้วย ijson

How to Process Large JSON Files in Python With ijson

JSON เป็นรูปแบบยอดนิยมสำหรับการจัดเก็บและแลกเปลี่ยนข้อมูลใน Python สำหรับไฟล์ขนาดเล็ก module json ที่มาพร้อมกับ Python นั้นทำงานได้ดีเยี่ยม แต่ปัญหาจะเริ่มเกิดขึ้นเมื่อไฟล์มีขนาดใหญ่ขึ้น

ฟังก์ชัน json.load() จะประมวลผล (parse) เอกสาร JSON ทั้งหมดและสร้าง Python object ขึ้นในหน่วยความจำก่อนเริ่มใช้งาน ไฟล์ที่มีขนาดเพียงไม่กี่ร้อยเมกะไบต์บนดิสก์จึงอาจใช้หน่วยความจำพุ่งสูงขึ้นมากเมื่อถูกแปลงเป็น dictionaries, lists, strings และ numbers

ทางเลือกที่มีประสิทธิภาพกว่าคือ ijson ซึ่งเป็น iterative JSON parser ที่ช่วยให้คุณประมวลผลข้อมูลแบบทีละส่วน (incrementally) ในบทความนี้เราจะใช้ไฟล์ JSON ข้อมูลแปลงที่ดินใน San Francisco เพื่อเปรียบเทียบการทำงานระหว่าง json.load() และ ijson ในแง่การใช้ทรัพยากรระบบ

Step 1: Downloading a Large JSON Dataset

เราจะใช้ชุดข้อมูล San Francisco City Lots ในการทดลอง โดยดาวน์โหลดผ่าน Google Colab ด้วยคำสั่งดังนี้:

curl -L \
https://raw.githubusercontent.com/zemirco/sf-city-lots-json/master/citylots.json \
-o citylots.json

คุณสามารถตรวจสอบขนาดไฟล์ด้วยคำสั่ง:

ls -lh citylots.json

ไฟล์นี้มีขนาด 181 MB ซึ่งใหญ่พอจะเห็นความแตกต่างระหว่างการโหลดปกติกับการทำ streaming โดยโครงสร้างเป็นไปตามมาตรฐาน GeoJSON ซึ่งประกอบด้วย array ชื่อ features ที่เก็บข้อมูลพิกัดและคุณสมบัติของที่ดินแต่ละแปลง

Step 2: Processing the File With json.load()

มาเริ่มด้วยวิธีที่นักพัฒนาส่วนใหญ่คุ้นเคยผ่านไฟล์ standard_json.py เพื่อกรองหาที่ดินบนถนน "MARKET":

import json
import time
 
start = time.perf_counter()
 
with open("citylots.json", "r") as f:
    data = json.load(f)
 
features = data["features"]
 
market_count = 0
first_five = []
 
for feature in features:
    properties = feature["properties"]
    if properties.get("STREET") == "MARKET":
        market_count += 1
        if len(first_five) < 5:
            first_five.append(properties.get("BLKLOT"))
 
elapsed = time.perf_counter() - start
 
print(f"Total parcels: {len(features):,}")
print(f"Market Street parcels: {market_count:,}")
print(f"First five parcel IDs: {first_five}")
print(f"Time: {elapsed:.2f} seconds")

แม้โค้ดจะถูกต้อง แต่ปัญหาอยู่ที่ json.load(f) เพราะ Python ต้องอ่านและ Parse ข้อมูลทั้งหมดรวมถึงพิกัดเรขาคณิต (coordinates) ที่เราไม่ได้ใช้เข้ามาเก็บไว้ในหน่วยความจำก่อนที่จะเริ่ม loop วิเคราะห์ข้อมูลได้

Step 3: Measuring the Memory Usage

เราจะวัดผลการใช้หน่วยความจำจริงด้วย memory-profiler:

pip install memory-profiler
python -m memory_profiler standard_json.py

ผลลัพธ์แสดงให้เห็นว่าในบรรทัดที่ใช้ json.load(f) หน่วยความจำพุ่งสูงขึ้นอย่างรวดเร็ว:

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
     9    808.9 MiB    765.5 MiB           1           data = json.load(f)

สาเหตุที่หน่วยความจำสูงกว่าขนาดไฟล์บนดิสก์หลายเท่า เพราะ JSON object ขนาดเล็กเมื่อถูกแปลงเป็น Python objects จะต้องใช้โครงสร้างข้อมูลที่ซับซ้อนกว่าเดิม ทั้งในส่วนของ string keys, values และ nested arrays

Step 4: Streaming the JSON File With ijson เพื่อแก้ปัญหาข้างต้น เราจะใช้ ijson เพื่อดึงข้อมูลทีละ record โดยไม่ต้องโหลดไฟล์ทั้งหมด:

pip install ijson

ฟังก์ชันหลักคือ ijson.items() ซึ่งเราสามารถกำหนดเส้นทาง (prefix) เข้าไปในโครงสร้าง JSON ได้ เช่น "features.item" เพื่อให้ parser ทยอยส่ง feature แต่ละตัวมาให้เราประมวลผลแล้วทิ้งข้อมูลเก่าไปทันที ช่วยลดภาระหน่วยความจำได้อย่างมหาศาล

Step 5: Performing the Same Analysis With ijson

ลองปรับปรุงโค้ดใน stream_json.py โดยใช้โมเดลการประมวลผลแบบ stream:

import ijson
import time
 
start = time.perf_counter()
total = 0
market_count = 0
first_five = []
 
with open("citylots.json", "rb") as f:
    for feature in ijson.items(f, "features.item"):
        total += 1
        properties = feature["properties"]
        if properties.get("STREET") == "MARKET":
            market_count += 1
            if len(first_five) < 5:
                first_five.append(properties.get("BLKLOT"))
 
elapsed = time.perf_counter() - start

เมื่อวัดผลด้วย memory-profiler อีกครั้ง พบว่าจุดสูงสุดของการใช้หน่วยความจำ (peak memory usage) ลดลงเหลือเพียง 46.1 MiB เท่านั้น เทียบกับวิธีเดิมที่ใช้ถึง 808.9 MiB

แนวทางไฟล์ Inputจุดสูงสุดของหน่วยความจำโมเดลการประมวลผล
json.load()181 MB808.9 MiBโหลดทุกอย่าง
ijson.items()181 MB46.1 MiBStream records

สรุปได้ว่าการประมวลผลไฟล์ JSON ขนาดใหญ่อย่างมีประสิทธิภาพไม่ได้ขึ้นอยู่กับรูปแบบไฟล์ แต่ขึ้นอยู่กับวิธีการจัดการข้อมูล หากงานของคุณต้องการข้อมูลเพียงทีละส่วน การเลือกใช้เครื่องมือที่สนับสนุนการทำ streaming อย่าง ijson จะช่วยให้ระบบทำงานได้เสถียรและประหยัดทรัพยากรมากขึ้น

Source: KDnuggets
ดูแลงานแปลและเรียบเรียงโดย SirilukP

ความคิดเห็น (0)

เข้าสู่ระบบเพื่อร่วมแสดงความเห็น

สมัครสมาชิก

มาเป็นคนแรกที่แสดงความเห็นกันเลยโบร