ข้อความคือส่วนที่ง่าย: ไปป์ไลน์ที่สร้างภาพประกอบให้บทความที่เขียนโดย AI

การสร้างข้อความนั้นแก้ปัญหาได้แล้ว แต่การสร้างภาพประกอบยังไม่ใช่ ไปป์ไลน์แบบครบวงจร — router prompt, การค้นหารูปภาพ, ไดอะแกรม Mermaid, กราฟที่ผ่านการตรวจสอบ และ alt text เครดิต และมาร์กอัป ImageObject ที่เปลี่ยนสิ่งเหล่านี้ให้เป็น SEO

แชร์
โปรแกรมเมอร์กำลังพิมพ์งานอยู่ที่โต๊ะซึ่งมีจอมอนิเตอร์สองจอเต็มไปด้วยโค้ด ถูกจับด้วยแสงจากโคมไฟหลากสี
ภาพจาก Unsplash

คุณเป็นนักพัฒนาที่มีเป้าหมายด้านเนื้อหา: บทความหลายสิบชิ้นต่อเดือน อาจถึงหลายร้อยชิ้น โมเดลเขียนข้อความจัดการร่างบทความ โครงเรื่อง meta description ลิงก์ภายในได้ทั้งหมด แต่แล้วไปป์ไลน์ก็มาติดที่ขั้นตอนเดียวที่ไม่มีโมเดลของตัวเอง — รูปภาพ — แล้วก็หยุดชะงัก ไม่ใช่เพราะหารูปภาพยาก แต่เพราะไม่มีส่วนใดในสแตกที่รู้ว่า รูปไหน จาก ที่ไหน มีสิทธิ์การใช้งาน แบบใด และบรรยาย อย่างไร

บทความนี้คือขั้นตอนที่ขาดหายไปนั้น เชื่อมโยงกันตั้งแต่ต้นจนจบ: ควรสร้างภาพประเภทไหนสำหรับหัวข้อแบบใด พรอมต์ที่แปลงร่างบทความสมบูรณ์ให้กลายเป็นบรีฟสำหรับค้นหา การเรียก API ที่คืนภาพถ่าย โมเดลตัวที่สองที่วาดสิ่งที่ภาพถ่ายวาดไม่ได้ และตัวประกอบที่ออก markup ที่ Google อ่านได้จริง ปิดท้ายด้วยไปป์ไลน์การทำงานที่สมบูรณ์สองแบบ พร้อมให้คุณนำไปใช้ได้เลย

ข้อความแก้ปัญหาได้แล้ว การสร้างภาพประกอบคือจุดที่สะดุด

ลองตรวจสอบไปป์ไลน์บทความอัตโนมัติอย่างตรงไปตรงมา โครงเรื่อง: แก้ได้แล้ว ร่างบทความ: แก้ได้แล้ว ชื่อเรื่อง meta description schema ลิงก์ภายใน การแปลภาษา: แก้ได้แล้วทั้งหมด โดยโมเดลตัวเดียวกัน ทั้งหมดเป็นข้อความ จากนั้น:

ขั้นตอนไปป์ไลน์ สถานะ สิ่งที่ติดขัดจริง
โครงเรื่อง & ร่างบทความ แก้ได้แล้ว การเรียกโมเดลครั้งเดียว พรอมต์เดียว
ชื่อเรื่อง meta schema ลิงก์ แก้ได้แล้ว รับข้อความเข้า ส่งข้อความออก
ภาพหลัก (hero image) ติดขัด ต้องการไฟล์จริง สิทธิ์การใช้งานจริง ขนาดจริง และข้อความ alt — ไม่มีสิ่งใดที่โมเดลข้อความสร้างให้ได้
ภาพประกอบแต่ละหัวข้อ ติดขัด สามถึงห้าภาพต่อบทความ แต่ละภาพต้องต่างกัน ไม่ซ้ำกันทั่วทั้งเว็บไซต์
กราฟ & แผนภาพ ติดขัด ต้องแม่นยำ — เป็นภาพประเภทเดียวที่การค้นหาไม่มีวันคืนให้ และตัวสร้างภาพห้ามแต่งขึ้นเอง

ความล้มเหลวนี้ไม่ใช่เรื่องความสวยงาม แต่เป็นเรื่องโครงสร้าง: บทความถูกเผยแพร่พร้อมภาพสต็อกที่ใส่แบบขอไปที หรือใช้ภาพเดียวกับสิบสองบทความก่อนหน้า หรือใช้ภาพที่สร้างขึ้นซึ่งมีมือหกนิ้วเป็นสิ่งแรกที่ผู้อ่านเห็น และรูปภาพก็ไม่ใช่แค่การตกแต่ง — เอกสารด้านภาพของ Google เองก็ระบุไว้ชัดเจนว่า ข้อความ alt "เป็นแอตทริบิวต์ที่สำคัญที่สุดเมื่อพูดถึงการให้ข้อมูลเมทาดาทาสำหรับรูปภาพ" และแนะนำให้ใช้ <img> จริงพร้อม alt ที่บรรยายอย่างเหมาะสม แทนการใช้ CSS background เพื่อให้ภาพสามารถถูกค้นพบและเข้าใจได้เลยตั้งแต่แรก1

ภาพประกอบสี่ประเภท โมเดลสี่แบบ

ความผิดพลาดด้านการออกแบบที่ใหญ่ที่สุดคือการมองว่า "รูปภาพ" เป็นปัญหาเดียวที่มีผู้ให้บริการเดียว แท้จริงแล้วมันคือปัญหาสี่แบบ และตัวที่จัดเส้นทางระหว่างกันก็เป็นเพียงบรรทัดหนึ่งในพรอมต์ ไม่ใช่บริการแยกต่างหาก:

หัวข้อนี้ต้องการ… สร้างด้วย ทำไมถึงไม่ใช้ตัวอื่น
ฉากจากโลกจริงภาพหลัก สถานการณ์ของมนุษย์ สถานที่ วัตถุ ท่าทาง การค้นหาภาพเชิงความหมาย (Pexafy) ตัวสร้างภาพจะแต่งรายละเอียดขึ้นเอง ส่วนกราฟไม่มีข้อมูลให้วาด
ตัวเลขที่คุณมีอยู่จริงเบนช์มาร์ก ราคา ผลสำรวจ เวลาแฝง โมเดลที่เขียนโค้ดสำหรับพล็อตกราฟ รันในแซนด์บ็อกซ์ ไว้ใจโมเดลสร้างภาพกับค่าตัวเลขไม่ได้ ส่วนภาพถ่ายก็บรรจุข้อมูลไม่ได้
โครงสร้างหรือกระบวนการสถาปัตยกรรม ลำดับขั้นตอน state machine โมเดลที่เขียน Mermaid / Graphviz แล้วเรนเดอร์แบบกำหนดผลลัพธ์ตายตัว คลังภาพถ่ายฟรีไม่มีแผนภาพของระบบของคุณเอง
ผลิตภัณฑ์ของคุณบนหน้าจอเอกสาร changelog บทเรียน สกรีนช็อตเบราว์เซอร์ที่สคริปต์ควบคุม (Playwright) ไม่มีอะไรอื่นที่แสดง UI ที่มีอยู่จริงเฉพาะใน build ของคุณได้

แล้วภาพประกอบที่สร้างขึ้น (generated illustration) ล่ะ? มันยังมีบทบาทที่ซื่อสัตย์อยู่ช่องหนึ่ง คือฉากที่ไม่สามารถถ่ายภาพได้และไม่ใช่ข้อมูล — กลไกเชิงนามธรรม สินค้าที่ยังไม่มีอยู่จริง หรือสไตล์ภาพประกอบประจำบ้านที่คุณเป็นเจ้าของ (เหตุผลทั้งหมดว่าทำไมภาพถ่ายจริงจึงดีกว่าการสร้างภาพ — ความเร็วเมื่อทำในปริมาณมาก ความแม่นยำ ปัญหาความซ้ำซาก — อธิบายไว้ที่นี่) ราคากำลังเคลื่อนไปในทิศทางนี้: ตั้งแต่วันที่ 2 สิงหาคม 2026 มาตรา 50 ของ EU AI Act กำหนดให้ผู้ให้บริการระบบเจนเนอเรทีฟต้องทำเครื่องหมายผลลัพธ์สังเคราะห์ในรูปแบบที่เครื่องอ่านได้2 นั่นเป็นข้อผูกพันของผู้ให้บริการและผู้นำ AI ไปใช้ ไม่ใช่กฎว่าบล็อกจะเผยแพร่อะไรได้บ้าง — แต่นี่คือเหตุผลว่าทำไมแหล่งที่มาของภาพที่อยู่ด้านบนบทความของคุณจึงกลายเป็นสิ่งที่ผู้อ่านตรวจสอบได้มากขึ้นเรื่อยๆ แทนที่จะต้องเชื่อโดยไม่มีหลักฐาน

ไปป์ไลน์ ตั้งแต่ต้นจนจบ

ห้าขั้นตอน มีเพียงขั้นตอนที่ 3 เท่านั้นที่แตะ image API และมีเพียงขั้นตอนที่ 4 เท่านั้นที่เป็นทางเลือก:

ภาพรวมของมัน — บทความหนึ่งเข้า บทความที่เผยแพร่ได้หนึ่งออก
┌─ 1. WRITE ────────────────────────────────────────────────────┐
   topic ──▶ LLM ──▶ draft.md  (h2 sections, front-matter)
└───────────────────────────────┬───────────────────────────────┘
┌─ 2. BRIEF ────────────────────┴───────────────────────────────┐
   draft.md ──▶ LLM ──▶ { hero: {...}, sections: [ {...} ] }
                     one JSON object: per slot, a kind +
                     either a camera brief or a data/diagram spec
└──────────┬──────────────────────────────────┬─────────────────┘
           │ kind = "photo"                │ kind = "chart" | "diagram"
           ▼                               ▼
┌─ 3. SEARCH ───────────────┐   ┌─ 4. DRAW (optional) ─────────┐
   GET /search/photos          LLM ──▶ mermaid | plotting code
   ← credited photo +           ──▶ sandbox ──▶ .svg / .png
     w/h, blur_hash, alt,       (deterministic render, no
     licence, source URL         invented numbers)
└──────────┬────────────────┘   └──────────────┬───────────────┘
           └───────────────┬──────────────────┘
┌─ 5. ASSEMBLE ─────────────┴───────────────────────────────────┐
   <img> with width/height + fetchpriority | loading
   alt written for a human · visible credit · ImageObject JSON-LD
   photo_id stored so no two pages share a hero
└───────────────────────────────────────────────────────────────┘

มีคุณสมบัติสองอย่างที่สำคัญกว่าแผนภาพนี้ ขั้นตอนที่ 2 คือตัวจัดเส้นทาง: มันตัดสินใจต่อแต่ละช่องว่าตัวสร้างใดจะทำงาน ดังนั้นคุณจึงไม่มีวันขอกราฟแท่งจากคลังภาพถ่าย และขั้นตอนที่ 5 คือจุดที่ SEO อยู่ — ทุกสิ่งที่ Google บันทึกไว้เกี่ยวกับรูปภาพ (alt ที่บรรยายเหมาะสม เมทาดาทาสิทธิ์การใช้งาน ภาพหลักที่ปลอดภัยต่อ LCP) จะถูกออกที่นี่ จากฟิลด์ที่มาจากผลการค้นหาอยู่แล้ว

ขั้นตอนที่ 2: แปลงร่างบทความเป็นบรีฟสำหรับภาพ

การเรียกโมเดลเพียงครั้งเดียวกับดราฟต์ที่เสร็จแล้ว สิ่งที่ได้กลับมาคือ แผน ไม่ใช่คำค้นหา วิธีเขียนบรีฟภาพเดียว — เหตุใดชื่อบทความจึงเป็นอินพุตที่แย่ที่สุดเท่าที่จะเป็นไปได้ บรีฟกล้อง 12 ถึง 25 คำมีลักษณะอย่างไร และรูปแบบที่มันล้มเหลว — อธิบายไว้อย่างครบถ้วนใน คู่มือการทำภาพประกอบบทความเดียว จึงจะไม่พูดซ้ำที่นี่ สิ่งที่ไปป์ไลน์เพิ่มเข้ามาคือ การจัดเส้นทาง (routing): การเรียกครั้งเดียวกันนี้ต้องตัดสินใจ ทีละช่อง ว่าตัวสร้างใดจะทำงาน — และรายการชาร์ตหนึ่งรายการพกข้อมูลไปในขณะที่รายการภาพถ่ายพกฉากไป

พรอมต์สำหรับตัวจัดเส้นทาง — คัดลอกไปใช้ได้เลย
# system prompt — run once per finished draft
You are the art director of a technical publication. Read the article and
return the visual plan: one entry for the hero, one per H2 section.

For each entry choose exactly one kind:
  "photo"    a real scene: someone doing something somewhere, a place,
              an object, a gesture. The default for heroes.
  "chart"    the section states numbers that are IN the article. Never
              invent values: copy them into data, verbatim.
  "diagram"  the section describes a structure, a flow or a sequence.
  "none"     the section is short, or already carries a code block.

Rules for "photo" entries — the field is query:
เขียนบรีฟสำหรับกล้อง: บรรยายฉากที่กล้องสามารถบันทึกได้จริง ความยาว 12 ถึง 25 คำ
   เป็นภาษาอังกฤษ ให้สอดคล้องกับอารมณ์ของเนื้อหาในส่วนนั้น ระบุสิ่งที่อยู่ในเฟรม
   ไม่ใช่หัวข้อของเนื้อหา ห้ามมีข้อความ โลโก้ แบรนด์ หรือบุคคลที่มีชื่อเสียง และห้ามใช้
   สัญลักษณ์เปรียบเทียบที่มองไม่เห็น (Full rules, with examples and failure cases:
   pexafy.com/blog/illustrate-blog-articles-at-scale/)

Do NOT write the alt text of a photo entry: the picture you get back is the
closest match to the brief, not the scene you described, so its alt has to be
written from the chosen photo. Chart and diagram entries DO carry an alt —
there you control exactly what is rendered.

Return JSON only:
{
  "hero": { "kind": "photo", "query": "…", "orientation": "landscape" },
  "sections": [
    { "h2": "…", "kind": "photo",   "query": "…" },
    { "h2": "…", "kind": "chart",   "title": "…", "unit": "ms",
      "data": [ {"label": "…", "value": 0} ], "alt": "…" },
    { "h2": "…", "kind": "diagram", "spec": "flowchart LR; …", "alt": "…" }
  ]
}

บรรทัด kind ทำงานส่วนใหญ่ และคำสั่ง data ทำส่วนที่เหลือ: รายการชาร์ตอาจพกได้เฉพาะตัวเลขที่ปรากฏอยู่ในดราฟต์อยู่แล้วเท่านั้น ดังนั้นโมเดลจึงถ่ายทอดข้อมูลมาแทนที่จะแต่งขึ้นเอง นี่คือตัวจัดเส้นทางบนสามส่วนจริงของบทความสำหรับนักพัฒนา:

หัวข้อ kind สิ่งที่ตัวจัดเส้นทางคืนมา
ภาพหลัก — "ทำไม nightly build ของเราถึงใช้เวลา 40 นาที" photo "นักพัฒนากำลังทำงานดึกที่โต๊ะ พร้อมจอสองจอและคีย์บอร์ดกลไก ในห้องมืดที่มีแสงจากหน้าจอ"
"เวลาถูกใช้ไปที่ไหนบ้าง" chart data ที่คัดลอกจากย่อหน้า: install 480 วินาที, compile 1080 วินาที, test 720 วินาที, upload 120 วินาที
"เราแบ่งกราฟงานอย่างไร" diagram flowchart LR ของกราฟความสัมพันธ์ของงาน
"สิ่งที่เราเปลี่ยน และสิ่งที่เราจะทำซ้ำอีก" photo "วิศวกรสองคนยืนที่ไวท์บอร์ดเต็มไปด้วยแผนภาพ ช่วยกันแก้ปัญหา"

ขั้นตอนที่ 3: ภาพถ่ายกลับมาพร้อมเครดิต

ทุกรายการที่มี kind: "photo" คือหนึ่งคำขอ บรีฟภาพหลักด้านบน เมื่อรันกับ API สาธารณะ จะคืนผลนี้ใน 147 มิลลิวินาที:

GET /search/photos — บรีฟภาพหลัก · "นักพัฒนากำลังทำงานดึกที่โต๊ะพร้อมจอสองจอ…" · 147 มิลลิวินาที
เอนจิ้นจัดอันดับตามความหมาย ดังนั้นประโยคยาว ๆ จึงช่วยแคบผลลัพธ์แทนที่จะทำให้ว่างเปล่า ลองรันการค้นหานี้แบบเป๊ะ ๆ →

บรีฟของหัวข้อสุดท้าย เป็นฉากที่ต่างออกไปโดยสิ้นเชิง ใน 144 มิลลิวินาที:

GET /search/photos — บรีฟหัวข้อ · "วิศวกรสองคนยืนที่ไวท์บอร์ดเต็มไปด้วยแผนภาพ…" · 144 มิลลิวินาที
บทความเดียวกัน การรันเดียวกัน ฉากที่ไม่มีใครจะสับสนกับภาพหลัก — เพราะบรีฟถูกเขียนต่อหัวข้อ ไม่ใช่ต่อบทความ ลองรันการค้นหานี้ด้วย →

สิ่งที่ได้กลับมาต่อภาพหนึ่งภาพคือส่วนที่ทำให้ขั้นตอนที่ 5 เป็นไปได้ — ไม่ใช่แค่ไฟล์เท่านั้น:

ผลลัพธ์หนึ่งรายการ ตัดให้เหลือเฉพาะฟิลด์ที่ตัวประกอบใช้งาน
{
  "photo_id":  "019e1c7f-0063-759e-b498-33ce1714e6c9",   // เก็บไว้: ไม่ให้ซ้ำ
  "urls": { "small": "…?w=400", "regular": "…?w=1080",
             "large": "…?w=1920" },
  "width": 3000, "height": 1688,          // → ไม่มี layout shift
  "blur_hash": "LJ8gjv9rVq-6OFxanNNFI7xco$Na",   // → placeholder จริง
  "alt_description": "Person types on keyboard in front of dual monitors…",
  "photographer_full_name": "Jakub Żerdzicki",   // → ImageObject.creator
  "source": "Unsplash", "license_type": "free",
  "source_image_url": "https://unsplash.com/photos/…",
  "attribution": { "html": "<span…>Photo by …</span>",
                    "plain": "Photo by Jakub Żerdzicki on Unsplash (…)" }
}

ขั้นตอนที่ 4: สิ่งที่ภาพถ่ายบอกไม่ได้

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

แผนภาพ: ข้อความเข้า SVG ออก

Mermaid เรนเดอร์แผนภาพจากคำนิยามข้อความล้วน3 ซึ่งทำให้มันเป็นเป้าหมายที่ปลอดภัยที่สุดสำหรับโมเดล: ผลลัพธ์ตรวจสอบได้ ทำ diff ใน git ได้ และเรนเดอร์ผลลัพธ์เดิมทุกครั้ง ตัวจัดเส้นทางได้คืนสเปกมาแล้ว

diagram.sh — โมเดลเขียนสเปก CLI เรนเดอร์มัน
# ฟิลด์ "spec" ของรายการ diagram ที่เขียนไปยัง build/graph.mmd
cat build/graph.mmd
flowchart LR
  install["install deps · 480s"] --> compile["compile · 1080s"]
  compile --> test["test suite · 720s"]
  compile --> upload["upload artifacts · 120s"]

npx -y @mermaid-js/mermaid-cli -i build/graph.mmd -o static/img/graph.svg
# → SVG ที่คุณรีวิวได้ใน PR ไม่ใช่ภาพที่ต้องเชื่อเปล่า ๆ

กราฟ: เฉพาะตัวเลขที่บทความมีอยู่แล้วเท่านั้น

หลักการเดียวกัน มีตัวป้องกันเพิ่มหนึ่งชั้น ตัวจัดเส้นทางคัดลอกค่าจากร่างบทความมาแล้ว โมเดลเขียนโค้ดสำหรับพล็อตกราฟ โค้ดรันในแซนด์บ็อกซ์ และตัวประกอบจะตรวจสอบค่าที่เรนเดอร์ออกมาซ้ำกับตัวเลขต้นฉบับ ก่อนที่กราฟจะได้รับอนุญาตให้เข้าใกล้หน้าเพจ

chart.py — พล็อตข้อมูลที่คัดลอกมา แล้วตรวจสอบ
import re, matplotlib
matplotlib.use("Agg")                # headless: ไม่มี display ใน CI
import matplotlib.pyplot as plt

def assert_in_draft(value, draft: str) -> None:
    """ค่าที่จะพล็อตต้องปรากฏในบทความในฐานะตัวเลข"""
    # การจับคู่ substring คือกับดักตรงนี้: "120" อยู่ใน "1200" และ
    # อยู่ใน "?w=1200" — `str(v) in draft` แบบง่าย ๆ จะผ่านทุกอย่าง
    # จับคู่ที่ขอบเขตคำ และยอมรับ 1 234 / 1,234 / 1234
    body = re.sub(r"(?<=\d)[  ,](?=\d{3}\b)", "", draft)   # ตัดตัวคั่นออก
    if not re.search(rf"(?<![\d.]){re.escape(str(value))}(?![\d.])", body):
        raise ValueError(f"{value} is not stated in the article — refusing to plot")

def render_chart(entry: dict, draft: str, out: str) -> str:
    labels = [d["label"] for d in entry["data"]]
    values = [d["value"] for d in entry["data"]]
    for v in values:
        assert_in_draft(v, draft)       # ค่าที่หลอนขึ้นมา → ไม่มีกราฟ

    fig, ax = plt.subplots(figsize=(8, 4.5), dpi=160)
    ax.barh(labels, values)
    ax.set_xlabel(entry["unit"])
    ax.set_title(entry["title"])
    fig.tight_layout()
    fig.savefig(out)                    # ผลลัพธ์ตายตัว รีวิวได้
    return out

ตัวป้องกันนี้สั้น แต่ต้องเขียนอย่างระมัดระวัง: การตรวจสอบแบบ substring ใช้ไม่ได้ "120" in draft เป็นจริงสำหรับบทความที่มี 1200 หรือ ?w=1200 ดังนั้นเวอร์ชันแบบไร้เดียงสาจึงผ่านทุกอย่างและป้องกันไม่ได้อะไรเลย ให้ยึดกับขอบเขตของตัวเลข ปรับตัวคั่นหลักพันให้เป็นมาตรฐาน แล้วข้อผิดพลาดประเภทที่ทำให้บทความเทคนิคถูกวิจารณ์เละในคอมเมนต์ — กราฟที่ขัดแย้งกับย่อหน้าของตัวเอง — จะไม่มีทางเข้าถึง production ได้ สกรีนช็อตก็ใช้หลักการเดียวกัน: page.screenshot() ที่สคริปต์ควบคุมกับ build จริงของคุณ คือแหล่งความจริงเพียงหนึ่งเดียวสำหรับ UI ของคุณเอง และยังคงเป็นจริงเสมอแม้ UI จะเปลี่ยนไป

ขั้นตอนที่ 5: ขั้นตอนการประกอบคือจุดที่ SEO อยู่

ทุกอย่างจนถึงตอนนี้สร้างไฟล์และฟิลด์ต่าง ๆ ขั้นตอนนี้จะแปลงมันเป็น markup — และควรพิจารณาให้ละเอียดตรงนี้ เพราะพฤติกรรมที่มีเอกสารบันทึกไว้สามอย่างถูกตัดสินใจในไม่กี่บรรทัดนี้

ภาพหลัก ออกจากผลการค้นหา — ไม่มีสิ่งใดถูกแต่งขึ้นเอง
<!-- ไบต์ข้อมูลมาจากโดเมนอื่น: จ่ายค่า handshake ล่วงหน้า -->
<link rel="preconnect" href="https://images.unsplash.com" crossorigin>

<!-- element ที่เป็น LCP: ห้าม lazy เด็ดขาด ให้ priority สูงเสมอ -->
<figure>
  <img src="{urls.regular}"
       width="{width}" height="{height}"        <!-- ป้องกัน layout shift -->
       alt="{alt}"                                <!-- เขียนหลังจากเลือกภาพแล้ว -->
       fetchpriority="high" decoding="async"
       style="background:{color_hex}">   <!-- สีเด่น หนึ่งฟิลด์ -->
  <figcaption>{attribution.html}</figcaption>
</figure>

<!-- ภาพประกอบหัวข้อ ใต้ fold: ตั้งค่าตรงข้ามกัน -->
<img src="{urls.regular}" width="{width}" height="{height}"
     alt="{alt}" loading="lazy" decoding="async">

Hotlink หรือโฮสต์ซ้ำเอง? โค้ดด้านบนใช้วิธี hotlink ซึ่งเป็นวิธีที่ส่งขึ้นระบบเร็วที่สุด และเป็นเหตุผลของ preconnect: ภาพหลักจากที่อื่นต้องเสียเวลา DNS lookup และ TLS handshake บน critical path ซึ่งอาจกินผลประโยชน์ที่คุณเพิ่งได้จาก fetchpriority ไป ส่วนการโฮสต์ซ้ำเองจะตัดโดเมนบุคคลที่สามออกไปทั้งหมด ทำให้คุณเสิร์ฟ AVIF/WebP ตาม breakpoint ของตัวเองได้ และไม่ได้รับผลกระทบเมื่อ URL ต้นทางเปลี่ยน — แลกมาด้วยพื้นที่จัดเก็บ ขั้นตอนดึงข้อมูลในไปป์ไลน์ และค่าใช้จ่าย CDN ของคุณเอง ไม่ว่าจะเลือกแบบไหน color_hex ก็ให้ placeholder สำหรับหนึ่งฟิลด์ (blur hash สวยกว่า แต่ต้อง decode เป็น data URI ก่อน — มันไม่ใช่สี CSS) การคัดลอกภาพหลักแบบหยาบ ๆ ตรง ๆ ลงใน background: คือจุดที่ไปป์ไลน์ส่วนใหญ่แอบส่งกล่องเทาว่าง ๆ ออกไปโดยไม่รู้ตัว

  1. อย่า lazy-load ภาพหลักเด็ดขาด web.dev พูดชัดเจนว่า "อย่า lazy-load ภาพ LCP ของคุณเด็ดขาด เพราะมันจะทำให้เกิดความล่าช้าในการโหลดทรัพยากรโดยไม่จำเป็นเสมอ และจะส่งผลเสียต่อ LCP" และแนะนำให้ใช้ fetchpriority="high" บน element ที่น่าจะเป็น LCP — ใช้อย่างประหยัด บนภาพเดียว4 ไปป์ไลน์ที่ตราตรา loading="lazy" บนทุกภาพ รวมถึงภาพหลัก คือบาดแผลที่ทำร้าย Core Web Vitals ด้วยตัวเองที่พบบ่อยที่สุดในระบบเผยแพร่อัตโนมัติ
  2. ออก width และ height เสมอ — ทั้งสองค่านี้มาพร้อมกับ ผลลัพธ์อยู่แล้ว จึงไม่มีข้ออ้างใด ๆ คู่แอตทริบิวต์นี้คือสิ่งที่ทำให้เบราว์เซอร์จองพื้นที่ไว้ล่วงหน้า และหยุดเลย์เอาต์กระโดด ใช้ blur_hash เป็น placeholder ระหว่างที่ไฟล์กำลังโหลด
  3. เขียน alt หลังจากเลือกภาพแล้ว ไม่ใช่ก่อน นี่คือจุดที่ละเอียดอ่อน ฟิลด์ alt ในแผนบรรยายฉากที่คุณขอ แต่ภาพที่ได้มาเป็นเพียงภาพที่ใกล้เคียงที่สุด ไม่ใช่ฉากนั้นจริง ๆ การใช้ข้อความจากบรีฟเป็น alt ตรง ๆ คือความล้มเหลวด้านการเข้าถึงที่ไปป์ไลน์นี้ ควรจะป้องกันได้ — คำบรรยายภาพที่ไม่ได้อยู่บนหน้าเพจ ให้สร้าง alt จาก alt_description ของภาพที่เลือก แล้วปรับให้เข้ากับย่อหน้าที่มันอยู่ คำแนะนำของ Google คือ "มุ่งเน้นสร้างเนื้อหาที่มีประโยชน์ อุดมด้วยข้อมูล ใช้คีย์เวิร์ดอย่างเหมาะสม และอยู่ในบริบทของเนื้อหาบนหน้าเพจ" และเตือนว่าการยัดคีย์เวิร์ดใน แอตทริบิวต์ alt "จะส่งผลเสียต่อประสบการณ์ผู้ใช้ และอาจทำให้เว็บไซต์ของคุณถูกมองว่าเป็นสแปม"1

ส่วนที่แทบไม่มีใครทำอัตโนมัติ: เมทาดาทาสิทธิ์การใช้งาน

Google รองรับ structured data แบบ ImageObject สำหรับสิทธิ์การใช้งานภาพ ต้องมี contentUrl บวกกับอย่างน้อยหนึ่งใน creator, creditText, copyrightNotice หรือ license แนะนำให้มี acquireLicensePage และภาพที่มีข้อมูลสิทธิ์การใช้งานจะมีสิทธิ์ได้รับตราสัญลักษณ์ Licensable ใน Google Images5 ทุกฟิลด์เหล่านี้มีอยู่ในผลการค้นหาอยู่แล้ว — ดังนั้นการออกมันจึงเป็นเทมเพลต ไม่ใช่โปรเจกต์:

ImageObject JSON-LD กรอกจากผลการตอบกลับของ API
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "ImageObject",
  "contentUrl": "{urls.large}",                 // จำเป็น
  "creator": { "@type": "Person",
                "name": "{photographer_full_name}" },
  "creditText": "{photographer_full_name} on {source}",
  "license": "{LICENSE_URL[source]}",           // หน้าเพจสิทธิ์การใช้งานของคลังภาพเอง
  "acquireLicensePage": "{source_image_url}"     // หน้าเพจของภาพนั้น
}
</script>

# LICENSE_URL จับคู่ฟิลด์ `source` กับสิทธิ์การใช้งานที่กำกับดูแลภาพนั้นจริง ๆ
# unsplash.com/license, pexels.com/license, pixabay.com/…

รายละเอียดหนึ่งที่ควรทำให้ถูกต้อง: license ควรชี้ไปยังสิทธิ์การใช้งานที่ กำกับดูแลภาพนั้นจริง ๆ — หน้าเพจสิทธิ์การใช้งานของคลังภาพต้นทางเอง — ไม่ใช่หน้าสรุปบนโดเมนของคุณ Google อ่านมันเพื่อตัดสินสิทธิ์ในการได้รับตราสัญลักษณ์ และ URL ที่อ้างอิงถึงตัวเองย่อมเป็นสัญญาณที่อ่อนกว่า และยากที่จะแก้ต่างว่าเป็นอะไรอื่นนอกจากลิงก์ย้อนกลับมาหาตัวเอง เก็บสรุปสิทธิ์การใช้งานของคุณเองไว้เป็นหน้าภายในสำหรับผู้อ่าน แต่ใส่ลิงก์มาตรฐานลงใน markup

และในขณะที่คุณกำลังทำตัวประกอบอยู่: ตั้งชื่อไฟล์ให้สั้นและบรรยายได้ชัดเจนแทนที่จะเป็น IMG_0042.jpg และเพิ่มภาพลงใน sitemap — รูปแบบ image sitemap ของ Google รองรับ ได้ถึง 1,000 ภาพต่อหนึ่ง URL หน้าเพจ6 ทั้งสองอย่างใช้เพียงหนึ่งบรรทัดในไปป์ไลน์ และไม่มีใครทำมันด้วยมือเลย

สองไปป์ไลน์ที่ควรนำไปใช้

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

1 · บล็อกนักพัฒนาใน CI — Markdown ในรีโพซิทอรี

บทความอยู่เป็น Markdown ภาพถูก commit ไว้ข้าง ๆ กัน และทั้งหมดรันเมื่อ push ให้ผลลัพธ์ตายตัว รีวิวได้ใน PR ไม่มีการพึ่งพา API ใด ๆ ตอน runtime:

.github/workflows/illustrate.yml
on: { pull_request: { paths: ["content/**.md"] } }

jobs:
  illustrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: python plan_to_pr.py $(git diff --name-only origin/main -- 'content/*.md')
        env:
          PEXAFY_API_KEY: ${{ secrets.PEXAFY_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      - uses: peter-evans/create-pull-request@v6   # ภาพถูกใส่ไว้ใน PR
        with: { commit-message: "chore(content): illustrate" }

มนุษย์ยังคงเป็นผู้อนุมัติ PR ซึ่งนั่นคือประเด็นสำคัญ: ไปป์ไลน์เสนอ ผู้รีวิวตัดสินใจ และ front-matter ที่มันเขียนก็ทำ diff ได้

ครึ่งหนึ่งของการค้นหาคือ GET เดียว — คำขอ, score_threshold ของมัน และฟิลด์ที่มันส่งกลับมาถูกเขียนไว้ทีละบรรทัดใน คู่มือบทความเดี่ยว ดังนั้นจึงนำเข้ามาใช้ที่นี่แทนการพิมพ์ซ้ำ สิ่งที่ไฟล์นี้เพิ่มเข้ามาคือทุกสิ่งที่ แผน ต้องการแต่ภาพเดี่ยวหนึ่งภาพไม่มี: การลองใหม่เมื่อบรีฟไม่ได้ผลลัพธ์กลับมา การจองสิทธิ์ photo_id เพื่อไม่ให้สองหน้าใช้ภาพเดียวกัน และ alt ที่เขียนขึ้นจากภาพที่ได้กลับมา

plan_to_pr.py — ตัวเชื่อม: รับแผนภาพเข้ามา ส่ง front-matter ออกไป
import frontmatter
from photo_search import search   # GET /search/photos หนึ่งครั้ง; ข้าม id ที่อยู่ใน `used`

def find_photo(entry: dict, used: set) -> dict | None:
    """ค้นหา; ถ้าบรีฟเจาะจงเกินไป ให้ขยายขอบเขตหนึ่งครั้ง แล้วยอมแพ้"""
    orientation = entry.get("orientation", "landscape")
    for query in (entry["query"], widen(entry["query"])):
        photo = search(query, orientation, used)
        if photo:
            used.add(photo["photo_id"])   # จองไว้: ไม่ให้ซ้ำทั้งเว็บไซต์
            return photo
    return None                        # ให้ผู้เรียกตัดสินใจ: ข้ามช่องนี้ หรือ fail

def widen(query: str) -> str:
    """ตัดวลีสุดท้ายออก — มักจะเป็นส่วนที่เจาะจงเกินไป"""
    return query.rsplit(" in ", 1)[0] if " in " in query else query

def illustrate(path: str, plan: dict, used: set) -> None:
    post = frontmatter.load(path)
    hero = find_photo(plan["hero"], used)
    if hero is None:                    # ไม่มีภาพหลักดีกว่ามีภาพหลักที่แย่
        raise SystemExit(f"{path}: no photo above threshold — rewrite the brief")

    post["hero"] = {                    # ทุกอย่างที่เทมเพลตต้องการ
        "src": hero["urls"]["regular"], "w": hero["width"], "h": hero["height"],
        # alt บรรยายภาพที่เรา "ได้มา" ไม่ใช่ฉากที่เราขอ
        "alt": alt_for(hero, section=post.get("title", "")),
        "bg": hero["color_hex"], "credit": hero["attribution"]["html"],
        "creator": hero["photographer_full_name"], "id": hero["photo_id"],
        "source": hero["source"],
        "source_url": hero["source_image_url"],   # → acquireLicensePage
    }
    open(path, "w").write(frontmatter.dumps(post))

def alt_for(photo: dict, section: str) -> str:
    """ใช้ alt_description เป็นฐาน ตัดให้เหลือ ~125 ตัวอักษรสำหรับ screen reader
    ส่งกลับไปให้โมเดลพร้อมย่อหน้านั้นอีกครั้งถ้าต้องการให้ดีขึ้น"""
    base = photo.get("alt_description") or photo.get("description", "")
    return base[:125].rstrip(" ,;")

2 · เอกสาร & changelog — สกรีนช็อตก่อน ภาพถ่ายทีหลัง

กลับด้านค่าเริ่มต้นของตัวจัดเส้นทาง ในเอกสารผลิตภัณฑ์ ภาพที่ตรงไปตรงมาที่สุดมักเป็น UI ของคุณเอง: สคริปต์ Playwright ที่เปิด build จริง ตั้งค่า viewport ตายตัว และจับภาพสถานะที่ย่อหน้านั้นบรรยายเป๊ะ ๆ แผนภาพครอบคลุมหน้าสถาปัตยกรรม และภาพถ่ายจะปรากฏเฉพาะในหน้าแนวคิดและหน้า landing page — ที่ซึ่งสกรีนช็อตจะไม่บอกอะไรเลย

shots.py — สกรีนช็อตถูกสร้างขึ้น ไม่ใช่บรรยาย
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page(viewport={"width": 1440, "height": 900},
                            device_scale_factor=2)   # คมชัดระดับ retina
    page.goto("http://localhost:3000/dashboard")
    page.get_by_role("button", name="New API key").click()
    page.screenshot(path="static/img/docs/new-api-key.png")
    browser.close()
# รันใน CI job เดียวกับการ build เอกสาร → สกรีนช็อตจึงไม่มีวัน
# บรรยาย UI เวอร์ชันที่ไม่มีอยู่แล้วได้เลย

มีสองรูปแบบที่ควรกล่าวถึงแต่ไม่คุ้มที่จะแยกเป็นสูตรของตัวเอง Programmatic SEO กลับด้านลูปทั้งหมด: ด้วยหลายร้อยหน้าที่สร้างจากฐานข้อมูล คุณจะไม่ค้นหาต่อหน้าเพจ — หนึ่งคำขอคืนได้ถึง 100 ภาพ ดังนั้นคุณจึงค้นหาต่อกลุ่มหัวข้อ แล้วกระจายจากพูล โดยมีข้อจำกัดเรื่องความไม่ซ้ำกันเป็นตัวจัดการการซ้ำซ้อน (สถาปัตยกรรมนั้น อธิบายแบบเต็ม) ส่วนจดหมายข่าวและการ์ดโซเชียลต้องการภาพเดียวกันในสี่ขนาด: เก็บ photo_id ไว้ ขอขนาดที่ต้องการจาก urls และค้นหาใหม่เฉพาะเมื่อ การตัดภาพล้มเหลวจริง ๆ เท่านั้น

ไปป์ไลน์เดียวกัน แต่เป็นเอเจนต์ตัวเดียว

ถ้าโมเดลกำลังเขียนดราฟต์อยู่แล้ว วิธีที่สั้นที่สุดคือมอบเครื่องมือค้นหาให้กับมันโดยตรง แทนที่จะส่งต่อ JSON ไปมาระหว่างโปรเซส Pexafy รัน Model Context Protocol server แบบโฮสต์ไว้ที่ mcp.pexafy.com/mcp การตั้งค่า connector และเครื่องมือที่มันเปิดให้ใช้งาน ได้อธิบายไว้ในที่อื่นแล้ว — การตั้งค่าเดสก์ท็อปและ editor ใน คู่มือบทความเดี่ยว, รูปแบบ headless สำหรับ CI runner ใน บทความเรื่อง content at scale, และภาพรวมของตลาด connector ที่กว้างขึ้น — ใครเป็นผู้ให้บริการ MCP image server บ้าง ด้วยเงื่อนไขแบบไหน — ใน การศึกษา โครงสร้างพื้นฐานการค้นหาภาพสำหรับ AI agent สิ่งที่ควรแสดงให้เห็นตรงนี้คือสิ่งที่เกิดขึ้นกับไปป์ไลน์นี้เมื่อ agent ถือเครื่องมือเหล่านั้นเอง: มันไม่ใช่ห้าขั้นตอนอีกต่อไป แต่กลายเป็นคำสั่งเดียว

คำสั่งเดียว แผนทั้งหมดถูกดำเนินการ
You  นี่คือร่างบทความ สร้างแผนภาพประกอบ ทำภาพประกอบ แล้วเปิด PR
     ให้กราฟใช้เฉพาะตัวเลขที่มีอยู่ในข้อความแล้วเท่านั้น

Agent  → plan: hero=photo · §2=chart · §3=diagram · §4=photo
      → search_photos(q="a developer working late at a desk with two
         monitors and a mechanical keyboard in a dark room…")
      ← 16 photos · 147 ms · picked #1, 3000×1688, credited
      → mermaid-cli build/graph.mmd → static/img/graph.svg
      → chart.py §2 → 4 values checked against the draft ✓
      → search_photos(q="two engineers standing at a whiteboard…")
      ← 16 photos · 144 ms · picked #1

      ✓ เติมครบ 4 ช่อง · เรียก API 2 ครั้ง · เขียน alt + credit + ImageObject แล้ว
      ⚠ §5 ไม่ได้ผลลัพธ์ที่เกิน 0.5 — บรีฟเป็นนามธรรมเกินไป จึงเขียนใหม่เป็น
        "a person at a kitchen table checking figures on a laptop"

บรรทัดสุดท้ายนั้นคือเหตุผลว่าทำไมเอเจนต์จึงคุ้มค่าที่จะมีอยู่ในลูปนี้มากกว่าสคริปต์ล้วน ๆ: จุดล้มเหลวของการทำภาพประกอบอัตโนมัติคือบรีฟที่แย่ และการเขียนบรีฟที่แย่ใหม่ก็เป็นสิ่งที่โมเดลภาษา เกิดมาเพื่อทำพอดี ส่วนที่ตายตัว — การกำหนด การป้องกันความซ้ำซ้อน ตัวป้องกันตัวเลข — ให้เก็บไว้ในโค้ด

บทความที่มีภาพประกอบหนึ่งชิ้นมีต้นทุนเท่าใด

ช่องภาพถ่ายสามช่องต่อบทความหมายถึงคำขอค้นหาสามครั้ง ดังนั้นแพลนฟรี (5,000 คำขอ/เดือน) ครอบคลุม 1,666 บทความต่อเดือน ก่อนที่จะต้องคิดเรื่องเสียเงินเลยด้วยซ้ำ — และถ้าคุณรวมการค้นหาต่อกลุ่มหัวข้อแทนที่จะเป็นต่อบทความ เพดานนี้จะขยับไปอีกหนึ่งขั้นของขนาด รายละเอียดแยกตามแพลน และสถาปัตยกรรมการรวมที่ทำให้ประเด็นนี้ไม่สำคัญอีกต่อไป อยู่ใน บทความคู่กันเรื่องการทำภาพประกอบเนื้อหาระดับใหญ่

การเรียกโมเดลสองครั้งต่อบทความ — หนึ่งครั้งสำหรับร่างบทความ หนึ่งครั้งสำหรับแผนภาพ — ใช้โทเคนเพียงไม่กี่พันตัว และจะเป็นบรรทัดที่ถูกที่สุดในไปป์ไลน์ การเรนเดอร์ Mermaid และ matplotlib ไม่มีต้นทุนอะไรนอกจากเวลา CI ตัวเลขที่ทำให้คนแปลกใจคือบรรทัดไหนไม่ถูก: การสร้างสี่ภาพต่อบทความ สี่ร้อยภาพต่อเดือน บวกกับความพยายามที่ไม่ผ่านคัดเลือก — และผลลัพธ์ก็ยังไม่มีชื่อช่างภาพ ไม่มีวันที่ และไม่มี URL ต้นทางอยู่ดี

สิ่งที่ไปป์ไลน์นี้ไม่ได้แก้ไข

ขอพูดอย่างตรงไปตรงมาในส่วนนี้ เพราะความล้มเหลวที่มันป้องกันไม่ได้มีราคาแพง บทความที่มีภาพประกอบดี ก็ยังคงเป็นแค่บทความ: ภาพถ่ายจริง กราฟที่แม่นยำ และ markup ที่ถูกต้อง ช่วยยกระดับหน้าเพจที่ สมควรมีอยู่แล้ว แต่ไม่ได้ทำให้เนื้อหาบาง ๆ ที่ผลิตแบบจำนวนมากติดอันดับได้ นโยบายสแปมของ Google ระบุถึง การใช้เนื้อหาแบบสเกลในทางที่ผิด (scaled content abuse) — การสร้างหลายหน้าเพจเป็นหลักเพื่อบิดเบือนอันดับและให้คุณค่าเล็กน้อยแก่ผู้ใช้ ไม่ว่าจะเกี่ยวข้องกับระบบอัตโนมัติหรือไม่ก็ตาม — และคุณภาพของภาพประกอบก็ไม่ใช่ปัจจัย ในการตัดสินนั้น7

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

  • ที่มาที่ผู้อ่านตรวจสอบได้ เครดิตที่ระบุช่างภาพจริงและ URL ต้นทางเป็นข้อกล่าวอ้าง ที่ตรวจสอบได้ — และฟิลด์เดียวกันก็ป้อนเข้าสู่ markup ImageObject ที่ Google อ่าน
  • การเข้าถึงและ Core Web Vitals ข้อความ alt จริง ขนาดกำหนดในทุกภาพ ภาพหลักที่ไม่เคย lazy-load เลย คูณเข้ากับทุกบทความที่คุณเผยแพร่ นี่คือเรื่องราวด้านคุณภาพภาพของเว็บไซต์เลย
  • ความแม่นยำในจุดที่ตรวจสอบได้ กราฟที่ตัวเลขถูกยืนยันกับบทความ สกรีนช็อตที่สร้างจาก build ที่รันอยู่จริง แผนภาพที่รีวิวได้เป็นข้อความใน PR — ภาพประกอบสามประเภทที่คลาดเคลื่อนจากความจริงไม่ได้ โดยไม่มี test ล้มเหลว

ในเรื่องการให้เครดิต ประเด็นเฉพาะของไปป์ไลน์นั้นแคบมาก: เหตุผลที่ควรแสดงเครดิตเสมอ อธิบายไว้ที่อื่นแล้ว และสิ่งที่ตัวประกอบอัตโนมัติเพิ่มเข้ามาคือสตริง attribution เดียวกันที่มันพิมพ์ออกมานั้นก็คือ creditText ที่ structured data ต้องการนั่นเอง ฟิลด์เดียว สองที่ ปล่อยออกมาในการรันเทมเพลตครั้งเดียวกัน — ซึ่งเป็นเหตุผลว่าทำไมไปป์ไลน์จึงมีข้อแก้ตัวน้อยกว่ามนุษย์เสียอีกที่จะละเลยมัน

จะเริ่มตรงไหน

  1. เพิ่มพรอมต์ของตัวจัดเส้นทาง เข้าไปในสิ่งที่กำลังเขียนร่างบทความของคุณอยู่แล้ว แล้วพิมพ์ JSON ออกมาโดยยังไม่ต้องดำเนินการใด ๆ อ่านแผนสิบชุด ถ้าบรีฟระบุหัวข้อแทนที่จะเป็นฉาก ให้แก้พรอมต์ก่อนที่จะเขียนการเชื่อมต่อใด ๆ
  2. เชื่อมต่อเฉพาะช่องภาพถ่าย หนึ่ง GET /search/photos ต่อบรีฟหนึ่งอัน และเก็บ photo_id ตั้งแต่วันแรก — การป้องกันความซ้ำซ้อนที่ต้องย้อนกลับไปทำกับ 3,000 หน้าเพจ ที่ใช้งานอยู่แล้ว คือการย้ายระบบ ไม่ใช่แค่การเพิ่มคอลัมน์
  3. ออก width, height และ alt ใน commit เดียวกัน มันคืองาน Core Web Vitals ที่ถูกที่สุดที่คุณจะได้ทำ
  4. จากนั้นเพิ่มขั้นตอนที่ 4 แผนภาพก่อนกราฟ — Mermaid เป็นข้อความ ดังนั้นมันจึงเป็น ตัวเดียวที่ไม่มีจุดล้มเหลวอะไรเกินกว่าข้อผิดพลาดทางไวยากรณ์
  5. เพิ่มตัวป้องกันตัวเลข ก่อนที่กราฟแรกจะไปถึงผู้อ่าน ไม่ใช่หลังจากนั้น

แหล่งอ้างอิง & เชิงอรรถ

1 Google Search Central, แนวทางปฏิบัติที่ดีที่สุดสำหรับ Image SEO: ข้อความ alt "เป็นแอตทริบิวต์ที่สำคัญที่สุดเมื่อพูดถึงการให้ข้อมูลเมทาดาทาสำหรับรูปภาพ" คำแนะนำคือให้สร้าง "เนื้อหาที่มีประโยชน์ อุดมด้วยข้อมูล ใช้คีย์เวิร์ดอย่างเหมาะสม และอยู่ในบริบทของเนื้อหาบนหน้าเพจ" หลีกเลี่ยงการยัดคีย์เวิร์ดในแอตทริบิวต์ alt ใช้ element <img> แบบ HTML แทนภาพ CSS และตั้งชื่อไฟล์ให้สั้นแต่บรรยายได้ชัดเจน

2 EU AI Act มาตรา 50 — ข้อผูกพันด้านความโปร่งใสที่มีผลบังคับใช้ตั้งแต่วันที่ 2 สิงหาคม 2026: ผู้ให้บริการระบบที่สร้างภาพ เสียง วิดีโอ หรือข้อความสังเคราะห์ ต้องทำเครื่องหมายผลลัพธ์ในรูปแบบที่เครื่องอ่านได้ และทำให้สามารถตรวจจับได้ว่าเป็นสิ่งที่สร้างขึ้นด้วย AI ข้อกำหนดนี้ผูกพันผู้ให้บริการและผู้นำ AI ไปใช้งาน ไม่ใช่กฎว่าเว็บไซต์สามารถเผยแพร่ภาพประเภทใดได้บ้าง

3 Mermaid เรนเดอร์แผนภาพและกราฟจากคำนิยามข้อความในรูปแบบคล้าย Markdown ซึ่งเป็นสิ่งที่ทำให้ผลลัพธ์รีวิวได้และให้ผลลัพธ์ตายตัว

4 web.dev, Optimize Largest Contentful Paint: "เป็นความคิดที่ดี ที่จะตั้งค่า fetchpriority="high" บน element <img> หากคุณคิดว่า มันน่าจะเป็น LCP element ของหน้าเพจ" ใช้อย่างประหยัด และ "อย่า lazy-load ภาพ LCP ของคุณเด็ดขาด เพราะมันจะทำให้เกิดความล่าช้าในการโหลดทรัพยากรโดยไม่จำเป็นเสมอ และจะส่งผลเสียต่อ LCP"

5 Google Search Central, Image metadata (structured data): ImageObject ต้องมี contentUrl บวกกับอย่างน้อยหนึ่งใน creator, creditText, copyrightNotice หรือ license แนะนำให้มี acquireLicensePage และภาพที่มีข้อมูลสิทธิ์ การใช้งานสามารถมีสิทธิ์ได้รับตราสัญลักษณ์ Licensable ใน Google Images

6 Google Search Central, Image sitemaps: image sitemap แจ้งให้ Google ทราบเกี่ยวกับภาพบนเว็บไซต์ รวมถึงภาพที่พบผ่าน JavaScript และรองรับได้ถึง 1,000 ภาพต่อหนึ่ง URL หน้าเพจ

7 นโยบายสแปมของ Google Search — scaled content abuse: การสร้างหลายหน้าเพจเป็นหลักเพื่อบิดเบือนอันดับและให้คุณค่าเล็กน้อยแก่ผู้ใช้ ไม่ว่าจะสร้างขึ้น ผ่านระบบอัตโนมัติ ความพยายามของมนุษย์ หรือทั้งสองอย่างรวมกัน

คำถามที่พบบ่อย

จะสร้างภาพประกอบบทความที่เขียนโดย LLM แบบอัตโนมัติได้อย่างไร?
เพิ่มการเรียกโมเดลหนึ่งครั้งระหว่างขั้นตอนการเขียนกับการเผยแพร่: ให้โมเดลส่งคืน แผนภาพ (visual plan) — หนึ่งรายการต่อหนึ่งตำแหน่งภาพ โดยแต่ละรายการถูกกำหนดประเภทเป็นรูปภาพ กราฟ ไดอะแกรม หรือไม่มีภาพเลย รายการที่เป็นรูปภาพจะมีคำอธิบายฉาก 12-25 คำที่กล้องถ่ายได้จริง ซึ่งคุณส่งไปยัง API ค้นหารูปภาพเชิงความหมาย (GET /api/v1/search/photos ใช้เวลาประมาณ 150 ms) ส่วนรายการที่เป็นกราฟและไดอะแกรมจะถูกส่งไปยังโมเดลที่เขียนโค้ดสำหรับพล็อตกราฟหรือ Mermaid ซึ่งจะถูก render อย่างแน่นอน (deterministic) อย่าใส่ชื่อบทความลงในระบบค้นหารูปภาพโดยตรง เพราะชื่อบทความมักเป็นแนวคิดเชิงนามธรรมที่ไม่มีภาพถ่ายใดสื่อถึงได้
เว็บไซต์เอกสารประกอบควรใช้ภาพหน้าจอหรือภาพสต็อกดี?
สลับค่าเริ่มต้นของ router กลับด้าน: ในเอกสารประกอบผลิตภัณฑ์ ภาพที่ตรงกับความจริงมากที่สุดมักจะเป็น UI ของคุณเองเสมอ สคริปต์ Playwright ที่เปิด build จริง กำหนด viewport ให้แน่นอน และจับภาพสถานะที่ตรงกับสิ่งที่ย่อหน้านั้นอธิบาย จะทำงานใน CI job เดียวกันกับการ build เอกสาร ทำให้ภาพหน้าจอไม่มีทางแสดงเวอร์ชันของอินเทอร์เฟซที่เลิกใช้ไปแล้วได้ ไดอะแกรมจะรับหน้าที่ในหน้าสถาปัตยกรรม ส่วนภาพถ่ายจะปรากฏเฉพาะในหน้าแนวคิดและหน้า landing page เท่านั้น ซึ่งเป็นจุดที่ภาพหน้าจอไม่สามารถสื่ออะไรได้เลย
จะหยุดไปป์ไลน์ AI จากการใส่ตัวเลขผิดในกราฟได้อย่างไร?
ทำให้แผนภาพเป็นการคัดลอกมากกว่าการสร้างขึ้นใหม่: router ควรคัดลอกได้เฉพาะค่าที่ปรากฏอยู่ในร่างบทความแล้วเท่านั้นลงในฟิลด์ data ของรายการกราฟ จากนั้นตรวจสอบด้วยโค้ดก่อน render — สำหรับแต่ละค่า ให้ตรวจว่าข้อความของค่านั้นปรากฏอยู่ในบทความหรือไม่ ถ้าไม่ปรากฏให้แจ้งข้อผิดพลาด เพียงเก้าบรรทัดโค้ด กราฟที่ขัดแย้งกับเนื้อหาของตัวมันเองก็จะไม่มีทางไปถึงผู้อ่านได้
ไปป์ไลน์อัตโนมัติควรใส่มาร์กอัปรูปภาพแบบใดเพื่อ SEO?
สามสิ่ง ล้วนมาจากฟิลด์ที่มีอยู่แล้วในผลลัพธ์การค้นหา ได้แก่ alt ที่บรรยายเนื้อหาโดยสัมพันธ์กับบริบทของพารากราฟนั้น — Google ระบุว่า alt text คือเมทาดาต้ารูปภาพที่สำคัญที่สุด และเตือนไม่ให้ยัดคำหลัก (keyword stuffing) ลงไป จากนั้นคือ width และ height บนทุกรูปภาพ พร้อม fetchpriority="high" บนภาพหลัก (hero) และไม่ใส่ loading="lazy" บนภาพนั้นเด็ดขาด เพราะภาพ LCP ต้องไม่ถูก lazy load และสุดท้ายคือ structured data แบบ ImageObject ที่มี contentUrl, creator, creditText และ license ซึ่งเป็นสิ่งที่ทำให้รูปภาพมีสิทธิ์ได้รับป้าย Licensable ใน Google Images
การสร้างภาพประกอบให้บทความที่เขียนโดย AI ช่วยให้อันดับดีขึ้นหรือไม่?
ไม่ใช่โดยตัวมันเองเพียงอย่างเดียว และควรระบุให้ชัดเจน นโยบายสแปมของ Google นิยาม scaled content abuse ว่าเป็นการสร้างหน้าเว็บจำนวนมากโดยมีเป้าหมายหลักเพื่อควบคุมอันดับโดยให้คุณค่าน้อยแก่ผู้ใช้ ไม่ว่าจะใช้ระบบอัตโนมัติหรือไม่ก็ตาม ภาพประกอบไม่เปลี่ยนแปลงการประเมินนั้น สิ่งที่ไปป์ไลน์ที่ดีมอบให้คือมาตรฐานคุณภาพขั้นต่ำสำหรับหน้าเว็บที่สมควรมีอยู่แล้ว ได้แก่ แหล่งที่มาที่ตรวจสอบได้ alt text ที่เข้าถึงได้ Core Web Vitals ที่ยังดีอยู่แม้ผ่านระบบอัตโนมัติ และกราฟกับภาพหน้าจอที่ไม่คลาดเคลื่อนจากความจริง
ฉันจะรันขั้นตอนการทำภาพประกอบใน CI โดยไม่เสียการควบคุมด้านบรรณาธิการได้อย่างไร?
ตั้งให้ job ทำงานเมื่อมี pull request ที่แก้ไขไฟล์เนื้อหาของคุณ ปล่อยให้มันเขียนภาพและ front-matter แล้วให้มันเปิด pull request แทนที่จะ commit ตรงเข้า branch เลย — pipeline เป็นฝ่ายเสนอ มนุษย์เป็นฝ่ายอนุมัติ และทุกฟิลด์ที่มันเขียนสามารถตรวจสอบความต่าง (diff) ได้ ให้คงสามสิ่งนี้ไว้เป็นตรรกะที่แน่นอนในโค้ดแทนที่จะปล่อยให้โมเดลตัดสินใจ: การกำจัดข้อมูลซ้ำด้วย photo_id, การยืนยันว่าตัวเลขทุกตัวที่ถูกพล็อตปรากฏอยู่ในบทความจริง และการทำให้ล้มเหลวทันทีเมื่อไม่มีภาพถ่ายใดผ่านเกณฑ์คะแนนที่กำหนด ไม่มีภาพหลักยังดีกว่ามีภาพหลักที่ผิด

เลิกตามหาคำค้น แล้วบรรยายสิ่งที่คุณหมายถึงแทน

ค้นหารูปภาพใช้งานได้ฟรี 9M+ รูปตามความหมาย — ทุกภาษา ภายในเวลาไม่ถึง 100 มิลลิวินาที