3 คะแนน โดย GN⁺ 2023-09-16 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • eurofxref-hist.zip ของ ECB เป็นเพียงชุดไฟล์ CSV ของอัตราแลกเปลี่ยน แต่สามารถใช้แค่ curl, gunzip และ sqlite3 เพื่อหาวันที่ดอลลาร์แข็งค่าที่สุดเมื่อเทียบกับยูโรได้ทันที คือ 2000-10-26
  • ต้นฉบับอยู่ในรูปแบบ wide format โดยมีคอลัมน์สกุลเงินต่อท้าย Date ทำให้วิเคราะห์ได้ไม่สะดวก และต้องจัดรูปใหม่เป็น long format ในรูป Date,Currency,Rate
  • เนื่องจากมี trailing comma ที่ท้ายแต่ละบรรทัด ตัวแยก CSV จะอ่านคอลัมน์ว่างเพิ่มเข้ามา และใน Pandas ต้องใช้ .iloc[:,:-1] เพื่อลบคอลัมน์สุดท้ายออกก่อน จึงจะได้ผลลัพธ์ melt ที่สะอาด
  • CSV ที่จัดรูปแล้วสามารถอัปโหลดไปยัง csvbase ด้วย HTTP PUT แล้วนำไปต่อกับเครื่องมืออย่าง gnuplot, DuckDB และ sqlite3 เพื่อทำกราฟ คำนวณค่าเฉลี่ยเคลื่อนที่ และโหลด CSV ผ่าน HTTP ได้
  • ข้อมูลสาธารณะที่ดาวน์โหลดได้โดยไม่ต้องเจรจาเรื่องการเข้าถึง การยืนยันตัวตน โควตา หรืออ่านเอกสาร API ที่ซับซ้อน สามารถทำงานได้เหมือน open API และแม้แต่ไฟล์ zip ธรรมดาก็เป็นฐานสำหรับการแลกเปลี่ยนข้อมูลของแอปการเงินได้

สอบถามอัตราแลกเปลี่ยนด้วยไฟล์ zip เพียงไฟล์เดียว

  • ECB เผยแพร่ข้อมูลอัตราแลกเปลี่ยนย้อนหลังระหว่างยูโรกับสกุลเงินอื่นในรูป ไฟล์ zip ทางการ
  • ไปป์ไลน์ด้านล่างจะดาวน์โหลดข้อมูล แตกไฟล์ แล้วให้ SQLite แบบอินเมมโมรีอ่าน CSV จากนั้นจัดเรียงตามค่า USD และคืนวันที่แรกออกมา
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| gunzip \
| sqlite3 ':memory:' '.import /dev/stdin stdin' \
  "select Date from stdin order by USD asc limit 1;"
  • ผลลัพธ์คือ 2000-10-26
  • curl -s ช่วยลดข้อความรบกวนจาก standard error และ gunzip ใช้คลายบีบอัดไฟล์ zip
  • บน Mac OS หรือ BSD นั้น gunzip สาย BSD ไม่รองรับไฟล์ zip จึงต้องใช้ bsdtar -xOf - แทน
  • sqlite3 ':memory:' ใช้ฐานข้อมูลในหน่วยความจำ และ .import /dev/stdin stdin จะนำข้อมูลจาก standard input เข้าเป็นตาราง stdin

จัดรูป CSV และใช้ Pandas melt

  • ส่วนหัวของ CSV ต้นฉบับเป็น wide format เช่น Date,USD,JPY,BGN,CYP,CZK,DKK,... โดยมีคอลัมน์วันที่ตามด้วยคอลัมน์ของแต่ละสกุลเงิน
  • หากต้องการกรองและสรุปข้อมูล รูปแบบ long format อย่าง Date,Currency,Rate จะจัดการได้ง่ายกว่า
  • การแปลงจาก wide format เป็น long format มักเรียกว่า melt
  • ฐานข้อมูล SQL ส่วนใหญ่ไม่มีโอเปอเรชันที่ตรงกับ melt จึงทำให้ Pandas มีประโยชน์มากในการจัดรูปข้อมูล
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).melt("Date").to_csv(sys.stdout, index=False)'
  • ไฟล์ของ ECB มี trailing comma อยู่ท้ายทุกบรรทัด ทำให้ตัวแยก CSV อ่านคอลัมน์ว่างเพิ่มเข้ามาที่ท้าย
  • คอลัมน์ว่างนี้ทำให้เกิดแถวที่ไม่มีประโยชน์ตอนท้ายของผลลัพธ์ melt จึงจำเป็นต้องลบออก
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)'
  • .iloc[:, :-1] คือการเลือกทุกแถวและทุกคอลัมน์ยกเว้นคอลัมน์สุดท้าย
  • แม้ข้อมูลอัตราแลกเปลี่ยนของ ECB จะต้องจัดรูปก่อนใช้งาน แต่ก็สามารถใช้งานได้ทันทีโดยไม่ต้องเจรจาเรื่องการเข้าถึง การชำระเงิน คุยกับฝ่ายขาย ส่งอีเมล ชื่อบริษัท หรือตำแหน่งงาน รวมถึงไม่ต้องเจอกับโควตา การยืนยันตัวตน หรือการอ่านเอกสาร API
  • เพราะต้องแค่จัดการรูปแบบพื้นฐานกับปัญหาเรื่องโครงสร้างข้อมูลเท่านั้น จึงถือว่าค่อนข้างดีเมื่อเทียบกับการเผยแพร่ข้อมูลสาธารณะอื่น ๆ

อัปโหลดข้อมูลที่จัดรูปแล้วไปยัง csvbase

  • CSV ที่จัดรูปแล้วสามารถอัปโหลดไปยัง ตาราง csvbase เพื่อหลีกเลี่ยงการจัดรูปซ้ำ ๆ
  • เพียงต่อ curl อีกตัวไว้ท้ายไปป์ไลน์เดิม ก็สามารถอัปโหลด CSV ด้วย HTTP PUT ได้
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \
gunzip | \
python3 -c 'import sys, pandas as pd
pd.read_csv(sys.stdin).iloc[:, :-1].melt("Date")\
.to_csv(sys.stdout, index=False)' | \
curl -n --upload-file - \
'https://csvbase.com/calpaterson/eurofxref-hist?public=yes'
  • --upload-file - จะอัปโหลดข้อมูลที่รับมาจาก standard input ไปยัง URL ที่ระบุ
  • หากยังไม่มีตารางใน csvbase ระบบจะสร้างใหม่ และถ้ามีอยู่แล้วก็จะนำข้อมูลใส่เข้าไปในตารางนั้น
  • -n ใช้ ข้อมูลยืนยันตัวตน จาก ~/.netrc

วาดกราฟอัตราแลกเปลี่ยนด้วย gnuplot

  • ตาราง csvbase ที่จัดรูปแล้วสามารถดึง CSV ด้วย curl แล้วต่อกับ grep, cut และ gnuplot ได้
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
grep USD | \
cut -d, -f 2,4 | \
gnuplot -e "set datafile separator ','; set term dumb; \
plot '-' using 1:2 with lines title 'usd'"
  • คำสั่งนี้วาดจุดข้อมูลมากกว่า 6,000 จุดให้อ่านพอได้ในรูป ASCII art บน เทอร์มินัลตัวอักษรขนาด 80x25
  • การตั้งค่า gnuplot ถูกปรับให้รับอินพุตแบบ CSV และวาดกราฟเส้นของวันที่กับอัตราแลกเปลี่ยน
    • set datafile separator ',': ระบุว่าอินพุตเป็น CSV
    • set term dumb: วาดเป็น ASCII art
    • plot -: รับข้อมูลจาก standard input
    • using 1:2 with lines: ใช้คอลัมน์ 1 และ 2 คือวันที่และอัตราแลกเปลี่ยนมาวาดเส้น
    • title 'usd': ตั้งชื่อเส้นเป็น usd
  • ยังสามารถส่งออกเป็นภาพ SVG ได้ และหากต้องการให้ดูเป็นข้อมูลอนุกรมเวลา ควรกำหนดให้แกน x เป็นเวลา พร้อมตั้งค่ารูปแบบเวลาและการหมุนฉลากบนแกน x
  • เพื่อใช้ซ้ำ สามารถรวมไว้เป็นฟังก์ชัน Bash ชื่อ plot_timeseries_to_svg ได้

คำนวณค่าเฉลี่ยเคลื่อนที่ด้วย DuckDB

  • หากต้องการดูเส้นแนวโน้มของอัตราแลกเปลี่ยน USD สามารถใช้ DuckDB คำนวณ ค่าเฉลี่ยเคลื่อนที่ ได้
curl -s https://csvbase.com/calpaterson/eurofxref-hist | \
duckdb -csv -c "select Date, avg(value) over \
(order by date rows between 100 preceding and current row) \
as rolling from read_csv_auto('/dev/stdin')
where variable = 'USD';" | \
plot_timeseries_to_svg rolling
  • หากไม่มี duckdb ก็แปลงคิวรีเดียวกันให้ใช้กับ sqlite3 ได้ไม่ยาก
  • DuckDB คล้าย SQLite แต่เป็นแบบ column-oriented ไม่ใช่แบบ row-oriented
  • DuckDB สามารถอ่าน CSV จาก HTTP ได้โดยตรงและสร้างเป็นไฟล์ตารางได้
CREATE TABLE eurofxref_hist AS SELECT * FROM
read_csv_auto("https://csvbase.com/calpaterson/eurofxref-hist";);
  • DuckDB อนุมานชนิดข้อมูลได้ค่อนข้างดี และตรวจจับขนาดเทอร์มินัลเพื่อย่อการแสดงผลลัพธ์ขนาดใหญ่โดยอัตโนมัติ
  • สำหรับคิวรีขนาดใหญ่ยังแสดงแถบความคืบหน้าได้ และสามารถส่งออกเป็นตาราง Markdown ได้ด้วย

วิธีที่ข้อมูลสาธารณะทำงานเหมือน open API

  • เพียงมี CSV ในไฟล์ zip และเครื่องมือที่ติดตั้งได้ง่ายด้วย brew install หรือ apt install ก็ทำงานได้หลายอย่างแล้ว
  • eurofxref-hist.zip เป็นรูปแบบที่เรียบง่ายมากสำหรับโปรโตคอลแลกเปลี่ยนข้อมูลระหว่างองค์กร
  • แม้ไฟล์ zip นี้จะดูเล็ก แต่มีแอปการเงินจำนวนมากใช้งานทุกวัน
  • เหตุผลที่ ECB ยังปล่อย trailing comma ไว้ อาจเป็นเพราะถ้าลบออกตอนนี้ โค้ดจำนวนมากอาจพังได้
  • เมื่อข้อมูลสาธารณะถูกเผยแพร่อย่างเข้าถึงง่ายมาก มันก็ทำหน้าที่เป็น open API ได้เช่นกัน
  • หาก API จำนวนมากมีลักษณะใกล้เคียงกับการแลกเปลี่ยนข้อมูลมากกว่าการเรียกใช้ฟังก์ชันระยะไกล มันก็แทบไม่ต่างในเชิงการใช้งานจากข้อมูลสาธารณะที่ดาวน์โหลดได้ง่ายนัก

URL แบบเรียบง่ายและ HTTP verbs ของ csvbase

  • csvbase ใช้หนึ่ง URL ต่อหนึ่งตาราง
https://csvbase.com/<username>/<table_name>;
  • ตัวอย่างมีดังนี้
https://csvbase.com/calpaterson/eurofxref-hist
  • แต่ละ URL รองรับ HTTP verbs หลัก 4 แบบ
    • GET: รับ CSV และในเบราว์เซอร์สามารถรับเป็นหน้าเว็บได้
    • PUT: สร้างตารางใหม่ด้วย CSV ใหม่ หรือเขียนทับตารางเดิม
    • POST: เพิ่มแถว CSV จำนวนมากเข้าไปในตารางเดิม
    • DELETE: ลบตารางนั้น
  • การยืนยันตัวตนใช้ HTTP Basic Auth

หมายเหตุเกี่ยวกับการจัดรูปข้อมูลและไปป์ไลน์

  • ในบรรดาฐานข้อมูล SQL ฟังก์ชันที่เทียบได้กับ melt มี UNPIVOT ของ Snowflake และ PIVOT/UNPIVOT ของ MS SQL Server
  • หนึ่งในเหตุผลสำคัญที่ R และ Pandas ถูกใช้อย่างแพร่หลายคือมีความสามารถด้าน การจัดรูปข้อมูล ที่แข็งแรง
  • ไปป์ไลน์ Bash ทำงานแบบหลายโปรเซส โดยแต่ละโปรแกรมจะรันแบบขนานกันในโปรเซสแยกอิสระ
  • ในเดือนตุลาคม 2000 อัตราแลกเปลี่ยนดอลลาร์เทียบยูโรอยู่ที่ 0.8252 ซึ่งหมายความว่าดอลลาร์ 1 หน่วยสามารถซื้อยูโรได้ 1.21 หน่วย
  • ยูโรเริ่มใช้ในเดือนมกราคม 1999 โดยยังไม่มีธนบัตรและเหรียญในตอนแรก ช่วงแรกมีอยู่เฉพาะในระบบธนาคาร และธนบัตรกับเหรียญจึงตามมาภายหลัง

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

 
GN⁺ 2023-09-16
ความคิดเห็นบน Hacker News
  • ผมจำไฟล์นี้ได้ ตอนทำงานที่ ECB เมื่อราว 15 ปีก่อน
    ไฟล์นี้เป็นไฟล์ที่ถูกดาวน์โหลดจากเว็บไซต์ ECB มากที่สุดอย่างท่วมท้น และผู้คนจำนวนมากรวมถึงสถาบันการเงินต่าง ๆ จะดาวน์โหลดทุกวันเพื่อเอาไปอัปเดตระบบของตัวเอง
    ในแต่ละวัน ช่วงไม่กี่นาทีหลังเวลาประกาศที่กำหนดไว้ ทราฟฟิกจะพุ่งขึ้นมาก และการทำให้เมื่อแตกไฟล์แล้วได้เป็นไฟล์ CSV ธรรมดา ๆ นั้นเป็นการตัดสินใจโดยตั้งใจ
    ด้วยเหตุนี้จึงสามารถให้บริการไฟล์ได้อย่างเสถียรและรวดเร็วโดยใช้ทรัพยากรน้อย และทีมเล็ก ๆ ที่ดูแลเว็บไซต์สาธารณะของ ECB ในตอนนั้นก็น่าจะภูมิใจกับการตัดสินใจเชิงเทคนิคที่ให้บริการข้อมูลนี้ด้วยไฟล์สแตติกเพียงไฟล์เดียวได้มาก

    • วิธีแบบนี้ค้ำจุนการแลกเปลี่ยนข้อมูลจำนวนมากอยู่ แต่คนที่ไม่เคยทำงานกับระบบเก่า ๆ มักไม่ค่อยรู้
      มันไม่หวือหวา และไม่มีเฟรมเวิร์ก
      เมื่อราว 15 ปีก่อน ผมเคยดูแลการแลกเปลี่ยนข้อมูลระหว่างระบบบันทึกข้อมูลผลิตภัณฑ์กับระบบย่อย/ระบบคู่ขนานที่เหลือมาจากการควบรวมและซื้อกิจการ ในบริษัทขนาดใหญ่เก่าแก่แห่งหนึ่งที่ทุกคนน่าจะเคยซื้อผลิตภัณฑ์ของเขา ส่วนใหญ่เป็นการนำเข้า/ส่งออกจำนวนมากผ่านไฟล์ความกว้างคงที่หรือไฟล์มีตัวคั่นที่ส่งกันทางเซิร์ฟเวอร์ SFTP
      ตอนนั้นผลิตภัณฑ์นั้นมีอายุ 15 ปีแล้ว และมีแหล่งข้อมูลหรือการส่งออกแบบนั้นราว 20–30 รายการวิ่งไปมา แต่ก็ทำงานได้ดีมาก
      มีความเป็นไปได้สูงว่ายังใช้อยู่จนถึงตอนนี้โดยแทบไม่เปลี่ยนแปลง และตอนนั้นฝั่งฟรอนต์เอนด์กำลังถูกเขียนใหม่จากของเดิมที่เป็น Smalltalk
    • ผมก็รู้จักไฟล์นี้เหมือนกัน และเป็นหนึ่งในคนพวกนั้นด้วย
      ในบรรดาแหล่งข้อมูลที่เราใช้ มันเป็นตัวที่จัดการได้ง่ายที่สุด
    • พอนึกถึงด่านที่ต้องผ่านถ้าในบริษัทของผมจะอัปโหลดข้อมูลที่เปลี่ยนแปลงเป็น CSV/ZIP แบบสแตติกแล้วก็น่าทึ่งมาก
      สถาปนิกคงบอกว่า ZIP ไม่ใช่ฟอร์แมตที่ตรงกับสเปกของวัตถุประสงค์นี้ ฝ่ายคอมพลายแอนซ์คงบอกว่าต้องตรวจการรั่วไหลของข้อมูลส่วนบุคคล และฝ่ายความเสี่ยงก็คงบอกว่าต้องป้องกันไม่ให้ผู้ไม่ประสงค์ดีดาวน์โหลดไฟล์ได้
      ฝ่ายเว็บก็น่าจะบอกว่าถ้าจะเพิ่มอะไรลงในไซต์ ต้องผ่านกระบวนการเปลี่ยนแปลงที่ได้รับอนุมัติก่อน
    • สงสัยว่าทำไมถึงตัดสินใจให้บริการเป็นไฟล์ ZIP แทนที่จะอัปโหลด CSV ตรง ๆ แล้วปล่อยให้การบีบอัด HTTP จัดการ
    • พอนึกภาพผลลัพธ์เวลาที่ไฟล์นี้เสียหายในทางใดทางหนึ่ง เช่น ชื่อคอลัมน์ผิด ก็แอบน่ากลัวนิดหน่อย
  • การดาวน์โหลดไฟล์ธรรมดา ๆ กับไฟล์ CSV นั้นยอดเยี่ยม
    อยากให้มีที่อื่นเปิดเผยข้อมูลด้วยรูปแบบเรียบง่ายแบบนี้มากขึ้น และทุกครั้งที่ต้องเติม “ตะกร้าสินค้า” ในการดาวน์โหลดข้อมูลรัฐบาลสหรัฐฯ ก็รู้สึกเหมือนตายลงทีละนิด
    มีเครื่องมือ wrapper มากมายที่ช่วยให้ไปป์ไลน์เฉพาะนี้ทำได้ง่ายขึ้น และถ้าต้องการมุมมองบนเว็บกับฟีเจอร์ขั้นสูงขึ้นอีกเล็กน้อย ของอย่าง Datasette ก็ใช้ได้ดี

    • ข้อดีของการให้บริการ CSV ที่บีบเป็น ZIP ผ่านเว็บคือ ถ้าไม่นับฐานข้อมูลแล้ว คุณสามารถสตรีมมันเข้าไปได้เร็วมากโดยไม่ต้องเก็บไว้ที่ไหนเลย
      อ่านไฟล์ ZIP เป็นสตรีม ประมวลผล CSV ทีละบรรทัดเพื่อแปลงข้อมูล แล้วโหลดเข้าฐานข้อมูลด้วย COPY FROM stdin ในกรณีของ Postgres ได้
    • น่าทึ่งที่ SQLite รับ CSV เป็นอินพุตแล้วคิวรีได้ทันที
      มันดูสมเหตุสมผลและมีประโยชน์มาก แต่ผมเพิ่งเคยเจอจนถึงตอนนี้
      เรามีรายงานเป็น CSV เยอะ เลยอยากลองเอาไปใช้รันคิวรีอย่างรวดเร็วเร็ว ๆ นี้
    • CSV จะยอดเยี่ยมก็ต่อเมื่อรับประกันได้อย่างเด็ดขาดว่ามี “CSV แบบเดียว” และไม่มีใครเปิด แก้ไขด้วยโปรแกรมสเปรดชีต หรือแก้ด้วยมือในโปรแกรมแก้ข้อความ
      ตัวอย่างเช่น วิธีจัดการเครื่องหมายอัญประกาศอาจแตกต่างกันแบบ "Look, this contains \"quotes\"!",012345 กับ "Look, this contains ""quotes""!",012345 และตัวอย่างที่พังยิ่งกว่าก็อาจเป็น "Look, this contains "quotes"!",012345 หรือ Look, this contains "quotes"!,012345
      ร่องรอยจากสเปรดชีตยังอาจทำให้เลข 0 ข้างหน้าถูกตัดทิ้ง เช่น "Look, this contains ""quotes""!",12345
      ในทางทฤษฎี JSON ก็อาจถูกแก้ด้วยมือจนกลายเป็นไฟล์ที่พังครึ่ง ๆ กลาง ๆ ได้เหมือนกัน แต่ในความเป็นจริงผมแทบไม่เคยเห็นใครทำแบบนั้นกับไฟล์ JSON และค่าประเภทเลขซีเรียลก็มักจะคงเป็นสตริงใน JSON ไม่ใช่จำนวนเต็มที่แอป “ใจดี” จะตัดเลข 0 ข้างหน้าออก
    • ผมไม่เคยคิดจริงจังเลยว่าวิธีแบบตะกร้าสินค้าของข้อมูลภาครัฐมันไร้สาระขนาดไหน
      ทำไมถึงต้องเป็นแบบนั้นกันแน่ มีเหตุผลที่สมควรอยู่บ้างไหม?
    • ดูเหมือนว่ารูปแบบเอกสารเองไม่ใช่ปัญหาหลัก
      ต่อให้เปลี่ยน CSV เป็นเอกสาร JSON ที่บีบใน ZIP ข้อดีก็เหมือนเดิม
      ปัญหาจริง ๆ คือมีอุปสรรคมากเกินไปในการดาวน์โหลดไฟล์เดียวที่ให้บริการแบบสแตติกอย่างเรียบง่าย
  • ผมเคยทำ API สำหรับหน่วยงานรัฐแห่งหนึ่ง ซึ่งข้อมูลเปลี่ยนแค่ปีละครั้งหรือแก้ไขนาน ๆ ครั้งเท่านั้น
    ชุดข้อมูลทั้งหมดสามารถรวมเป็นไฟล์ ZIP ขนาดต่ำกว่า 1MB ได้ไฟล์เดียว แต่พอโซลูชันอาร์คิเท็กต์เข้ามากำหนดข้อกำหนด งานก็ขยายใหญ่ขึ้น
    เขาไม่ให้ใช้แคช ด้วยเหตุผลว่าข้อมูลอาจเปลี่ยนขึ้นมาได้ ณ วินาทีที่มีคำขอพอดี ทำให้กลายเป็น API ที่ช้า และยังมีระบบเว็บฮุคที่ซับซ้อนเกินจำเป็นเพื่อแจ้งสมาชิกเมื่อข้อมูลเปลี่ยน
    ไฟล์ ZIP ไฟล์เดียวอาจจะเรียบง่ายเกินไป แต่ก็ไม่ได้ต่างจากสิ่งที่ต้องการจริง ๆ มากนัก

    • ถ้าข้อมูลต่ำกว่า 1MB เปลี่ยนปีละครั้งหรือสองครั้ง ผมมองว่า API ที่ถูกต้องคือเว็บเซิร์ฟเวอร์สแตติกที่รองรับ ETag/If-Modified-Since อย่างเหมาะสม
      ถ้าอยากทำให้ดูดีขึ้น ก็เพิ่มเว็บฮุคที่ถูกทริกเกอร์เมื่อไฟล์เปลี่ยน เพื่อให้ไคลเอนต์รู้ว่าควรดาวน์โหลดใหม่เมื่อไหร่ โดยไม่ต้องโพลวันละครั้ง
      หรือแค่ทำสคริปต์ส่งอีเมลที่กำหนดไว้ล่วงหน้าไปยังเมลลิงลิสต์เมื่อมีการเปลี่ยนแปลงก็เพียงพอแล้ว
    • วางไฟล์ ZIP ไว้บนเว็บเซิร์ฟเวอร์ที่รองรับ ETag แล้วโพลทุกครั้งที่ต้องการเข้าถึงก็พอ
      ถ้าไม่มีอะไรเปลี่ยนจากครั้งก่อน ก็จะได้คำตอบ HTTP 304 เปล่า ๆ กลับมา และถ้าเปลี่ยนแล้วก็รับไฟล์ ZIP ขนาดต่ำกว่า 1MB พร้อม ETag ใหม่อีกครั้ง ผมไม่รู้ว่าขาดอะไรไปตรงไหน
    • ถ้าข้อมูลเปลี่ยนแค่ปีละครั้งหรือนาน ๆ ครั้ง ผู้ใช้ที่ใช้ข้อมูลนั้นก็คงไม่ได้เรียก API บ่อยอยู่แล้ว ดังนั้นความเร็วอาจไม่ใช่ปัญหาใหญ่
      แคชเพิ่มความซับซ้อนและทำให้มีความเสี่ยงว่าต้อง revalidate แคชด้วยมือ ดังนั้นโซลูชันอาร์คิเท็กต์อาจจะถูกก็ได้
  • ถ้าต้องดาวน์โหลดไฟล์ 565KB เพียงเพื่อให้ได้ผลลัพธ์ค่าเดียวคือ 2000-10-26 ก็ถือเป็น API ที่แย่มาก
    ถ้าเป็นกรณีที่ต้องดึงข้อมูลจำนวนมากแล้วนำไปให้ผู้ใช้อีกที CSV ที่มัดรวมเป็น ZIP นั้นยอดเยี่ยม และผมชอบมันมากกว่า protobuf สำหรับตารางเวลารถไฟแบบเรียลไทม์ของขนส่งสาธารณะที่รองรับหลายภาษาได้ไม่ดีเสียอีก
    แต่ถ้าปฏิบัติกับมันเหมือน API สำหรับดึงค่าเดียว ก็เป็นการสิ้นเปลืองมหาศาล และหวังว่าจะไม่มีใครเอาไปใส่ในแอปด้วยวิธีนี้
    ตัวบทความเองยอดเยี่ยม แต่ชื่อเรื่องให้ความรู้สึกเหมือนเป็น ข้ออ้างเชิงยั่วยุ เกินไป

    • นี่คือ ข้อมูลย้อนหลัง
      ไม่มีเหตุผลเลยที่จะร้องขอมากกว่าวันละครั้ง และคนที่ใช้ข้อมูลแบบนี้ก็น่าจะต้องการตัวกรองหรือการรวมข้อมูลที่แตกต่างกันมาก
      ถ้าใช้เพื่อดึงอัตราแลกเปลี่ยนปัจจุบัน ก็ใช่ เป็นการออกแบบที่ไม่ดี แต่สำหรับกรณีนั้นมีบริการอื่นอยู่แล้ว และไฟล์นี้ก็เหมาะกับกรณีใช้งานทั่วไปของมัน
    • ถ้าคุณหวังว่าไม่มีใครเอาอะไรแบบนั้นไปใส่ในแอป ก็มีข่าวร้าย
      แม้จะไม่เกี่ยวกับ API โดยตรง แต่เมื่อก่อนตอนผมซัพพอร์ตแอปพลิเคชันจัดการที่ดิน ก่อนเวอร์ชันใหม่ออกมันยังทำงานได้ดีแม้ในสำนักงานสาขาที่เชื่อมต่อผ่านดาวเทียมช้า ๆ ซึ่งอาจเร็วแค่ระดับ ISDN แต่เวอร์ชันใหม่กลับใช้ไม่ได้เลย
      ผู้ขายบอกให้รันบนเซิร์ฟเวอร์ RDP แต่ผมคิดว่าเหลวไหล จึงตรวจสอบดู แล้วพบว่ามีการเรียกครั้งหนึ่งทำ SELECT * FROM sometable โดยไม่มีเหตุผลใด ๆ ในขณะที่การเรียกอื่น ๆ ในการรันเดียวกันใช้ส่วน select ของ SQL อย่างเหมาะสม
      พอบอกเรื่องนี้กับผู้ขาย ตอนแรกพวกเขาสับสนมากว่าเรารู้ได้อย่างไร และสุดท้ายก็ออกเวอร์ชันใหม่ที่แก้ให้ใช้งานบนสายสัญญาณช้าได้
      เข้าใจยากว่าทำไมการทดสอบภายในของพวกเขาถึงไม่จับเรื่องนี้ได้ แล้วกลับผลักภาระโซลูชันราคาแพงให้ลูกค้า
    • รู้สึกเหมือนข้อถกเถียงเมื่อ 20 ปีก่อนโทรกลับมาขอคำพูดของตัวเองคืน
      ถ้าคุณเคยดู JavaScript สมัยนี้แม้แต่นิดเดียว 565KB กับลอจิกสำหรับหาค่าขนาดใหญ่ในนั้นถือว่าเล็กมากตามมาตรฐานที่สมเหตุสมผลใด ๆ
    • ในเธรดนี้ ดูเหมือนหลายคนจะมองข้ามความแตกต่างของนิยามคำว่า “API” ไปแบบคร่าว ๆ
      บางคนมองว่า “วิธีได้มาซึ่งข้อมูล แม้จะรับข้อมูลทั้งหมดโดยไม่มีการกรอง” ก็คือ API แต่ส่วนตัวผมมองว่าการดาวน์โหลดทั้งตารางคือการดาวน์โหลด โมเดลข้อมูล ที่ไม่มีลอจิกทำงานกับโมเดลนั้น ส่วน API คือโลจิกที่กรองและส่งคืนบางส่วนของโมเดลตามวิธีที่ผมสนใจ
    • ไม่เข้าใจว่าทำไมถึงถือว่า 565KB เป็นเรื่องสำคัญ
      ผมเคยทำซอฟต์แวร์การเงินมามากทั้งฝั่งแบ็กเอนด์และฟรอนต์เอนด์ และฝั่งฟรอนต์เอนด์ก็พบได้บ่อยอย่างน่าเสียดายที่จะส่ง “ข้อมูล” ปริมาณประมาณนั้นตั้งแต่ก่อนจะไปถึงข้อมูลจริงด้วยซ้ำ
      ฝั่งแบ็กเอนด์มันก็เป็นแค่การตัดสินใจด้านการออกแบบ และไม่มีอะไรเร็วไปกว่างานครอนทุกคืนที่พาร์สอัตราแลกเปลี่ยน สร้าง todays-rates.json ที่ปรับให้ตรงวัตถุประสงค์ แล้วเสิร์ฟเป็นไฟล์สแตติกให้แอปมือถือ เว็บ และไมโครเซอร์วิส
      ไม่มีตรงไหนบอกว่าแอปมือถือต้องบริโภค ZIP-CSV-over-HTTP นี้โดยตรง
  • มีการปรับให้เหมาะสมแบบง่ายมากสำหรับคนที่บ่นว่าต้องดาวน์โหลดไฟล์ใหญ่ทุกครั้งที่ต้องการข้อมูลเล็ก ๆ เพียงค่าเดียว
    ถ้ารับประกันว่าไฟล์เป็นแบบ เพิ่มต่อท้ายเท่านั้น และใช้การบีบอัดอย่าง HTTP gzip/brotli แทนไฟล์ ZIP ก็สามารถใช้ range request เพื่อรับเฉพาะข้อมูลใหม่หลังการอัปเดตครั้งล่าสุดได้
    เพิ่มเฮดเดอร์ checksum ไว้เพื่อความอุ่นใจอีกสักตัว ก็จะกลายเป็น incremental API ที่ค่อนข้างมีประสิทธิภาพและเรียบง่ายมาก
    แน่นอนว่าต้องเก็บสถานะ ต้องจ่ายต้นทุนการดาวน์โหลดครั้งแรกและการดูแลสถานะ และถ้าต้องการแค่อัตรา EUR/JPY ของวันที่ 2007-08-22 เพียงครั้งเดียว ก็ยังไม่มีประสิทธิภาพอยู่ดี

    • ใช่แล้ว ผมกำลังคิดทำไลบรารีไคลเอนต์ที่ใช้ ETag กับเทคนิคอื่น ๆ เพื่อทำสิ่งนั้นพอดี
      ยังอยู่ระหว่างทำมาก ๆ แต่โค้ดระดับ “คุณภาพสำหรับงานวิจัย” ตอนนี้อยู่ที่นี่: https://pypi.org/project/csvbase-client/
    • พูดต่อเรื่อง range request ถ้าเซิร์ฟเวอร์อนุญาต range request สำหรับไฟล์ ZIP และไฟล์ ZIP มีขนาดใหญ่มากแต่ต้องการแค่ไม่กี่ไฟล์ข้างใน ก็สามารถดึงเฉพาะ central directory กับข้อมูลที่ถูกบีบอัดของไฟล์ที่ต้องการได้ โดยไม่ต้องดาวน์โหลด ZIP ทั้งก้อน
      https://github.com/gtsystem/python-remotezip
    • หรือไม่ก็แค่ให้ไฟล์ diff หลาย ๆ ไฟล์ก็พอ
      แค่มีแพตช์รายวันหนึ่งไฟล์ ก็ลดแบนด์วิดท์ที่ฝั่งผมต้องใช้เพื่อทำให้ไฟล์เป็นปัจจุบันได้มากแล้ว
      ทั้งนี้หมายถึงกรณีที่การดาวน์โหลดเพิ่มอีกไม่กี่ร้อย KB ต่อวันมีความหมาย ซึ่งส่วนใหญ่ก็น่าจะไม่ใช่แบบนั้น
  • ตัวอย่าง sqlite มีพิมพ์ตก
    แม้ในสกรีนช็อตจะไม่มี แต่ต้องเพิ่ม อาร์กิวเมนต์ -csv ให้กับ sqlite

    • แปลกนะ เดิมทีผมใส่อาร์กิวเมนต์นั้นไว้ แล้วเห็นเหมือนว่าไม่จำเป็นจึงเอาออก และในสภาพแวดล้อมของผมมันก็ทำงานได้
      ผมจะเพิ่มกลับเข้าไปและทำให้แคชหมดอายุ จะลองดูว่าเกิดอะไรผิดพลาดหลังจากกล่อมเด็ก ๆ เข้านอนแล้ว
      แก้ไข: เหตุผลที่มันทำงานในสภาพแวดล้อมของผมคือมีการตั้งค่า .separator ',' อยู่ใน ~/.sqliterc
      น่าจะเคยตั้งเป็นค่าเริ่มต้นไว้หลังจากตระหนักว่าผมมักใส่ไฟล์ CSV เป็นหลัก
  • ขอออกนอกเรื่องสักนิด แม้ยูโรในตอนแรกจะมีอยู่แค่ในรูปแบบอิเล็กทรอนิกส์ แต่ก็มี อัตราแลกเปลี่ยนคงที่ กับสกุลเงินเดิมของประเทศสมาชิกยูโรโซน
    โดยเฉพาะอย่างยิ่ง มันถูกตรึงกับ Deutsche Mark ของเยอรมนีที่เป็นที่ยอมรับและเชื่อถือได้
    ดังนั้นถ้าจะอธิบายว่า “ทำไมยูโรช่วงแรกถึงอ่อนค่า” ก็ต้องอธิบายด้วยว่า DEM ในเวลานั้นอ่อนค่าเพราะอะไร แต่คำอธิบายในย่อหน้านั้นดูเหมือนจะไม่ผ่านการตรวจสอบข้อนี้

  • สำหรับปัญหาขนาดเล็กที่สามารถดาวน์โหลดฐานข้อมูลทั้งหมดทุกครั้งแล้วจัดการแบบอ่านอย่างเดียวได้ อย่าประเมินค่าความเรียบง่ายต่ำไป
    ผมชอบ SQLite เพราะพกพาได้เหมือนไฟล์ .json หรือ .csv แต่พร้อมสำหรับ การโต้ตอบแบบฐานข้อมูล มากกว่า

    • ใช้ clickhouse-local ก็จัดการไฟล์ CSV เก่า ๆ เหมือนเป็นฐานข้อมูลได้
  • ประเด็นหลักอยู่ตรงนี้
    สิ่งที่ไม่ต้องทำในกรณีนี้: ต่อรองสิทธิ์การเข้าถึง เช่น ต้องจ่ายเงินหรือคุยกับฝ่ายขาย, เอาอีเมล ชื่อบริษัท ตำแหน่งงานไปใส่ในฐานข้อมูลลูกค้าเป้าหมายของใครสักคน, คอยรักษาโควตา, ยืนยันตัวตน, อ่านเอกสาร API, จัดการปัญหาที่ร้ายแรงกว่ารูปแบบและโครงสร้างพื้นฐาน

    • ผมไม่ค่อยเชื่อส่วนที่ว่าไม่มี “คอยรักษาโควตา”
      แบนด์วิดท์ ไม่ได้ฟรี
  • SQLite อ่านและเขียนไฟล์ ZIP ได้
    https://sqlite.org/zipfile.html
    สงสัยว่าสามารถแตกไฟล์ด้วย sqlite3 แทน gunzip ได้ไหม

    • เป็นประเด็นที่ดี
      ถ้าอนุญาตให้บันทึกไฟล์ลงดิสก์ได้ ก็ทำแบบนี้ได้:

sqlite3 -newline '' ':memory:' "SELECT data FROM zipfile('eurofxref-hist.zip')" \
| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
"select ...;"

การทำโดยไม่ใช้ไฟล์ชั่วคราวนั้นยุ่งยาก ตัวอย่างเช่น `readfile('/dev/stdin')` จะไม่ทำงาน เพราะ SQLite พยายามใช้ `seek()`  
วิธีที่น่าเกลียดมากแต่ทำได้คือแปลงไฟล์ ZIP เป็นเลขฐาน 16 ด้วย `xxd` แล้วใส่ลงในสตริงลิเทอรัลของคิวรี SQL:

`curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \`  
`| { printf "SELECT data FROM zipfile(x'"; xxd -p | tr -d '\n'; printf "')"; } \`  
`| sqlite3 -newline '' \`  
`| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \`  
`"select ...;"`