งานจัดระเบียบไฟล์คือสิ่งที่หลายทีมต้องทำซ้ำจนเสียเวลา โดยเฉพาะเมื่อทุกอย่างกองรวมอยู่ในโฟลเดอร์เดียว ไม่ว่าจะเป็นใบแจ้งหนี้ รายงาน ผลงานดีไซน์ หรือรูปสินค้า บทความนี้ชวนทุกคนมา แยกไฟล์ใน Google Drive แบบอัตโนมัติจากชื่อไฟล์ด้วย ChatGPT + Google Apps Script โดยให้ GPT ช่วยเขียนสคริปต์ที่อ่านชื่อไฟล์แล้วตีความหมวดหมู่ จากนั้นสร้างโฟลเดอร์ย่อยให้อัตโนมัติและย้ายไฟล์เข้าไปทันทีครับ
Step 1: ตั้งโจทย์ให้ ChatGPT ชัดเจน
จุดเริ่มต้นคือโจทย์ที่เราอยาก “แยกไฟล์ใน Google Drive” ตามกฎแบบไหน เช่น ถ้าไฟล์ขึ้นต้นด้วย INV_ ให้ไปโฟลเดอร์ “ใบแจ้งหนี้” หรือถ้าใช้แท็กในวงเล็บอย่าง [Design] ให้แยกเข้าหมวด “Design” หรือถ้างานหลากหลาย ให้ใช้ Regex เพื่อจับแพทเทิร์น เมื่อเราได้โจทย์ครบก็คุยกับ GPT จนเราได้โค้ดที่พร้อมใช้งานครับ
Step 2: เตรียมโฟลเดอร์และ ID ที่ต้องใช้
ให้เลือกโฟลเดอร์ต้นทางที่ไฟล์กองอยู่ และถ้าต้องการให้โฟลเดอร์ย่อยไปอยู่ใต้ปลายทางอีกชั้น ให้เตรียมโฟลเดอร์ปลายทางเพิ่ม จากนั้นให้คัดลอกลิงก์โฟลเดอร์เพื่อดึง Folder ID ซึ่งก็คือส่วนตัวอักษรท้าย ๆ ของ URL เช่น URL คือ https://drive.google.com/drive/folders/1vtt1YFBefZVfKKPETMfDlpJgZwKngIlR ตัว Folder ID ก็คือ 1vtt1YFBefZVfKKPETMfDlpJgZwKngIlR ที่เราไฮไลต์ไว้ ให้เราเก็บไว้ใช้งานกับ APP Script ครับ
Step 3: สร้างโปรเจกต์ Apps Script และวางโค้ด
เปิด https://script.new เพื่อสร้างโปรเจกต์ใหม่ จากนั้นวางโค้ดที่เราได้มาด้านล่าง ปรับค่า CONFIG ให้ตรงกับงาน เช่น SOURCE_FOLDER_ID และกฎที่ต้องการ แล้วบันทึก
กดดูตัวอย่าง Code ที่ได้ ตรงนี้
/** * Google Apps Script: แยกไฟล์อัตโนมัติจาก “ชื่อไฟล์” ไปยังโฟลเดอร์ย่อย * รองรับกฎ: PREFIX_UNDERSCORE, BRACKET_TAG, REGEX_MAP * มี Dry-Run, Log ลงชีต และ Undo ได้ด้วย runId */ const CONFIG = { SOURCE_FOLDER_ID: ‘PUT_SOURCE_FOLDER_ID_HERE’, DESTINATION_ROOT_ID: ”, // เว้นว่าง = สร้างโฟลเดอร์ย่อยใน SOURCE LOG_SHEET_ID: ”, // ใส่ Google Sheet ID ถ้าต้องการบันทึก Log RULE: ‘PREFIX_UNDERSCORE’, // ‘PREFIX_UNDERSCORE’ | ‘BRACKET_TAG’ | ‘REGEX_MAP’ PREFIX_SEPARATOR: ‘_’, REGEX_MAP: [ { pattern: ‘^INV’, target: ‘ใบแจ้งหนี้’ }, { pattern: ‘^Q[UO]T’, target: ‘ใบเสนอราคา’ }, { pattern: ‘^IMG|^PHOTO’, target: ‘ภาพ’ }, { pattern: ‘REPORT|SUMMARY’, target: ‘รายงาน’ }, ], DRY_RUN: false, SKIP_GOOGLE_FILES: false, MAX_PER_RUN: 500 }; function separateFilesByName() { coreSeparate_(false); } function previewSeparation() { coreSeparate_(true); } function undoRun() { const runId = Browser.inputBox(‘ใส่ runId ที่ต้องการย้อนกลับ:’); if (!runId || runId === ‘cancel’) return; coreUndo_(runId); } function coreSeparate_(forceDryRun) { const runId = new Date().toISOString(); const dryRun = forceDryRun ? true : !!CONFIG.DRY_RUN; const src = DriveApp.getFolderById(CONFIG.SOURCE_FOLDER_ID); const root = CONFIG.DESTINATION_ROOT_ID ? DriveApp.getFolderById(CONFIG.DESTINATION_ROOT_ID) : src; const folderCache = {}; const files = src.getFiles(); let processed = 0; const sheet = getLogSheet_(); if (sheet && sheet.getLastRow() === 0) { sheet.appendRow([‘timestamp’,’runId’,’fileId’,’fileName’,’fromFolderId’,’fromFolderName’,’toFolderId’,’toFolderName’,’category’,’action’,’dryRun’]); } while (files.hasNext()) { const file = files.next(); if (CONFIG.SKIP_GOOGLE_FILES && String(file.getMimeType()).startsWith(‘application/vnd.google-apps’)) continue; const baseName = file.getName(); const category = extractCategory_(baseName); const safeCategory = sanitizeFolderName_(category || ‘Uncategorized’); const toFolder = getOrCreateFolder_(root, safeCategory, folderCache); const action = dryRun ? ‘SIMULATED’ : ‘MOVED’; if (!dryRun) { try { file.moveTo(toFolder); } catch (e) { logRow_(sheet, runId, file, src, toFolder, safeCategory, ‘ERROR:’ + e.message, dryRun); continue; } } logRow_(sheet, runId, file, src, toFolder, safeCategory, action, dryRun); processed++; if (processed >= CONFIG.MAX_PER_RUN) break; } Logger.log(‘RunId: %s | Processed: %s | DryRun: %s’, runId, processed, dryRun); } function coreUndo_(runId) { const sheet = getLogSheet_(); if (!sheet) { Browser.msgBox(‘ยังไม่ได้ตั้งค่า LOG_SHEET_ID จึง Undo ไม่ได้’); return; } const data = sheet.getDataRange().getValues(); const header = data.shift(); const map = Object.fromEntries(header.map((h,i)=>[h,i])); let undone = 0; data.forEach((row, idx) => { if (row[map[‘runId’]] !== runId || row[map[‘action’]] !== ‘MOVED’) return; try { const file = DriveApp.getFileById(row[map[‘fileId’]]); const fromFolder = DriveApp.getFolderById(row[map[‘fromFolderId’]]); file.moveTo(fromFolder); sheet.getRange(idx+2, map[‘action’]+1).setValue(‘UNDONE’); undone++; } catch (e) { sheet.getRange(idx+2, map[‘action’]+1).setValue(‘UNDO_ERROR:’+e.message); } }); Browser.msgBox(‘ย้อนกลับสำเร็จ: ‘ + undone + ‘ ไฟล์ สำหรับ runId: ‘ + runId); } function extractCategory_(fileName) { const base = removeExtension_(fileName); if (CONFIG.RULE === ‘BRACKET_TAG’) { const m = base.match(/\[([^\]]+)\]/); if (m) return m[1].trim(); } if (CONFIG.RULE === ‘REGEX_MAP’) { for (const rule of CONFIG.REGEX_MAP) { const re = new RegExp(rule.pattern, ‘i’); if (re.test(base)) return rule.target; } } const sep = CONFIG.PREFIX_SEPARATOR || ‘_’; if (base.includes(sep)) return base.split(sep)[0].trim(); if (base.includes(‘-‘)) return base.split(‘-‘)[0].trim(); const m2 = base.match(/^(\d{2,})/); if (m2) return m2[1]; return ‘Uncategorized’; } function removeExtension_(name) { const i = name.lastIndexOf(‘.’); return i > 0 ? name.slice(0, i) : name; } function sanitizeFolderName_(name) { return String(name).replace(/[\\/:*?”<>|#]+/g, ‘ ‘).trim().slice(0,128) || ‘Uncategorized’; } function getOrCreateFolder_(parent, name, cache) { if (cache[name]) return cache[name]; const it = parent.getFoldersByName(name); const folder = it.hasNext() ? it.next() : parent.createFolder(name); cache[name] = folder; return folder; } function getLogSheet_() { if (!CONFIG.LOG_SHEET_ID) return null; try { return SpreadsheetApp.openById(CONFIG.LOG_SHEET_ID).getSheets()[0]; } catch (e) { Logger.log(‘เปิดชีตไม่สำเร็จ: ‘ + e.message); return null; } } function logRow_(sheet, runId, file, fromFolder, toFolder, category, action, dryRun) { if (!sheet) return; sheet.appendRow([new Date(), runId, file.getId(), file.getName(), fromFolder.getId(), fromFolder.getName(), toFolder.getId(), toFolder.getName(), category, action, dryRun]); }
Step 4: ให้สิทธิ์และรันครั้งแรก
ครั้งแรกที่กดรัน ระบบจะขอสิทธิ์เข้าถึงทั้ง Drive และ Sheets กรณีที่เราใส่ ID ของ Sheet ไปด้วย ให้ยืนยันตามขั้นตอน จากนั้นก็เริ่มกด Run โหมดซ้อมโดยให้สังเกตที่ CONFIG.DRY_RUN ให้เป็น true เพื่อดูผลลัพธ์ก่อน แล้วค่อยเปลี่ยนเป็นการย้ายจริงเมื่อมั่นใจครับ
ก่อนจะไปต่อขออธิบายเพิ่มเติม เพื่อทำความเข้าใจ Dry-Run, Log ลงชีต และ Undo ด้วย runId ว่าคืออะไรครับ
Dry-Run คือโหมดซ้อมที่ไม่ย้ายไฟล์จริง แต่แสดงให้เห็นล่วงหน้าว่าเมื่อย้ายจริงแต่ละไฟล์จะไปอยู่ที่ไหน การเปิดใช้ทำได้สองทาง คือกำหนดค่า CONFIG.DRY_RUN เป็น true แล้วรันฟังก์ชันหลัก หรือ ทดลองเรียกฟังก์ชัน previewSeparation ซึ่งจะบังคับให้เป็นโหมดซ้อมโดยอัตโนมัติ ผลลัพธ์จะถูกบันทึกเป็นสถานะ SIMULATED เพื่อให้ตรวจสอบในภายหลังครับ
Log ลงชีต คือการบันทึกรายการทุกไฟล์ที่ถูกซ้อมหรือย้ายจริงลงใน Google Sheet เดียวกันครับ ซึ่งจะมีข้อมูลเวลา รหัสรอบการรันหรือ runId ชื่อและไอดีของไฟล์ ต้นทางและปลายทางที่เกี่ยวข้อง หมวดหมู่ที่ตีความจากชื่อไฟล์ และสถานะการทำงาน ไม่ว่าจะเป็น SIMULATED, MOVED หรือ UNDONE สิ่งนี้ช่วยให้ตรวจทานย้อนหลังได้ละเอียด และเป็นฐานข้อมูลสำหรับการกู้คืนครับ
Undo ด้วย runId คือความสามารถในการย้อนกลับเฉพาะการย้ายของรอบใดรอบหนึ่ง โดยระบบจะสร้าง runId พร้อมกันทุกครั้งที่รัน เมื่อเรียกฟังก์ชัน undoRun แล้วใส่ runId นั้น ระบบจะอ่าน Log ของรอบดังกล่าวและย้ายไฟล์กลับไปยังโฟลเดอร์เดิม พร้อมเปลี่ยนสถานะเป็น UNDONE ทั้งหมดนี้จะทำงานได้ต่อเมื่อกำหนด LOG_SHEET_ID ไว้ก่อน และไฟล์หรือโฟลเดอร์ที่อ้างอิงยังอยู่ครบและเข้าถึงได้ครับ
Step 5: ตรวจผลลัพธ์ใน Google Drive
หลังการรันแล้ว จะเห็นโฟลเดอร์ย่อยเกิดขึ้นตามหมวดที่สคริปต์ตีความจากชื่อไฟล์ และไฟล์ถูกย้ายเข้าไปอย่างเรียบร้อยครับ หากเริ่มจาก Dry-Run ไม่ได้ย้ายจริง ให้เปิดชีตที่ใช้บันทึก Log เพื่อตรวจว่าไฟล์จะย้ายเข้าหมวดถูกต้องหรือไม่ เมื่อมั่นใจจึงสลับเป็นการย้ายจริงครับ
Step 6: ปรับกฎให้ตรงงานของคุณ
เราสามารถปรับกฎการแยกไฟล์ได้ตามที่เราต้องการ เช่น หากชื่อไฟล์ใช้คำนำหน้าก่อนขีดล่าง เช่น INV_ หรือ IMG_ ให้คงค่า RULE เป็น PREFIX_UNDERSCORE เพื่อใช้คำก่อนตัวคั่นเป็นหมวด แต่ถ้างานที่ต้องการความยืดหยุ่นสูง เช่น แยกตามรหัสลูกค้า เดือน ปี หรือคำ ให้ใช้ REGEX_MAP และกำหนดแพทเทิร์นตามที่เราต้องการ ซึ่งเราสามารถบอกกับ ChatGPT เพื่อให้แก้ Code ได้ครับ
แล้วหากไฟล์มีจำนวนมาก ควรตั้ง MAX_PER_RUN ให้เหมาะสมแล้วรันเป็นรอบ ๆ เพื่อลดโอกาส timeout ครับ และงานที่อยู่ใน Shared Drive จำเป็นต้องมีสิทธิ์ Organize ในปลายทางจึงจะย้ายได้โดยไม่ติดข้อจำกัด และหากไม่ต้องการให้สคริปต์แตะไฟล์ประเภทเอกสารของ Google ให้ตั้งค่า SKIP_GOOGLE_FILES เป็น true เพื่อข้ามไฟล์เหล่านั้นครับ
เมื่อวางกฎให้ชัดและปล่อยให้สคริปต์จัดการตั้งแต่การอ่านชื่อไฟล์ แยกหมวดหมู่ สร้างโฟลเดอร์ย่อย ไปจนถึงการย้ายไฟล์ครับ เพียงเท่านี้งานที่เราเคยต้องลากวางทีละรายการ ก็เปลี่ยนเป็น Workflow ที่ทำซ้ำได้ ปลอดภัย และย้อนกลับได้ด้วย Log และ runId ได้อีกด้วยครับ
ขอบคุณภาพจาก Shutterstock AI Generator Prompt : A tidy digital workspace where Google Drive shows a crowded folder on the left and neatly organized category folders on the right, with an automation flow animating files moving into labeled folders. On another window, Google Apps Script editor with code and a run button. Bright daylight, modern home office, soft 3D or flat, minimal productivity.
และนี่คือ แยกไฟล์ใน Google Drive อัตโนมัติ จากชื่อ ด้วย ChatGPT + Apps Script ถ้าชอบ หรือ สนใจอยากอ่านบทความด้านการใช้ AI แบบนี้อีก ผู้เขียนฝากติดตามด้วยครับหรือ ถ้าใครอยากให้ผู้เขียนนำ AI ตัวไหนมาเล่าให้ฟัง สามารถคอมเมนต์บอกกันได้เลยครับ
สำหรับนักอ่านที่ชอบ และ อยากอ่านบทความเกี่ยวกับการตลาดเพิ่มเติม รวมถึงข่าวสารด้านการตลาดต่าง ๆ สามารถติดตามได้จาก เพจการตลาดวันละตอน รวมไปถึง Twitter Instagram YouTube ของการตลาดวันละตอนได้เลย แล้วพบกันใหม่ในบทความหน้าครับヽ(•‿•)ノ