เกณฑ์ราคากลางคอมพิวเตอร์ ICT ภาครัฐ + จัดสเปก DIY ด้วยสคริปต์
เอกสารเกณฑ์ราคากลางเขียนสเปกขั้นต่ำไว้ครบ แต่ไม่ได้บอกว่าราคาจริงหน้าร้านเป็นเท่าไร ผมเลยเขียนสคริปต์ดึงราคา Advice มาเทียบทีละชิ้น แล้วพบว่าประกอบเองให้ผ่านทั้งสองเกณฑ์ในเครื่องเดียว ต่างจากงบข้อ 5 แค่หลักสิบบาท
สรุปใน 1 นาที
- อาการ/ปัญหา
- เกณฑ์ราคากลาง ICT ภาครัฐกำหนดสเปกไว้ แต่ไม่บอกราคาจริงหน้าร้าน
- ใครควรอ่าน
- คนเตรียมจัดซื้อคอมพิวเตอร์ภาครัฐ ที่อยากรู้ว่าประกอบเองคุ้มไหม
- ทางแก้แบบสั้น
- อ่านเกณฑ์ ธ.ค. 2568 ทีละข้อ แล้วใช้สคริปต์ Python ดึงราคา Advice มาจัดสเปก DIY เทียบกับราคาเครื่องสำเร็จรูป
พอดีมีน้องในวงการถามมาว่าเกณฑ์ราคากลางคอมพิวเตอร์ภาครัฐรอบล่าสุด (ฉบับเดือนธันวาคม 2568) มันบังคับสเปกไว้แค่ไหน แล้วถ้าประกอบเองจะคุ้มกว่าซื้อเครื่องสำเร็จตามงบหรือเปล่า ผมเลยนั่งอ่านเอกสารเกณฑ์จริงทีละข้อ แล้วเขียนสคริปต์ Python ดึงราคาจากหน้า Advice มาจัดสเปกเทียบให้เห็นเป็นตัวเลข ไม่ใช่เดาเอา งานนี้มันมีกับดักซ่อนอยู่หลายจุด ที่ถ้าอ่านเกณฑ์ผ่านๆ แล้วจะพลาด เลยอยากเล่าให้ฟังทั้งฝั่งเกณฑ์และฝั่งโค้ด
ชื่อไฟล์และ path ในบทความเป็นตัวอย่างแบบ relative ส่วนโครงสร้างสคริปต์และตัวเลขราคาคือของจริงจากรอบ crawl ล่าสุด
เกณฑ์ ICT สองข้อที่ต้องอ่านให้ขาด
ในเล่มมีสองรายการที่คนซื้อเครื่องเดสก์ท็อปต้องดู คือข้อ 4 เครื่องสำหรับงานสำนักงาน กับข้อ 5 เครื่องสำหรับงานประมวลผลแบบที่ 1 สองข้อนี้ต่างกันที่ซีพียูกับการ์ดจอเป็นหลัก ที่เหลือ (RAM DDR5 16GB, SSD 500GB, จอ 23 นิ้ว, แป้นพิมพ์+เมาส์) เหมือนกันเป๊ะ
งบ 20,000 บาท
CPU ≥4 core / 8 thread, Turbo ≥4.4 GHz, Cache ระดับเดียวกัน ≥12 MB, RAM DDR5 ≥16 GB, SSD ≥500 GB, จอ ≥23 นิ้ว -> ไม่บังคับการ์ดจอ
งบ 24,000 บาท
CPU ≥8 core / 8 thread, Turbo ≥4.6 GHz, Cache ระดับเดียวกัน ≥16 MB, บวกเงื่อนไข GPU หน่วยความจำ ≥2 GB ที่เหลือเท่าข้อ 4
จุดที่คนอ่านผ่านแล้วพลาดคือคำว่า "แกนหลักรวมกันไม่น้อยกว่า 8 แกน" ในข้อ 5 มันหมายถึง physical core จริงๆ ไม่ใช่ thread ทำให้ซีพียูฝั่งประหยัดหลายตัวที่มี 6 core 12 thread ผ่านข้อ 4 สบายแต่ตกข้อ 5 ทันที เรื่องนี้ต้องให้สคริปต์เช็คให้ ไม่ใช่กะด้วยตา
ข้อ 5 บังคับ GPU หน่วยความจำ ≥2 GB แต่ตัวเกณฑ์เปิดช่องให้ iGPU ที่ "ใช้หน่วยความจำหลักในการแสดงภาพ ≥2 GB" ผ่านได้ -> ถ้าเลือกซีพียูที่มี iGPU ในตัว ก็ ตัดการ์ดจอแยกออกได้ทั้งก้อน ประหยัดไปหลายพันบาท
ปัญหาคือเกณฑ์ไม่ได้บอกราคาจริง
เอกสารบอกแค่สเปกขั้นต่ำกับเพดานราคา แต่ไม่ได้บอกว่าของที่ผ่านสเปกจริงๆ ตอนนี้ราคาหน้าร้านเท่าไร มีของหรือเปล่า ผมเลยไปดึงราคาจาก Advice มาเป็นฐานอ้างอิง เพราะมีหน้าเว็บให้ดูสต็อกรายสาขาและราคาสมาชิกครบ
ปัญหาแรกที่เจอคือหน้า product ของ Advice เป็น Nuxt SSR ข้อมูลราคาไม่ได้อยู่ใน HTML ตรงๆ แต่ฝังอยู่ใน __NUXT_DATA__ เป็น "index graph" คือทุกค่าอ้างถึงกันด้วยเลข index ไม่ใช่ค่าจริง ต้องเขียนตัว resolve ไล่ตามลูกโซ่เอง
import json, re
from pathlib import Path
# หน้า Advice เป็น Nuxt SSR — ข้อมูลจริงฝังใน __NUXT_DATA__ เป็น index graph
NUXT_RE = re.compile(r'__NUXT_DATA__"[^>]*>(\[.*?\])</script>', re.S)
def load_graph(html_path):
text = Path(html_path).read_text(encoding="utf-8", errors="ignore")
m = NUXT_RE.search(text)
return json.loads(m.group(1)) if m else None
# ค่าทุกตัวใน graph อ้างกันด้วย index ไม่ใช่ค่าตรงๆ ต้องไล่ resolve เอง
def resolve(graph, idx, depth=0, seen=frozenset()):
if depth > 25 or idx in seen or not isinstance(idx, int) or not (0 <= idx < len(graph)):
return idx
seen = seen | {idx}
val = graph[idx]
if isinstance(val, dict):
return {k: resolve(graph, v, depth + 1, seen) for k, v in val.items()}
if isinstance(val, list):
# unwrap reactive wrapper ของ Nuxt ก่อนไล่ต่อ
if len(val) == 2 and val[0] in ("ShallowReactive", "Reactive", "Ref"):
return resolve(graph, val[1], depth + 1, seen)
return [resolve(graph, v, depth + 1, seen) for v in val]
return val
พอ resolve graph กลับมาเป็น dict ปกติแล้ว ตัวข้อมูลสินค้าจะอยู่ใต้ seed ชื่อ pdp-hero-seed: เจาะเข้าไปที่ product_get แล้วดึงราคากับสเปกออกมาได้ จุดที่สำคัญคือราคาสมาชิก (SALEPRICE6 ใน item_master) มักถูกกว่าราคาขายหน้าเว็บเยอะ ต้องเก็บทั้งคู่
def extract(graph):
tree = resolve(graph, 0)
pdp = next(v for k, v in tree["data"].items()
if str(k).startswith("pdp-hero-seed:"))
prod = find_first(pdp, lambda x: isinstance(x, dict) and "product_get" in x)
pg = prod["product_get"]
im = pg.get("item_master") or {}
specs = [strip_html(s["spec_value"]) for s in (pg.get("spec_dtls") or [])
if s.get("spec_value")]
return {
"sku": pg.get("code"),
"name": pg.get("Product") or pg.get("header"),
"group": pg.get("Grp"), # CPU / MB / RAM / ...
"price_sale": to_int(prod.get("price_sale")), # ราคาขายหน้าเว็บ
"price_member": to_int(im.get("SALEPRICE6")), # ราคาสมาชิก (ถูกกว่าเยอะ)
"spec_full": " | ".join(specs), # สเปกดิบ คั่นด้วย |
"stock_type": (pg.get("stock") or {}).get("type"), # instock / outstock
}
find_first, strip_html, to_int เป็น helper เล็กๆ ในไฟล์เดียวกัน — ไล่ dict/list หา key ที่ต้องการ, ถอด tag HTML, แปลงราคาที่มี comma ให้เป็น int แล้วรันทีเดียวจบทั้งโฟลเดอร์ cache
# แกะทุกไฟล์ HTML ใน cache ออกมาเป็น JSON ก้อนเดียว
python tools/extract_advice.py data/advice_cache/*.html > data/advice_products.json
กรองเฉพาะของที่มีจริง แล้วให้สคริปต์เช็คสเปกเอง
ทีนี้ของสำคัญคือ ราคาถูกแต่ไม่มีของ ก็ไม่มีความหมาย ผมเลยกรองเฉพาะ stock_type = instock (ออนไลน์) ก่อน แล้วค่อยเอา spec_full ของซีพียูมา parse เทียบเกณฑ์ ข้อ 4 กับ ข้อ 5 ทีละตัว สเปกดิบของ CPU มันเรียงเป็น brand | model | socket | core | thread | base | boost | L1 | L2 | L3 | iGPU
import json
rows = json.load(open("data/advice_products.json", encoding="utf-8"))
def passes(cpu, core_min, thread_min, ghz_min, l3_min):
# spec_full: brand | model | socket | core | thread | base | boost | L1 | L2 | L3 | iGPU
f = [x.strip() for x in cpu["spec_full"].split("|")]
socket = f[2]
core, thread = int(f[3]), int(f[4])
boost = float(f[6].replace("GHz", "").strip())
l3_mb = int(f[9].replace("MB", "").strip())
ddr5 = socket != "AM4" # AM4 = DDR4 -> ตกเกณฑ์ "DDR5 หรือดีกว่า"
return (core >= core_min and thread >= thread_min
and boost >= ghz_min and l3_mb >= l3_min and ddr5)
cpus = [r for r in rows if r["group"] == "CPU" and r["stock_type"] == "instock"]
for c in cpus:
ok4 = passes(c, 4, 8, 4.4, 12) # ข้อ 4 งานสำนักงาน
ok5 = passes(c, 8, 8, 4.6, 16) # ข้อ 5 งานประมวลผล
print(f'{c["name"]:<34} ข้อ4={ok4} ข้อ5={ok5} {c["price_sale"]}฿')
พอรันจริงกับข้อมูลรอบล่าสุด มันโผล่ปัญหาที่อ่านเกณฑ์เฉยๆ ไม่มีทางเห็น สามข้อ
เกณฑ์เขียนว่า RAM ต้องเป็น DDR5 หรือดีกว่า -> ซีพียู AMD AM4 อย่าง R5 5600 / 5600G แม้จำนวนคอร์ผ่านสบาย แต่แพลตฟอร์มเป็น DDR4 ตกเกณฑ์ทันที สคริปต์เลยต้องเช็ค socket ด้วย ไม่ใช่ดูแค่ core กับความเร็ว
เรื่องที่สอง ซีพียู 8 core ขึ้นไปที่ผ่านข้อ 5 ณ วันที่ crawl หมดสต็อกออนไลน์เกือบหมด (i5-14400/14500, i7-13700, R7 5700X ขึ้น outstock ทั้งแถบ ต้องสั่งเข้าสาขา) เรื่องที่สาม i3-14100 ตัวมี iGPU ก็ outstock เหลือแต่รุ่น F ที่ไม่มีการ์ดจอในตัว แปลว่าถ้าจะจัดข้อ 4 ด้วยตัวนี้ต้องบวกการ์ดจอแยกเข้าไปอีก พวกนี้ถ้าไม่ได้เก็บสถานะสต็อกมาด้วยตั้งแต่ต้น จะจัดสเปกบนกระดาษสวยแต่ซื้อจริงไม่ได้
เทียบเกณฑ์กับของจริง แล้วออกมาเป็นตาราง
ขั้นสุดท้ายคือประกอบเป็นชุด แล้วคิดราคารวมให้ครบ ราคาที่ควรเอาไปเทียบกับงบราชการไม่ใช่ราคาสินค้าเปล่าๆ ต้องบวก VAT 7% กับค่าบริการหลังการขายของผู้ประกอบการเข้าไปด้วย ผมเขียนตัวคิดเลขให้ reproduce ตัวเลขในตารางได้ตรงเป๊ะ
# ราคาชิ้นส่วน Build ประหยัดสุด (ราคา sale ที่ดึงมาได้จาก Advice)
parts = {
"CPU Core Ultra 5 225 (10C/10T 4.9GHz 20MB +iGPU)": 5410,
"MB H810M DDR5 LGA1851": 2290,
"RAM 16GB DDR5-6000": 6520,
"SSD 512GB NVMe PCIe": 2845,
"PSU 600W": 770,
"Case mATX": 820,
"Monitor 23.8\" IPS": 2420,
"KB+M": 359,
}
subtotal = sum(parts.values()) # 21,434
after_vat = round(subtotal * 1.07) # + VAT 7%
grand = round(after_vat * 1.05) # + ค่าบริการหลังการขาย 5%
print(subtotal, after_vat, grand) # 21434 22934 24081
ตัวออกรายงานสุดท้ายผมให้ matplotlib วาดเป็นตาราง PNG เทียบทุกชุดในหน้าเดียว (ต้องมีฟอนต์ไทยในเครื่องด้วย ไม่งั้นตัวหนังสือเป็นสี่เหลี่ยม) ทั้ง pipeline เดินเป็นขั้นแบบนี้
- โหลดหน้า product ของ Advice เก็บเป็น HTML ไว้ใน cache (กัน rate-limit และ crawl ซ้ำได้ไม่ต้องยิงเว็บใหม่)
- รัน extract_advice.py แกะ __NUXT_DATA__ ออกมาเป็น JSON ที่มีราคา sale/member + สเปก + สถานะสต็อก
- กรองเฉพาะ stock_type = instock แล้ว parse spec_full เทียบเกณฑ์ ข้อ 4 / ข้อ 5
- ประกอบเป็นชุด แล้วคิดราคารวม VAT + ค่าบริการหลังการขาย
- รัน render_build_table.py ออกเป็นตารางภาพเทียบทุกชุดในหน้าเดียว
# ออกเป็นตารางภาพเทียบทุกชุด -> docs/build_comparison.png
python tools/render_build_table.py
สรุปตัวเลขที่ได้
ผลที่ออกมาน่าสนใจกว่าที่คิด ชุดประหยัดสุดที่เลือกซีพียู Core Ultra 5 225 (10C/10T 4.9GHz 20MB มี iGPU) ราคาสินค้า 21,434 บาท พอบวกภาษีและค่าบริการเป็นราคารวมทั้งสิ้น 24,081 บาท เครื่องนี้ผ่านทั้งข้อ 4 และข้อ 5 ในก้อนเดียว
ข้อ 5 งบ 24,000 บาท
ได้เครื่องที่ผ่านเกณฑ์ "พอดีเส้น" -> 8C/8T 4.6GHz 16MB ตามที่เขียนในเอกสาร ไม่มีส่วนเผื่อ ผู้ขายเลือกของถูกสุดที่ยังผ่านให้
Core Ultra 5 225 รวม 24,081 บาท
ราคารวมต่างกันแค่ +81 บาท แต่ได้ 10C/10T 4.9GHz 20MB -> เกินทั้งข้อ 4 และข้อ 5 ในเครื่องเดียว ถ้ากดราคาสมาชิกยิ่งถูกลงอีก
ส่วนตัวผมสรุปเลยว่า ถ้าต้องการเครื่องที่ "ผ่านเกณฑ์ ICT" จริงๆ การจัดเองด้วยของที่มีสต็อกตอนนี้ทำได้ในราคาระดับเดียวกับซื้อสำเร็จ ต่างกันหลักสิบบาทแต่ได้ของเกินสเปกไปเยอะ ประเด็นสำคัญไม่ใช่ว่าจะประหยัดกี่บาท แต่คือการมีสคริปต์ที่ดึงราคาจริงมาเทียบเกณฑ์ให้อัตโนมัติ ทำให้เห็นกับดัก DDR5, ปัญหาสต็อก, และช่อง iGPU ที่อ่านเอกสารเปล่าๆ ไม่มีทางเห็น รอบหน้าราคาขยับเมื่อไร รัน crawl ใหม่แล้วสั่งสคริปต์เดิมทับ ตารางก็อัปเดตเองทั้งใบ
ใครจะเอาโครงนี้ไปปรับใช้กับเกณฑ์ปีอื่นหรือร้านอื่น เปลี่ยน regex ที่แกะ SSR กับ index ของ spec_full ให้ตรงเว็บนั้นก็พอ ที่เหลือ logic เทียบเกณฑ์ใช้ซ้ำได้เลย ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ
เคยเจองานแบบนี้ไหม? ติดตรงไหน?
มาแลกวิธีกันต่อในบอร์ด LinuxThai



ความคิดเห็น 0
ยังไม่มีความคิดเห็น — เป็นคนแรกเลย