EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Backup / กู้รูปเว็บเก่าที่หายไป ด้วย Wayback Machine CDX API + Python
Backup

กู้รูปเว็บเก่าที่หายไป ด้วย Wayback Machine CDX API + Python

โดย Ton-Or ·7 ก.ค. 2026·อ่าน 9 นาที
กู้รูปเว็บเก่าที่หายไป ด้วย Wayback Machine CDX API + Python

เว็บที่เจ้าของเสียชีวิตไปแล้ว ครอบครัวอยากคงไว้เป็นที่ระลึกตามสภาพเดิม แต่รูปที่ hotlink จากเว็บอื่นกลายเป็นกรอบแตกทั้งเว็บ — archive.org มี API ให้กู้กลับมาได้ ฟรี และได้ผลเกินคาด

พอดีมีเว็บเก่าอายุสิบกว่าปีอยู่เว็บหนึ่งครับ คนทำเว็บเสียชีวิตไปแล้ว ครอบครัวอยากคงเว็บไว้เป็นที่ระลึกตามสภาพเดิมทุกอย่าง ปัญหาคือรูปในเว็บจำนวนมากเจ้าของเคย hotlink มาจากเว็บอื่น — Facebook CDN บ้าง เว็บฝากรูปที่ปิดตัวไปแล้วบ้าง พอต้นทางตาย รูปเลยกลายเป็นกรอบแตกทั้งเว็บ

จะแก้มือด้วยการไล่หารูปทีละรูปไม่ไหวแน่ (ผมนับได้สามร้อยกว่ารูป) เลยเขียนสคริปต์ให้ archive.org ช่วย — สคริปต์ในบทความนี้คือตัวที่ใช้จริง แก้แค่ path กับชื่อโดเมนเป็นค่าสมมติ

หลักการ: CDX API

Wayback Machine มี API ชื่อ CDX ไว้ query ว่า "โดเมนนี้เคยถูก archive อะไรไว้บ้าง" ตัวอย่าง:

http://web.archive.org/cdx/search/cdx?url=oldsite.com&matchType=domain
  &filter=statuscode:200
  &filter=mimetype:image/(jpeg|png|gif)
  &collapse=urlkey
  &output=json&limit=5000
  &fl=timestamp,original
  • matchType=domain เอาทั้งโดเมนรวม subdomain
  • filter=mimetype:image/... เอาเฉพาะรูป
  • collapse=urlkey URL ซ้ำเอา record เดียว
  • ได้ JSON เป็นคู่ [timestamp, url เดิม]

แล้วเวลาดึงไฟล์จริง ใช้ URL รูปแบบนี้:

http://web.archive.org/web/<timestamp>id_/<url เดิม>
id_ ต่อท้าย timestamp สำคัญมาก

แปลว่า "เอาไฟล์ดิบตามต้นฉบับ" — ถ้าไม่ใส่ archive.org จะฉีด toolbar/rewrite HTML มาให้ ซึ่งกับไฟล์รูปคือได้ไฟล์เสีย

แนวคิด 3 ขั้น — ไม่แตะ HTML เลย

ขั้นตอนกู้รูปscan HTMLหารูปที่อ้างแต่ไฟล์ไม่มี→CDX queryรายการรูปที่ archive ไว้→จับคู่ basenameโหลดวาง path เดิม

หัวใจคือโหลดรูปมาวางที่ path เดิมที่ HTML อ้าง — โครงเว็บเดิมทุกตัวอักษร ไม่ต้อง rewrite อะไรทั้งสิ้น

path ที่ HTML อ้างไฟล์ไม่มีในเครื่องURL เดิมจาก CDXhotlink จาก CDN ต้นทางจับคู่ด้วย basenameชื่อไฟล์ตรงกันโหลดไฟล์ดิบ (id_)วางที่ path เดิมผลในเคสจริง: อ้างรูปหาย 378 pathกู้ได้ 35 รูปที่เหลือ archive.org ไม่เคยเก็บไว้
path ที่ HTML อ้างกับ URL เดิมใน CDX ต่างกัน แต่ชื่อไฟล์มักเดิม เลยจับคู่ด้วย basename แล้วโหลดผ่าน id_ มาวางที่ path เดิม ในเคสจริงกู้ได้ 35 จาก 378 path

สคริปต์เต็ม

# -*- coding: utf-8 -*-
# กู้รูปที่หายจาก archive.org Wayback -> วางที่ path เดิมที่ HTML อ้าง (ไม่แตะ HTML)
import os, re, glob, json, urllib.parse, urllib.request, sys, time
DR = '/home/webuser/domains/oldsite.com/public_html'

# 1) รวบรวม missing refs (path ที่ HTML อ้างแต่ไฟล์ไม่มี)
missing = {}   # rel_path -> basename(lower)
for f in glob.glob(DR + '/**/*.htm*', recursive=True):
    try:
        s = open(f, encoding='latin-1', errors='ignore').read()
    except Exception:
        continue
    for m in re.findall(r'src=["\']([^"\']+\.(?:jpg|jpeg|png|gif))["\']', s, re.I):
        if m.startswith(('http://', 'https://', '//')):
            continue
        rel = urllib.parse.unquote(m.split('?')[0]).lstrip('/')
        p1 = os.path.normpath(os.path.join(os.path.dirname(f), rel))
        p2 = os.path.normpath(os.path.join(DR, rel))
        if os.path.exists(p1) or os.path.exists(p2):
            continue
        missing[rel] = os.path.basename(rel).lower()
print(f'missing refs: {len(missing)}')

# 2) ดึงรายการรูปที่ archive ไว้ทั้งโดเมน
def fetch(url, timeout=60):
    req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 recovery'})
    return urllib.request.urlopen(req, timeout=timeout).read()

cdx = ("http://web.archive.org/cdx/search/cdx?url=oldsite.com&matchType=domain"
       "&filter=statuscode:200&filter=mimetype:image/(jpeg|png|gif)&collapse=urlkey"
       "&output=json&limit=5000&fl=timestamp,original")
rows = json.loads(fetch(cdx))
rows = rows[1:] if rows and rows[0][0] == 'timestamp' else rows
arch = {}   # basename(lower) -> (timestamp, original) เอา snapshot ล่าสุด
for ts, orig in rows:
    bn = urllib.parse.unquote(orig.split('?')[0]).rsplit('/', 1)[-1].lower()
    if bn:
        arch[bn] = (ts, orig)
print(f'archived images: {len(arch)}')

# 3) จับคู่ด้วย basename แล้วโหลดมาวาง path เดิม
rec = lost = fail = 0
for rel, bn in missing.items():
    if bn not in arch:
        lost += 1
        continue
    ts, orig = arch[bn]
    way = f'http://web.archive.org/web/{ts}id_/{orig}'
    dest = os.path.normpath(os.path.join(DR, rel))
    if not dest.startswith(DR) or os.path.exists(dest):
        continue
    try:
        data = fetch(way, timeout=45)
        if len(data) < 200:       # กันได้หน้า error มาแทนรูป
            fail += 1
            continue
        os.makedirs(os.path.dirname(dest), exist_ok=True)
        open(dest, 'wb').write(data)
        os.chmod(dest, 0o644)
        rec += 1
        time.sleep(0.15)          # อย่ายิงรัว เกรงใจ archive.org
    except Exception:
        fail += 1
print(f'RESULT: recovered={rec} lost={lost} fail={fail}')

จุดที่อยากชี้ในสคริปต์

  • จับคู่ด้วย basename เพราะรูป hotlink URL ต้นทาง (เช่น CDN) กับ path ที่เว็บอ้างไม่เหมือนกัน แต่ชื่อไฟล์มักเดิม
  • เช็ค len(data) < 200 — บางทีได้หน้า HTML error แทนรูป ไฟล์รูปจริงไม่มีทางเล็กขนาดนั้น
  • dest.startswith(DR) กัน path traversal จากชื่อไฟล์แปลกๆ ใน HTML เก่า
  • time.sleep — archive.org ให้ใช้ฟรี อย่าทำตัวเป็น DDoS ใส่เขา

ผลจริง

เคสของผม: อ้างรูปหาย 378 path กู้กลับมาได้ 35 รูป ที่เหลือคือ archive.org ไม่เคยเก็บไว้ (รูปพวก Facebook CDN ส่วนใหญ่ crawler เข้าไม่ถึงตั้งแต่แรก)

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

บทเรียนอีกข้อ

อยากให้รูปอยู่กับเว็บไปนานๆ ดูดมาเก็บไว้ที่โฮสตัวเองตั้งแต่แรก อย่า hotlink — ต้นทางตายเมื่อไหร่รูปหายเมื่อนั้น

ใครมีเว็บเก่าที่รูปแตกๆ อยู่ ลองเอาสคริปต์ไปปรับใช้ได้ตามสะดวกครับ ของฟรีและได้ผลเกินคาด ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

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

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

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

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