- Gmail to SQLite เป็นแอปพลิเคชัน Python ที่ซิงก์ข้อความ Gmail ไปยัง ฐานข้อมูล SQLite บนเครื่อง เพื่อนำไปใช้ในการวิเคราะห์และจัดเก็บถาวร
- การทำงานปกติคือ การซิงก์แบบเพิ่มเฉพาะส่วน ที่จะดาวน์โหลดเฉพาะข้อความใหม่ และมีตัวเลือกซิงก์ทั้งหมดเพื่อดาวน์โหลดทุกข้อความและตรวจจับได้ว่ามีการลบหรือไม่
- การดึงข้อความใช้ การประมวลผลแบบขนานหลายเธรด และมีการจัดการข้อผิดพลาดและการปิดโปรแกรม เช่น การลองใหม่อัตโนมัติด้วย exponential backoff และการจัดการ CTRL+C
- การใช้งานต้องมี Python 3.8 ขึ้นไป, Google Cloud Project ที่เปิดใช้ Gmail API และไฟล์ OAuth 2.0
credentials.json
- ข้อมูลที่บันทึกไว้ประกอบด้วยผู้ส่ง ผู้รับ ป้ายกำกับ เนื้อหา ขนาด สถานะการอ่าน สถานะส่งออก สถานะการลบ ฯลฯ ทำให้สามารถวิเคราะห์รูปแบบการใช้งาน Gmail ได้โดยตรงด้วย SQL
เครื่องมือซิงก์ข้อความ Gmail แบบโลคัล
- Gmail to SQLite เป็นแอปพลิเคชัน Python ที่เก็บข้อความ Gmail ลงในฐานข้อมูล SQLite บนเครื่อง
- จุดประสงค์คือทำให้สามารถ วิเคราะห์ และ จัดเก็บถาวร ข้อมูล Gmail ได้
- มีการใส่ type hints ทั่วทั้งโค้ดเบสเพื่อให้มี ความปลอดภัยด้านชนิดข้อมูล
วิธีซิงก์และความเสถียร
- การซิงก์เริ่มต้นทำงานแบบ ซิงก์เพิ่มเฉพาะส่วน และจะดาวน์โหลดเฉพาะข้อความใหม่
- หากใช้ตัวเลือก
--full-sync จะซิงก์ข้อความทั้งหมดและตรวจจับข้อความที่ถูกลบจาก Gmail
- การดึงข้อความทำด้วย การประมวลผลแบบขนานหลายเธรด เพื่อเพิ่มประสิทธิภาพ
- การจัดการข้อผิดพลาดมีการลองใหม่อัตโนมัติและ exponential backoff
- เมื่อกด CTRL+C จะเข้าสู่ขั้นตอนปิดอย่างปลอดภัย
- หยุดรับงานใหม่
- รอให้งานที่กำลังทำอยู่เสร็จ
- บันทึกสถานะความคืบหน้าของงานที่เสร็จแล้ว
- ปิดโปรแกรมตามปกติ
- หากกด CTRL+C อีกครั้งจะออกทันที
การติดตั้งและเงื่อนไขที่ต้องเตรียม
- สภาพแวดล้อมสำหรับใช้งานต้องมี Python 3.8 ขึ้นไป
- ต้องมี Google Cloud Project ที่เปิดใช้ Gmail API
- ต้องมีไฟล์ยืนยันตัวตน OAuth 2.0
credentials.json อยู่ที่รูทของโปรเจ็กต์
- ขั้นตอนติดตั้งคือโคลนรีโพซิทอรีแล้วติดตั้ง dependency ด้วย
uv sync
- การตั้งค่าการยืนยันตัวตนของ Gmail API ทำได้โดยสร้างหรือเลือกโปรเจ็กต์ใน Google Cloud Console, เปิดใช้ Gmail API, จากนั้นสร้าง OAuth 2.0 credentials สำหรับ Desktop application แล้วบันทึกเป็น
credentials.json
วิธีใช้คำสั่ง
- การซิงก์แบบเพิ่มเฉพาะส่วนตามปกติรันได้ดังนี้
python main.py sync --data-dir ./data
# or: uv run main.py sync --data-dir ./data
- การซิงก์ทั้งหมดและตรวจจับการลบใช้
--full-sync
python main.py sync --data-dir ./data --full-sync
- หากต้องการซิงก์เฉพาะข้อความใดข้อความหนึ่ง ให้ใช้
sync-message และ --message-id
python main.py sync-message --data-dir ./data --message-id MESSAGE_ID
- หากต้องการตรวจจับและแสดงเฉพาะข้อความที่ถูกลบ ให้ใช้
sync-deleted-messages
python main.py sync-deleted-messages --data-dir ./data
- สามารถกำหนดจำนวน worker threads ด้วย
--workers โดยค่าปริยายคือจำนวนคอร์ของ CPU
python main.py sync --data-dir ./data --workers 8
- อาร์กิวเมนต์บรรทัดคำสั่งมีดังนี้
command: จำเป็น และต้องเป็นหนึ่งใน sync, sync-message, sync-deleted-messages
--data-dir: จำเป็น คือไดเรกทอรีที่จะเก็บฐานข้อมูล SQLite
--full-sync: ตัวเลือกเพิ่มเติม เพื่อบังคับซิงก์ทั้งหมด
--message-id: จำเป็นสำหรับ sync-message คือ ID ของข้อความที่จะซิงก์
--workers: ตัวเลือกเพิ่มเติม คือจำนวน worker threads
--help: แสดงคำอธิบายคำสั่งและตัวเลือก
สคีมาของ SQLite และตัวอย่างการวิเคราะห์
- ตาราง
messages ในฐานข้อมูล SQLite ที่สร้างขึ้นมีฟิลด์ที่จำเป็นสำหรับการวิเคราะห์ข้อความ Gmail
message_id: ID ข้อความ Gmail แบบไม่ซ้ำกัน
thread_id: ID เธรดของ Gmail
sender: ข้อมูลผู้ส่งแบบ JSON ที่มีชื่อและอีเมล
recipients: JSON ของผู้รับแยกตามประเภท to, cc, bcc
labels: อาร์เรย์ของป้ายกำกับ Gmail
subject: หัวข้อข้อความ
body: เนื้อหาข้อความแบบ plain text
size: ขนาดข้อความเป็นไบต์
timestamp: เวลาของข้อความ
is_read: สถานะการอ่าน
is_outgoing: เป็นข้อความที่ผู้ใช้ส่งหรือไม่
is_deleted: เป็นข้อความที่ถูกลบจาก Gmail หรือไม่
last_indexed: เวลาที่ซิงก์ล่าสุด
- สามารถสรุปจำนวนอีเมลตามผู้ส่งได้
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
GROUP BY sender->>'$.email'
ORDER BY count DESC
- สามารถนับอีเมลที่ยังไม่ได้อ่านตามผู้ส่ง เพื่อดูว่าผู้ส่งรายใดส่งอีเมลที่ไม่น่าสนใจจำนวนมาก
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE is_read = 0
GROUP BY sender->>'$.email'
ORDER BY count DESC
- สามารถใช้
strftime เพื่อสรุปจำนวนอีเมลตามปี เดือน วัน วันในสัปดาห์ หรือชั่วโมง
SELECT strftime('%Y', timestamp) AS period, COUNT(*) AS count
FROM messages
GROUP BY period
ORDER BY count DESC
- สามารถค้นหาเมลที่มี
newsletter หรือ unsubscribe อยู่ในเนื้อหา แล้วจัดกลุ่มจดหมายข่าวตามผู้ส่ง
SELECT sender->>'$.email', COUNT(*) AS count
FROM messages
WHERE body LIKE '%newsletter%' OR body LIKE '%unsubscribe%'
GROUP BY sender->>'$.email'
ORDER BY count DESC
- สามารถดูขนาดรวมของอีเมลตามผู้ส่ง และดูว่าผู้ส่งรายใดส่งอีเมลขนาดใหญ่ในหน่วย MB
SELECT sender->>'$.email', sum(size)/1024/1024 AS size
FROM messages
GROUP BY sender->>'$.email'
ORDER BY size DESC
- สามารถคำนวณจำนวนเมลที่ส่งถึงตัวเองได้จาก
recipients JSON และเงื่อนไขอีเมลใน sender
SELECT count(*)
FROM messages
WHERE EXISTS (
SELECT 1
FROM json_each(messages.recipients->'$.to')
WHERE json_extract(value, '$.email') = 'foo@example.com'
)
AND sender->>'$.email' = 'foo@example.com'
- สามารถดูเรียงลำดับตามขนาดรวมของเมลที่ได้รับจากผู้ส่งแต่ละราย
SELECT sender->>'$.email', sum(size)/1024/1024 as total_size
FROM messages
WHERE is_outgoing=false
GROUP BY sender->>'$.email'
ORDER BY total_size DESC
- ข้อความที่ถูกลบสามารถค้นหาได้ด้วยเงื่อนไข
is_deleted=1
SELECT message_id, subject, timestamp
FROM messages
WHERE is_deleted=1
ORDER BY timestamp DESC
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
สงสัยว่าทำไมถึงแยกบางเฮดเดอร์ออกมาต่างหากในสคีมา
recipients,subject,senderอาจใส่ไว้เป็นฟิลด์ JSON ก็ได้ แต่ก็อาจใส่ทั้งหมดไว้ในheadersเดียว รวมถึงเฮดเดอร์ที่เหลือของข้อความด้วยถ้าเป็นเพราะประสิทธิภาพ ก็ให้
headersเป็น JSON blob เดียว แล้วทำฟิลด์ที่ต้องการเป็น คอลัมน์ที่สร้างขึ้น ได้ เช่นsubjectสร้างด้วยjson_extract("headers", '$.Subject')แล้วทำดัชนีได้โมเดลนี้ทรงพลังเพราะผู้ใช้สามารถเพิ่มคอลัมน์ที่สร้างขึ้นและมีดัชนีสำหรับคิวรีของตัวเองด้วย
ALTER TABLEได้ สถานะ DKIM ก็ทำได้โดยดึง"Dkim-Signature"ออกมาทำเป็นคอลัมน์และดัชนี จากนั้นGROUP BYCREATE INDEX subjectidx ON messages(json_extract(headers, '$.Subject'))แล้วตำแหน่งที่อ้างถึงนิพจน์นั้นก็จะใช้ดัชนีได้หลังจากสร้างดัชนีแบบนี้แล้ว การสร้าง
VIEWที่ใช้นิพจน์นั้นมีประโยชน์กว่าการALTERตารางหลักเพื่อเพิ่มคอลัมน์ที่สร้างขึ้นโดยทั่วไปผมชอบแยกคอลัมน์ที่จะถูกใช้อย่างต่อเนื่องออกมาต่างหากมากกว่า ยิ่งถ้าเป็นสิ่งที่เสถียรอย่างเฮดเดอร์อีเมลก็ยิ่งใช่ และแม้คอลัมน์
headersจะทำให้การเปลี่ยนสคีมาง่ายขึ้นเล็กน้อย แต่ก็เหมือนย้ายความเจ็บปวดตอนเขียนไปเป็นความเจ็บปวดตอนอ่าน และยังเหลือช่องให้ล้มเหลวแบบเงียบ ๆ ได้ผ่านไปสักสองเดือน พอเห็นว่าฟิลด์ไหนจำเป็นจริง ๆ ก็เติมจาก JSON แล้วให้ API คอยอัปเดตให้เป็นปัจจุบันต่อไป หรือไม่ก็สร้างวิว วิธีนี้ช่วยหลีกเลี่ยงปัญหาการเติบโตจากแนวคิด “ก็ใส่ทั้งหมดลง MongoDB ไปเลย” หรือ “ก็วางไว้ใน file system ไปเลย” ได้ค่อนข้างดี และต้นทุนก็ไม่มาก
dkimเป็นNOT NULLเลยสงสัยว่าถ้าข้อความอีเมลไม่มีเฮดเดอร์Dkim-Signatureจะเกิดอะไรขึ้นเมื่อหลายปีก่อนเคยทำเครื่องมือ แสดงภาพอีเมลจำนวนมาก แบบ Gmail: https://github.com/terhechte/postsack
สงสัยว่ามีตัวเลือกดูตามขนาดด้วยไหม อยากดูว่าผู้ส่งคนไหนใช้พื้นที่เก็บข้อมูลของผมมากที่สุด และใบรับรอง SSL ของเว็บไซต์หมดอายุแล้ว
ในทางกลับกัน ไฟล์แคช ของ qdirstat สร้างได้ง่าย จึงใช้แสดงภาพหลายสิ่งที่หน้าตาเหมือนไฟล์ได้
ตอนนี้น่าเสียดายมากที่แม้แต่รหัสผ่านเฉพาะแอปก็ใช้ล็อกอินไม่ได้แล้ว ต้องสร้าง OAuth client และผ่านโฟลว์ OAuth ทั้งที่เป็นอีเมลของผมเอง แต่ Google กลับพรากมาตรฐานเปิดสำหรับให้ผมเข้าถึงมันไป
โดยเฉพาะอย่างยิ่ง ผมได้รับข้อมูลมากขึ้นเรื่อย ๆ ว่าอีเมลฟรีแลนซ์ของผมไปตกอยู่ในสแปมในระบบของผู้รับ แต่ก็ยังมืดแปดด้านว่าจะเลิกนิสัยที่คุ้นเคยกับระบบนิเวศของ Google ได้อย่างไร
เมื่อเร็ว ๆ นี้ผมพยายามรวม Gmail เข้ากับแอปของผม https://github.com/rumca-js/Django-link-archive แต่เสียเวลาไปมากเกินไป และตัดสินใจว่าการรองรับ Gmail ไม่คุ้มค่า
Gmail to SQLite อธิบายการตั้งค่าข้อมูลรับรองไว้ 6 ขั้นตอน แต่ในกรณีของผมไม่ใช่แบบนั้น หลังจาก 6 ขั้นตอนแล้ว Google ยังบอกว่าแอปยังไม่ได้เผยแพร่ ต้องเผยแพร่ก่อน และบอกว่าถ้าไม่ใช่ผู้ใช้ Workspace ก็ปล่อยเป็นแอปภายในไม่ได้ พอเปลี่ยนเป็นแอปภายนอกก็บอกว่าใช้ไม่ได้ก่อนผ่านการตรวจสอบ
ในกระบวนการตรวจสอบยังเรียกร้องโดเมน ที่อยู่ รายละเอียดอื่น ๆ เหตุผลประกอบการขอ scope และแม้แต่วิดีโออธิบายวิธีใช้แอป แล้วยังบอกว่าต้องใช้เวลาเพื่อตรวจสอบข้อมูลที่ส่งไป ทั้งหมดเหมือนเขาวงกตการตั้งค่า และการให้ผู้ใช้ต้องกระโดดข้ามอุปสรรคที่ Google บังคับนั้นมากเกินไป
อยากรู้ว่าซอฟต์แวร์ สำรองข้อมูล Gmail แบบโอเพนซอร์สที่ดีที่สุดที่มีอยู่ตอนนี้คืออะไร รวมถึงการเก็บไฟล์แนบด้วย มีใครเคยตั้งค่าอะไรแบบนี้บ้างไหม
อนึ่ง ยังมี https://www.mailstore.com/en/products/mailstore-home/ ด้วย ไม่ใช่โอเพนซอร์ส แต่เป็น GUI ที่มีดัชนี จึงเหมาะกับการค้นหาเมลในเครื่อง และ resume ได้เฉพาะตอนสำรองข้อมูล ทำให้การกู้คืนข้อมูลขนาดใหญ่มักล้มเหลว
ผมตั้งการแจ้งเตือนให้รันสิ่งนี้ทุก ๆ สองสามเดือน แล้วอัปเดตแบ็กอัปในเครื่อง เท่าที่จำได้จะดาวน์โหลดมาเป็นไฟล์ mbox ที่บีบอัดด้วย gzip
ผมคิดว่าสิ่งนี้ควรชื่อประมาณ “IMAP to SQLite” มากกว่า “Gmail to SQLite” ไม่ใช่หรือ ไม่เข้าใจว่าทำไมต้องผูกกับผู้ให้บริการอีเมลเจ้าเดียวโดยเฉพาะ
IMAP ยากกว่ามาก ช้ากว่ามาก และยังติดข้อจำกัดแบนด์วิดท์ของ Google ด้วย
ไม่รู้ว่าอยู่ในสตอเรจที่เย็นเกินไปจน timeout หรือเปล่า ดังนั้นจึงเข้าใจได้ว่าการใช้ API แบบ proprietary ของ Google อาจทำงานได้ดีกว่า
ทุกวันนี้ Google Takeout มี mbox รวมมาให้ ทำงานได้ถูกต้อง และค่อนข้างเร็ว แต่ไม่ได้อัปเดตต่อเนื่อง สุดท้ายผมย้ายไปใช้ Infomaniak ซึ่งเป็นผู้ให้บริการเมลรายอื่น และรู้สึกขอบคุณตัวเองในอดีตที่เคยใช้ โดเมนอีเมล ของตัวเองไว้
ถ้าเปิดใช้ การค้นหาแบบเต็มข้อความ ได้ด้วยก็คงดี
เมื่อวานผมก็ทำของแบบเดียวกัน เพราะอยาก แสดงรายชื่ออีเมลผู้รับตามโดเมน โค้ดเละเทะ แต่ดูได้ที่นี่: https://github.com/hugoferreira/gmail-sqlite-db
ทำให้นึกถึง Archiveopteryx เซิร์ฟเวอร์ IMAP ที่ใช้ PostgreSQL นิดหน่อย: https://github.com/aox/aox
สคีมาของ AOX ดูดีเสมอ แต่ไม่เคยได้ลองใช้งานจริงอย่างจริงจัง กรณีใช้งานหลักของผมคือการวิเคราะห์และค้นหาเมล ไม่ใช่เซิร์ฟเวอร์ IMAP ที่ใช้เป็น daily driver
อยากรู้ว่าตรงนี้ค่าแบนด์วิดท์คิดยังไง ในฐานะคนที่มี บัญชี Gmail มากกว่า 40GB อยากรู้ว่าถ้าโอนด้วยเครื่องมือนี้จะโดนคิดเงินไหม
แก้ไม่ยาก เพราะดูเหมือน Google Takeout จะฟรี ก็แค่ดาวน์โหลดมาก่อนแล้วค่อย parse ไฟล์ ถึงอย่างนั้นในแง่ของการเริ่มใช้งานทันที เครื่องมือนี้น่าจะเร็วกว่า