Browsertrix Crawler คือ crawler สำหรับการเก็บถาวรของ Webrecorder: Docker image เดียวที่ขับ Chromium ตัวจริงผ่าน Puppeteer บันทึกทุกอย่างที่เบราว์เซอร์ดึงมา และเขียนลงเป็น WARC — มาตรฐานไฟล์เก็บเว็บ — พร้อมตัวเลือกในการแพ็กเป็นชุด WACZ ที่มีทั้งดัชนี รายการหน้า และล็อกการทำงาน เป้าหมายของมันคือสิ่งที่ทำให้แตกต่างจากเครื่องมือสแครปข้อมูลที่หน้าตาใกล้เคียงกัน สแครปเปอร์ออกไปเก็บข้อมูลแล้วทิ้งหน้าเว็บนั้นไปเมื่อได้ฟิลด์ที่ต้องการ แต่ตัวเก็บถาวรจะเก็บ “การเข้าชม” ไว้ทั้งชุด — ทั้งไบต์ เฮดเดอร์ และลำดับที่ข้อมูลมาถึง — เพื่อให้เปิดหน้าเดิมกลับมาได้อีก แม้เว็บไซต์จะเปลี่ยนไปหรือหายไปแล้วก็ตาม Webrecorder ดูแลชั้นโครงสร้างพื้นฐานของเว็บส่วนนี้ ทั้งฟอร์แมตและระบบเล่นกลับข้อมูล มาตั้งแต่ก่อนที่การเก็บถาวรเว็บจะถูกมองว่าเป็นหมวดสินค้าด้วยซ้ำ และห้องสมุด ห้องข่าว และนักวิจัยจำนวนมากก็ใช้งานมันอยู่
ผมทดสอบ v1.14.0 บน Docker กับชุดทดสอบในเครื่องที่มีเอ็นด์พอยต์ 4 แบบซึ่งตั้งใจให้แตกต่างกันชัดเจน และติดเครื่องมือวัดทั้งสองฝั่งของการ crawl: ตรวจ WARC เพื่อดูสิ่งที่ถูกเก็บ และดูตัวนับการเรียกฝั่งเซิร์ฟเวอร์เพื่อเทียบกับ request จริง ประเด็นสำคัญไม่ใช่แค่ static กับ dynamic Browsertrix เก็บทั้งลิงก์ที่สร้างตอนรันไทม์และ fetch() ที่หน้าเว็บเรียกจริงได้ แต่กลับไม่ไปแตะ URL literal สองรายการที่อยู่ในฟังก์ชัน JavaScript ซึ่งไม่เคยถูกเรียกเลย
ไฟล์ app.js ที่ลิงก์ไว้ถูกเก็บเข้า archive แบบครบถ้วน รวมทั้ง path literal ทั้งสอง แต่ไม่มีเอ็นด์พอยต์ใดให้ response record, request record หรือ hit ฝั่งเซิร์ฟเวอร์เลย ฟังก์ชันที่เก็บมันไว้ไม่เคยรัน ดังนั้นบทความนี้จึงประเมิน Browsertrix ในฐานะเครื่องมือเก็บ “session ของเบราว์เซอร์” ไม่ใช่ตัวนับทุก URL ที่ถูกพูดถึงในซอร์สโค้ด
Browsertrix Crawler จริง ๆ คืออะไร
หลายคนเข้ามาเพราะคิดว่าจะได้สแครปเปอร์ แล้วก็ผิดหวัง ไม่มีอะไรใน pipeline ค่าเริ่มต้นที่ส่ง CSV ราคาสินค้ามาให้คุณ และถ้าจะหวังแบบนั้น ก็เหมือนคาดหวังให้กล้องติดรถเขียนรายงานจราจรให้คุณเอง ผลลัพธ์ที่ได้คือ บันทึกการใช้งานเบราว์เซอร์ที่เล่นกลับได้ และการออกแบบทุกอย่างหลังจากนั้นก็ถูกกำหนดด้วยข้อเท็จจริงนี้
ผมทดสอบ v1.14.0 เมื่อวันที่ 27 กรกฎาคม 2026: webrecorder/browsertrix-crawler:latest ที่ digest sha256:9d6800a8… โดย crawl --version ยืนยันบิลด์แล้ว โปรเจกต์นี้ใช้สัญญาอนุญาต AGPL-3.0 ทุกการวัดรันผ่าน Docker ด้วย Colima บน macOS arm64 และยิงเข้า fixture ในเครื่องที่ควบคุมได้ โดยตัวนับฝั่งเซิร์ฟเวอร์ทำหน้าที่เป็นหลักฐานที่แยกอิสระจากทั้งล็อก Browsertrix และการอ่านไฟล์ archive
เรื่อง AGPL-3.0 ควรพูดให้ชัดสักนิด เป็น copyleft แบบเข้มพร้อมเงื่อนไขการใช้งานผ่านเครือข่าย ถ้า Browsertrix Crawler จะถูกฝังอยู่ในผลิตภัณฑ์เชิงพาณิชย์ ไม่ใช่แค่รันเป็นเครื่องมือแยกเดี่ยว ควรให้ใครสักคนอ่านไลเซนส์ให้ถี่ถ้วนก่อนปล่อยใช้งาน นี่เป็นข้อควรระวัง ไม่ใช่คำแนะนำทางกฎหมาย
Archive เก็บเซสชัน ไม่ได้เก็บซอร์สโค้ด
fixture ของผมให้บริการเอ็นด์พอยต์ 4 กลุ่ม แยกกันไว้ชัด ๆ เพราะตัวเก็บถาวรจะจัดการแต่ละแบบต่างกันโดยสิ้นเชิง:
- Class A —
<a href>ธรรมดาใน HTML มี 4 หน้าและความลึก 3 ชั้น crawler ทั่วไปจับได้หมด - Class B — URL literal อยู่ในฟังก์ชันที่ไม่เคยรัน มี 2 path คือ
/api/js-endpoint-7และ/api/js-endpoint-8วางเป็นสตริงในloadData()ที่ไม่ได้ถูกเรียกในapp.js - Class C — ลิงก์ที่ประกอบขึ้นตอนรันไทม์ เป็น
<a href>ที่เอาชิ้นส่วนหลายส่วนมาต่อกันใน JavaScript ('endpoint' + (6 * 7)) แล้ว append เข้า DOM path แบบต่อเนื่อง/runtime-only/endpoint42ไม่มีอยู่ใน byte ที่เสิร์ฟมาเลย - Class D —
fetch()ที่หน้าเว็บเรียกจริง path ถูกประกอบแบบเดียวกัน ('runtime-xhr-' + (33 * 3)) แล้ว request จริงตอนโหลดหน้า
สำหรับ Class C และ D path เต็มไม่มีอยู่เป็นสตริงต่อเนื่องในไฟล์ที่เสิร์ฟมา ดังนั้น hit ฝั่งเซิร์ฟเวอร์และ response record จึงเป็นหลักฐานว่าการประกอบตอนรันไทม์และเส้นทางของ request ถูกใช้งานจริงใน fixture นี้
อะไรถูกเก็บ อะไรไม่ถูกเก็บ

มีเครื่องมือสองตัว และผมเทียบกันทุกช่อง: WARC response record (สิ่งที่อยู่ใน archive) กับตัวนับ hit ฝั่งเซิร์ฟเวอร์ที่ใช้คีย์ (Host header, path) (สิ่งที่ถูก request จริง) ผลตรงกันทุกจุด
| ประเภทเอ็นด์พอยต์ | มี response record ใน WARC | ถูก fetch จริง (ฝั่งเซิร์ฟเวอร์) | สรุป |
|---|---|---|---|
A — HTML <a href> | 4/4 | 4/4 | เก็บได้ |
| A — depth chain (3 ชั้น) | 3/3 | 3/3 | เก็บได้ |
| B — URL literal ใน JS ที่ไม่ถูกรัน | 0/2 | 0/2 | ไม่ถูกเก็บ |
| C — ลิงก์ที่ inject ตอนรันไทม์ | yes | yes | เก็บได้ |
D — fetch() ตอนรันไทม์ | yes | yes | เก็บได้ |
Class C คือจุดที่แยกเบราว์เซอร์จริงออกจากการ crawl แบบ static การดึงลิงก์ค่าเริ่มต้นอ่านจาก DOM ที่ render แล้ว (a[href]->href ตาม เอกสาร common options) ดังนั้นลิงก์ที่มีอยู่หลังจาก JavaScript รันแล้วก็ยังถูกคิว ถูก fetch และถูกเก็บได้อยู่ดี Class D ถูกจับได้ด้วยเหตุผลอีกแบบ — หน้าเว็บเป็นคนส่ง request เอง และตัวเก็บถาวรก็ยืนอยู่บนเส้นทาง network คอยบันทึกทุกอย่างที่วิ่งผ่าน
มีข้อจำกัดที่ควรพูดตรง ๆ สำหรับ Class C: ลิงก์ของผมถูก inject แบบ ซิงโครนัสตอนหน้าโหลด ลิงก์ที่โผล่ทีหลังระหว่าง behaviors ของ Browsertrix เป็นอีกกรณี และมี issue เปิดอยู่พอดีสำหรับเรื่องนี้ — #723, "Links on pages that are discovered during behaviors are not extracted" ผมไม่ได้ทดสอบสถานการณ์นั้น จึงไม่ได้สรุปอะไรเกี่ยวกับมันทั้งทางบวกหรือทางลบ
ไฟล์ถูกเก็บแล้ว แต่เอ็นด์พอยต์ไม่ถูกเก็บ
การยืนยันว่า Class B หายไปจริงต้องไล่ดู WARC ทีละ record ไม่ใช่แค่นับรวมแบบสรุป
app.js อยู่ ใน archive — มี response record หนึ่งรายการ เนื้อหา JavaScript 222 ไบต์ — และ literal ของ Class B ทั้งสองก็ปรากฏอยู่ในนั้นตรงตัว ขณะเดียวกัน /api/js-endpoint-7 และ /api/js-endpoint-8 ไม่ใช่ target URI ของ record ใด ๆ ตลอดทั้งไฟล์เลย: response record เป็นศูนย์ request record เป็นศูนย์ ทุกสตริง literal ปรากฏเพียงครั้งเดียวตลอดทั้ง archive และทั้งสองครั้งนั้นอยู่ในเนื้อไฟล์ที่เก็บของ app.js ทั้งหมด
นั่นตัดคำอธิบายแบบง่าย ๆ ทิ้งไปได้เลย (“app.js ไม่เคยถูก fetch”) ตัวเก็บถาวรเก็บไฟล์ที่อ้างถึงเอ็นด์พอยต์เหล่านั้นเอาไว้ แต่ไม่เคยส่ง request ไปหาเอ็นด์พอยต์เอง เพราะ loadData() ไม่เคยถูกเรียก Browsertrix เปิด behaviors ค่าเริ่มต้นไว้จริง — autoplay, autofetch, autoscroll, siteSpecific — แต่ autofetch ก็ไม่ได้ช่วยอะไร ซึ่งก็สมเหตุสมผลเมื่ออ่านว่า autofetch ทำอะไร มันตามหาแค่ img srcset, stylesheet และ URL ใน data-* ไม่ได้ไล่สตริง literal ที่ฝังอยู่ในตัวฟังก์ชัน
เพื่อให้เห็นภาพเดียวกันอีกมุม ผมยังรัน Katana v1.6.1 เป็น katana -u <seed> -jc -silent -nc -d 4 ด้วย รายงาน discovery summary ดิบ แสดงผลตรงข้ามกันสำหรับ JavaScript สองกลุ่มที่ตั้งใจสร้างขึ้น:
| สิ่งที่คุณอยากหา | Browsertrix v1.14.0 | Katana v1.6.1, standard -jc |
|---|---|---|
| ลิงก์ใน HTML ที่เสิร์ฟมา | พบ | พบ |
| ลิงก์ที่ inject เข้า DOM ตอนรันไทม์ | พบ (ดึงจาก DOM ที่ render แล้ว) | ไม่พบ หากไม่ใช้ headless mode |
fetch() ที่หน้าเว็บเรียกจริง | พบ (ถูกบันทึกเป็น traffic) | ไม่พบ — ไม่มีการรันอะไรเลย |
| URL literal ใน JS ที่ไม่เคยถูกรัน | ไม่พบ (0/2) | พบ (2/2 ใน fixture เดียวกัน) |
| ไฟล์ JS ที่เก็บ literal นั้นไว้ | เก็บครบทั้งไฟล์ | parse ได้ แต่ไม่เก็บไว้ |
ทั้งสองคำสั่งใช้ fixture และชื่อเอ็นด์พอยต์ชุดเดียวกัน ตารางนี้ไม่ได้เป็นการจัดอันดับ browser crawler กับ static crawler แบบทั่ว ๆ ไป แต่เพื่อชี้ว่าการสำรวจเอ็นด์พอยต์กับการเก็บเซสชันต้องใช้การทดสอบ coverage คนละแบบ
คำว่า “เป็นเบราว์เซอร์จริง ดังนั้นมันต้องจับทุกอย่างที่ JavaScript ทำได้” เป็นประโยคที่มักถูกพูดซ้ำในบทความหลายชิ้น แต่มันพูดเกินจริงเกินไป มันจับ traffic ที่ถูกรันจริง โค้ดที่อ้าง URL แต่ไม่เคยเรียกมัน จะไม่เกิด traffic และเมื่อไม่มี traffic ก็ไม่มี record
มี body สำหรับเล่นกลับ แต่ผมยังไม่ได้ตรวจอีกอย่าง
สำหรับเอ็นด์พอยต์สองตัวที่เกิดจากรันไทม์ ผมดึง HTTP response body ที่เก็บอยู่ใน WARC ออกมาดู และยืนยันว่าเป็น JSON ที่เสิร์ฟมาจริง: 206 ไบต์ สำหรับ target ของลิงก์ที่ inject ตอนรันไทม์ และ 201 ไบต์ สำหรับ target ของ fetch() ตอนรันไทม์ ดังนั้นนี่ไม่ใช่ stub ของ index ที่ชี้ไปหาความว่างเปล่า — ตัว content อยู่ใน archive จริง ซึ่งเป็นเงื่อนไขตั้งต้นของการเล่นกลับข้อมูลให้ทำงานได้
สิ่งที่ผม ยังไม่ได้ทำ คือเอา pywb หรือ replayweb.page ขึ้นมาแล้ว render archive จริง ๆ การที่ body อยู่ใน archive กับการ replay แล้วแสดงผลถูกต้องเป็นคนละคำกล่าว และการทดสอบนี้ครอบคลุมแค่ข้อแรกเท่านั้น พฤติกรรมการ replay ความถูกต้องของกลไก chain of custody และความเป็นพยานหลักฐานต้องตรวจแยกต่างหาก
การเก็บในงานจริงต้องมี acceptance test ที่กว้างกว่า
fixture นี้ตอบคำถามแคบ ๆ ได้ชัดเจน: ลิงก์ที่สร้างตอนรันไทม์แบบซิงโครนัส และ request ที่หน้าเว็บเป็นคนเรียกเอง กลายเป็น network traffic และ archive record หรือไม่? แต่งานเก็บรักษาใน production มักมีทางพลาดอีกหลายทาง ถึงแม้จะยังได้ WACZ ที่ดูถูกต้องอยู่ก็ตาม
เริ่มจาก replay ก่อน เปิดแพ็กเกจในระบบ replay ที่จะใช้งานจริง แล้วเทียบหน้าที่คัดเลือกไว้กับ reference ตอนเก็บ ตรวจทั้งข้อความที่ render แล้ว รูปภาพ สไตล์ การนำทาง และ interaction ที่สำคัญต่อบันทึกนั้น จากนั้นดูแท็บ network ของเบราว์เซอร์ replay ว่ามี subresource หายไปหรือไม่ response body อาจอยู่ใน WARC แต่ replay ยังล้มเหลวได้ เพราะการเขียน URL ใหม่ ดัชนี เวลา origin หรือ dependency ไม่ตรงกัน รีวิวนี้ไม่ได้ข้ามไปตรวจจุดนั้น
พฤติกรรมแบบ dynamic ควรมี fixture ชุดของตัวเอง ลิงก์ class C ในที่นี้โผล่มาแบบซิงโครนัสระหว่าง page load แอปจริงอาจเผย content หลัง timer, scroll, ปิด consent, route change, custom element หรือ chain ของ API ที่ยาวกว่านั้น วาง target ที่รู้ตำแหน่งไว้หลังพฤติกรรมแต่ละแบบที่คุณพึ่งพา แล้วตรวจทั้ง server hit และ body ที่ถูกเก็บ Browsertrix behaviors ค่าเริ่มต้นมีประโยชน์เป็น input สำหรับการทดสอบแบบนี้ ไม่ใช่หลักฐานว่าทุก state ที่หน่วงเวลาจะถูกเข้าถึงได้ Issue #723 สำคัญมากถ้าลิงก์โผล่ระหว่าง behaviors ไม่ใช่ระหว่างการรันหน้าแรก
การเก็บแบบมีการยืนยันตัวตนมีคำถามเรื่อง session เพิ่มเติม ต้องเช็กว่า state การล็อกอินเข้าสู่ browser profile ได้อยู่ต่อผ่าน navigation ที่จำเป็น และไม่รั่วไปยัง collection ที่ควรถูกแยกออก ทดสอบเส้นทาง refresh token และ logout ถ้า archive มีเนื้อหาส่วนตัวหรือข้อมูลอ่อนไหว ต้องทดสอบการควบคุมสิทธิ์และการเก็บรักษาไฟล์ที่ได้เป็นส่วนหนึ่งของแผน acceptance เดียวกัน การเก็บที่สมบูรณ์ในทางเทคนิคก็ยังอาจถูกจัดการผิดพลาดได้หลังจบการเก็บ
service worker, streaming media, WebSocket, ดาวน์โหลด, cross-origin frame และ signed URL ต่างก็ควรมีหน้าแทนตัวอย่างหากมันสำคัญต่อเป้าหมาย fixture 11 หน้าไม่ได้บอกอะไรเกี่ยวกับสิ่งเหล่านี้ และก็ไม่ยืนยันด้วยว่า crawler จะทำงานอย่างไรเมื่อหน้าเว็บยัง active อยู่หลาย นาที ส่ง request หลังช่วง settle ปกติ หรือจำเป็นต้องมี gesture จากผู้ใช้ อย่าเปลี่ยนคำว่า “Chromium ตัวจริง” ให้กลายเป็นการอ้างว่าครอบคลุมทุกอย่าง ให้ระบุพฤติกรรมของ browser ที่ collection ต้องรักษาไว้ แล้วทำให้แต่ละอย่างตรวจสอบได้
สุดท้าย ให้เก็บหลักฐานที่ใช้ไล่สาเหตุของ miss ไว้ด้วย บันทึก image digest และคำสั่งที่ใช้จริง, ล็อก Browsertrix, รายการหน้า, index, checksum ของ WARC/WACZ, หลักฐาน request ฝั่งเซิร์ฟเวอร์เมื่อมี, และ manifest ground truth ขนาดเล็ก สำหรับการเก็บซ้ำ ให้บันทึกเวลา ค่าคอนฟิก และสภาพแวดล้อมประกอบกับ artifact เหล่านั้น หลักฐานพวกนี้ไม่ทำให้เกิดการรับฟังทางกฎหมายโดยตัวมันเอง แต่ช่วยให้คำกล่าวอ้างเชิงเทคนิคทำซ้ำได้ และช่วยชี้ได้ว่าความแตกต่างที่เกิดขึ้นภายหลังมาจากตัวเว็บปลายทาง จาก crawler หรือจาก replay stack
ค่าใช้จ่ายด้านไบต์ของ fixture ที่ตัวเล็กมากนี้
fixture นี้เสิร์ฟเพียงไม่กี่ร้อยไบต์ต่อหน้า ดังนั้นอัตรา overhead ของมันไม่ควรถูกเอาไปใช้แทนเว็บไซต์ที่มี asset หนัก ๆ ภายในขอบเขตแคบ ๆ นี้ ผมวัดองค์ประกอบของ archive ไม่ใช่แค่ขนาดสุดท้าย:
| ประเภท WARC record | จำนวน | Content bytes | สัดส่วน |
|---|---|---|---|
request | 14 | 6,912 | 40.6% |
response (payload ของหน้าจริง) | 13 | 5,339 | 31.4% |
resource (JSON urn:pageinfo: หน้าละ 1 รายการ) | 11 | 4,527 | 26.6% |
revisit (ลิงก์ตายที่ dedupe แล้ว) | 1 | 154 | 0.9% |
warcinfo | 1 | 92 | 0.5% |
| Total record content | 40 | 17,024 | 100% |
จำนวนและ byte รวมแยกตามประเภทอยู่ในไฟล์สาธารณะ
capture-summary.json; สัดส่วนคำนวณโดยใช้ 17,024 ไบต์เป็นตัวหาร
ในรันที่ body เล็กแบบนี้ request record มีน้ำหนักมากกว่า response content และ bytes ของ request บวก urn:pageinfo: รวมกันมากกว่า payload ของ response ประมาณ 2.1 เท่า นี่คือภาพของ record mix ใน fixture นี้ ไม่ใช่อัตราส่วน WARC ทั่วไป
บนดิสก์ เมื่อรันแยกกัน 3 รอบ:
| ตัวชี้วัด | ต่ำสุด | มัธยฐาน | สูงสุด |
|---|---|---|---|
| เวลา crawl ทั้งหมด (วินาที) | 28.22 | 29.75 | 30.27 |
| ขนาด WARC.gz (ไบต์) | 24,174 | 24,250 | 24,262 |
| ขนาด WACZ (ไบต์) | 53,446 | 53,523 | 53,533 |
| response payload ที่เก็บได้ (ไบต์) | 5,339 | 5,339 | 5,339 |
เมื่อใช้ค่ามัธยฐาน จะได้สัดส่วน 4 แบบดังนี้:
| ตัวชี้วัดที่คำนวณจากมัธยฐาน | ค่า |
|---|---|
| WARC ที่บีบอัดแล้วเทียบกับ response payload ที่เก็บได้ | 4.5× |
| WACZ เทียบกับ response payload ที่เก็บได้ | 10× |
| WARC ต่อหน้า | ~2.2 KB |
| WACZ ต่อหน้า | ~4.9 KB |
และภายใน WACZ เอง:
| ส่วนประกอบของ WACZ | สัดส่วนของแพ็กเกจ |
|---|---|
| WARC | 45% |
| CDX index | 16% |
| Crawl log | 30% |
แถวสุดท้ายนี่แหละที่ทำให้ผมประหลาดใจ เกือบหนึ่งในสามของแพ็กเกจ archive ในการ crawl เล็ก ๆ คือบันทึกของการ crawl เอง ไม่ใช่ตัวเว็บ
ผลวัดมีความนิ่ง response payload กลับมา byte-identical ทั้ง 3 รอบ (5,339 B ทุกครั้ง) ขณะที่ WARC.gz และ WACZ แกว่งไม่ถึง 0.4%
อัตราส่วนเหล่านี้ใช้ไม่ได้กับหน้าเว็บจริงที่มีรูป ฟอนต์ และ bundle สคริปต์ใหญ่ ๆ แต่ข้อสังเกตเชิงโครงสร้างยังจริงอยู่: request record และ page-info record ทำให้เกิด overhead ไม่ขึ้นกับขนาด payload ควรเก็บตัวอย่างที่ใกล้เคียงของจริงก่อนวางแผน storage ใน production อย่าเอาอัตราส่วน 10× จาก fixture ไปคูณตรง ๆ กับทั้งคอร์ปัส
การตั้งค่า และพื้นที่ดิสก์ที่ควรวางแผนไว้
เมื่อมี Docker หรือ Colima ติดตั้งและพร้อมใช้งานแล้ว การตั้งค่าของ Browsertrix ก็แค่ docker pull webrecorder/browsertrix-crawler:latest แล้วตามด้วย docker run … crawl --url … --generateWACZ ภายใน image มี Chromium มาให้แล้ว จึงไม่ต้องติดตั้ง browser หรือ Python แยกต่างหาก
ความสะดวกนี้มีต้นทุนดังนี้จากการรันที่วัดไว้:
| สิ่งที่ต้องเผื่อไว้ | ที่วัดได้ |
|---|---|
| ขนาดดาวน์โหลด image | ~1 GB |
| ขนาด image เมื่อแตกลงดิสก์ | 3.51 GB |
ต้นไม้ crawls/ (WARCs, WACZs, ข้อมูลโปรไฟล์เบราว์เซอร์) หลังรันไม่กี่รอบของ fixture 11 หน้า ที่เสิร์ฟ content แค่ไม่กี่กิโลไบต์ | ~116 MB |
| เวลา wall clock ต่อการ crawl 11 หน้า | 28–30 s |
ความติดขัดส่วนใหญ่เกิดใน container และบรรทัด download+unpack นั่นคือราคาของการแพ็ก browser ไปด้วย ผมรันด้วย --shm-size 1g และเพราะ fixture อยู่บน host ในขณะที่ crawl รันใน container ผมจึงต้องใช้ --add-host=host.docker.internal:host-gateway และผูก fixture ให้ฟังที่ 0.0.0.0 แทน loopback ถ้าคุณ crawl บนอินเทอร์เน็ตสาธารณะ คุณจะข้ามขั้น networking นี้ไปได้ แต่ถ้ากำลังเก็บของบนเครื่องตัวเองหรือ host staging ภายใน ควรเผื่อเวลาสักบ่าย
โฟลเดอร์ output คือแถวที่ประเมินต่ำได้ง่ายที่สุด ถ้าเอาการเติบโตนี้ไปเทียบกับ crawl จริง ควรวางแผน storage ก่อนเริ่ม ไม่ใช่หลังจากดิสก์เต็มตอนตี 3
การสตาร์ตเบราว์เซอร์น่าจะมีส่วนสำคัญต่อ crawl 11 หน้า ใช้เวลาราว 28–30 วินาที แต่ผมไม่ได้แยกเวลา startup ออกจากเวลา navigation หรือ packaging ดังนั้นรันนี้จึงสรุป throughput ต่อหน้าไม่ได้
วางแผน storage โดยไม่เอาสัดส่วนของ fixture ไปใช้ผิด ๆ
วิธีคำนวณขนาด collection ที่มีประโยชน์คือวัดจากของจริง เลือกหน้าที่แทนการกระจายของเป้าหมายได้ เช่น shell ของแอปที่บางมาก, หน้า landing ที่มีรูปหนัก ๆ, ดาวน์โหลดเอกสาร, บทความยาว ๆ และหน้าที่ต้องล็อกอินถ้าอยู่ในขอบเขต เก็บแต่ละคลาสด้วย behaviors และการแพ็กที่ตั้งใจจะใช้ วัด payload ของ response, WARC, WACZ, indexes, logs, ข้อมูลค้างใน browser profile และพื้นที่ทำงานชั่วคราวที่ยังเหลืออยู่ระหว่างรัน Peak disk use สำคัญพอ ๆ กับขนาดแพ็กสุดท้าย ถ้าการแพ็กเก็บไฟล์ซ้ำไว้ชั่วคราวหลายชุด
แยกส่วนคงที่กับส่วนแปรผันให้ชัด Image container ขนาด 3.51 GB เป็น overhead ของ deployment ที่แชร์ได้กับการเก็บหลายงานบน worker เครื่องเดียว ส่วน request records, page-info records, indexes และ page lists โตตามกิจกรรมของ crawl ขณะที่ response bodies ขึ้นกับตัวเว็บมาก และ logs ขึ้นกับความยาวกับระดับความละเอียดของการรัน จากนั้น retention และ replication จะคูณขนาดชุดสุดท้ายโดยไม่ขึ้นกับพฤติกรรม crawl เอง โมเดล capacity ที่เอาทุกอย่างไปรวมเป็น “ไบต์ต่อหน้า” จะเปราะบางมาก
การบีบอัดและ dedupe ก็ต้องใช้เนื้อหาที่ใกล้เคียงจริง fixture นี้มี response payload เหมือนเดิมทุก byte ตลอด 3 รอบ แต่ไม่ได้แทนหน้าที่มีโฆษณาเปลี่ยนไป, timestamp, response ที่ personalized หรือ asset URL ที่ทำ cache busting ถ้าการเก็บซ้ำเป็นส่วนหนึ่งของโปรแกรม ควรวัด capture ซ้ำของหน้าเดิมและดู revisit record แทนที่จะสมมติว่าหน้าที่ดูไม่เปลี่ยนจะ dedupe ได้ดี เช่นเดียวกัน ควรทดสอบว่าล็อกและ index ถูกเก็บในระดับ replication เดียวกับ payload ของการเก็บรักษาหรือไม่
ในเชิงปฏิบัติ ตั้ง threshold เตือนไว้ก่อนเริ่ม collection ติดตามพื้นที่ว่าง การโตของแต่ละ collection การแพ็กที่ล้มเหลว และขนาด browser profile หรือไดเรกทอรีชั่วคราวต่าง ๆ ลอง restore จาก WACZ ที่เก็บไว้จริง ไม่ใช่แค่ตรวจ checksum ตัวเลขข้างบนมีประโยชน์เพราะบอกว่ามีองค์ประกอบอะไรบ้าง ส่วนตัวอย่างที่ใกล้เคียงของจริงจะบอกว่าแต่ละอย่างจะใหญ่แค่ไหนสำหรับไซต์ของคุณ
บันทึกสมมติฐานเหล่านั้นไว้ข้าง ๆ การประเมิน capacity และกลับมาทบทวนอีกครั้งหลัง pilot crawl
วินัยเรื่อง scope ที่ทดสอบด้วยสองเงื่อนไขควบคุม
crawler ที่หลุดขอบเขตไปเรื่อย ๆ เป็นความเสี่ยงในการปฏิบัติงานจริง เพราะคุณอาจได้ทั้งปัญหาทางกฎหมายและบิล storage พร้อมกัน หน้าแรกของผมลิงก์ไปที่ http://outofscope.test:<port>/page/out ซึ่งเป็น hostname อื่นแต่ชี้ไปยัง fixture เดียวกัน ดังนั้น hit ที่มี Host header นั้นจะพิสูจน์ได้ว่ามีการ fetch นอกขอบเขตโดยไม่ต้องเกี่ยวข้องกับอินเทอร์เน็ตจริง
| การตั้งค่า | มีการ fetch host นอก scope หรือไม่ | hits ฝั่งเซิร์ฟเวอร์ |
|---|---|---|
--scopeType prefix (ค่าเริ่มต้น) | ไม่ | 0 |
--scopeType any | ใช่ | 2 |
แถวที่สองทำให้แถวแรกมีความหมายจริง ภายใต้ any ลิงก์ถูกเข้าถึง 2 ครั้ง แปลว่ามันเข้าถึงได้ — ค่า 0 ภายใต้ prefix scope ค่าเริ่มต้นจึงเป็นวินัยเรื่อง scope จริง ไม่ใช่ลิงก์ที่ crawler มองไม่เห็น มีรายงานเปิดไว้เกี่ยวกับการ visit นอก scope ในคอนฟิกอื่น ๆ #788 ซึ่งผมไม่ได้ทำซ้ำภายใต้ default prefix scope บน fixture นี้ รู้ไว้ก็ดี แต่ผมไม่ขออ้างว่าพบเอง
ความทนทานถือว่าดีแบบไม่หวือหวา route ที่คืน HTTP 500 และลิงก์ที่เสียถูก request ทั้งคู่ การ crawl จบอย่างเรียบร้อยพร้อม WARC และ WACZ ที่ใช้ได้ และลิงก์เสียถูกเก็บเป็น revisit record แบบ dedupe แทนที่จะทำให้พัง
ข้อดีและข้อเสีย
ข้อดี
- จับลิงก์ DOM ที่ inject ตอนรันไทม์ และ
fetch()ที่หน้าเว็บเรียกจริงได้ — ยืนยันทั้งใน archive และฝั่งเซิร์ฟเวอร์ ใน path ที่ไม่มีอยู่เป็น literal ที่ไหนเลย - HTML แบบ static และการไล่ depth ทำได้ครบ: ลิงก์ 4/4, depth chain 3/3, ไม่พลาด
- เมื่อ Docker/Colima พร้อมแล้ว สั่ง
docker runครั้งเดียวได้ทั้ง WARC และ WACZ; Chromium มากับ image เลย - scope แบบ
prefixค่าเริ่มต้นคุมได้จริง ไม่ fetch นอก scope;anyขยายตามที่เอกสารบอก ปุ่มตั้งค่านี้ทำงานตามชื่อ - ผลลัพธ์เป็น archive ตามมาตรฐาน (WARC และแพ็กเป็น WACZ พร้อม CDX index และ page list) ไม่ใช่ blob แบบ proprietary
- archive มีความใกล้เคียง deterministic: payload byte-identical ตลอด 3 รอบ ขนาดบนดิสก์แกว่งไม่ถึง 0.4%
- พฤติกรรมเมื่อเจอปัญหาสุภาพดี: route 500 และลิงก์เสียไม่ทำให้ crawl หยุด
ข้อเสีย
- URL literal ใน JavaScript ที่ไม่ถูกรัน จะไม่ถูกค้นพบเลย (0/2) แม้ไฟล์ที่เก็บมันจะถูก archive อยู่ก็ตาม ถูกต้องตามการออกแบบ แต่ยังเป็นช่องว่าง coverage จริงถ้าเป้าหมายคุณคือการค้นหาเอ็นด์พอยต์
- ฟุตพรินต์หนัก: ดึง image ราว 1 GB, ใช้บนดิสก์ 3.51 GB และ output โตเร็ว
- overhead เป็น byte ค่อนข้างมากบนหน้าเล็ก ๆ — request บวก pageinfo มากกว่า payload จริง และ WACZ ราว 30% เป็น crawl log
- AGPL-3.0 แปลว่ามีงาน compliance จริงหากจะฝังในเชิงพาณิชย์
- ไม่ใช่เครื่องมือข้อมูลเชิงโครงสร้าง ไม่มี schema ไม่มี mapping ฟิลด์ และไม่มีแถวข้อมูลที่สะอาดพร้อมใช้
- throughput ต่อหน้าไม่สูงโดยธรรมชาติ เพราะทุกหน้าผ่านเบราว์เซอร์จริง
ใครควรรัน และใครไม่ควรรัน
Browsertrix เหมาะกับทีมที่ต้องการ artifact เป็น archive ของทรัพยากรที่เบราว์เซอร์ดึงมา ไม่ใช่แถวข้อมูลที่สกัดออกมา ใน fixture นี้ response body ที่มาจากรันไทม์อยู่ใน WARC และถูกแพ็กเข้า WACZ ด้วย ห้องสมุด ห้องข่าว และนักวิจัยเป็นผู้ใช้งานที่เป็นไปได้ แต่การนำไปใช้จริงควรทดสอบ replay, authentication, service worker, consent flow, behaviors ที่หน่วงเวลา, streaming assets, การควบคุมการเก็บรักษา และข้อกำหนดด้านการจัดการหลักฐานแยกต่างหาก
ไม่ต้องใช้มันถ้าสิ่งที่คุณต้องการคือ “ข้อมูล” จริง ๆ ถ้าเป้าหมายคือ “ขอทุกสินค้าและราคาจาก 400 หน้าให้เป็นสเปรดชีต” ตัวเก็บถาวรเป็นทางที่แปลกมากไปสู่ผลลัพธ์นั้น — คุณจะได้ archive หลายกิกะไบต์ก่อน แล้วก็ยังต้องเขียนโค้ด extraction กับไฟล์ WARC อยู่ดี และไม่ควรใช้ถ้าคุณกำลังทำแผนที่ surface ของ API เพราะผลของ class B บอกชัดว่า parser JavaScript แบบ static จะเห็นเอ็นด์พอยต์ที่ Browsertrix ไม่เคยแตะ และถ้าคุณไม่ชอบ Docker หรือทำงานในที่ที่ image ขนาด 3.5 GB เป็นปัญหา เครื่องมือนี้ก็ไม่ใช่ตัวที่ยืดหยุ่นตามคุณ
การอนุญาตและการเก็บรักษา
การควบคุม scope ไม่ได้แปลว่าได้รับอนุญาต ให้กำหนด host ที่อนุญาต, ระยะเวลาการเก็บ และสิทธิ์เข้าถึง archive ก่อนเริ่ม crawl โดยเฉพาะเมื่อการเก็บแบบคงทนอาจมีข้อมูลส่วนบุคคลปนอยู่ การทดสอบ prefix/any แสดงให้เห็นว่าการตั้งค่าทำให้เครือข่ายเข้าถึงได้มากน้อยต่างกัน แต่ไม่ได้บอกว่าการเข้าถึงระดับใดถูกกฎหมายสำหรับ collection ใด
รีวิวที่เกี่ยวข้อง: ด้านกฎหมายของ web scraping และการเก็บถาวรเว็บ.
ทางเลือกตามผลลัพธ์ที่ต้องการ
เลือกจาก artifact ที่ต้องส่งมอบ Browsertrix มุ่งเก็บรักษาในรูป WARC/WACZ เครื่องมือ automation ของ browser อย่าง Playwright ให้หน้าเว็บที่สั่งโปรแกรมได้ แต่การแพ็กการเก็บข้อมูลต้องจัดการเอง crawler สำหรับค้นหาเอ็นด์พอยต์จะไล่ URL ส่วนเครื่องมือสำหรับ extraction จะคืนข้อความหรือ records แบบมีโครงสร้าง หมวดเหล่านี้อาจใช้เบราว์เซอร์ร่วมกันได้ แต่แก้คนละงาน
รีวิวที่เกี่ยวข้อง: รีวิว Heritrix.
การเปิดเผยข้อมูล: Thunderbit เป็นผลิตภัณฑ์ของผู้เผยแพร่บทความนี้ และไม่ได้ถูกทดสอบใน fixture ของ Browsertrix นี้ มันอยู่ในหมวดการดึงข้อมูลแบบ managed extraction โดยให้ผลลัพธ์เป็นข้อความจากหน้าเว็บหรือข้อมูลเชิงโครงสร้าง ไม่ใช่ archive ตามมาตรฐาน รีวิวนี้ยืนยันเฉพาะขอบเขตของผลลัพธ์ ไม่ได้เป็นการอ้างอิงเชิงเปรียบเทียบด้านประสิทธิภาพหรือความสามารถ
ลองใช้ Thunderbit เพื่อดึงข้อมูลเว็บ
ข้อสรุป
ใช้ Browsertrix Crawler เมื่อผลลัพธ์ที่ต้องการคือ WARC/WACZ capture และการ deploy แบบคอนเทนเนอร์ที่มี Chromium อยู่ในตัวเข้ากับงานของคุณ fixture นี้แสดงว่า archive record กับ server hit ตรงกันสำหรับ runtime-DOM ที่เกิดแบบซิงโครนัสและ page-issued fetch scope แบบ prefix ค่าเริ่มต้นกัน host ที่สองได้ และ route ที่ล้มเหลวไม่ได้ทำให้ archive ที่ถูกต้องเสียหาย
ก่อนใช้งานจริง ควรตรวจ replay, behavior ที่หน่วงเวลา, session ที่ยืนยันตัวตน, service worker, องค์ประกอบของ storage บนหน้าเว็บที่เป็นตัวแทนของของจริง และภาระหน้าที่ด้านไลเซนส์ ขอบเขตที่ทดสอบแล้วแคบกว่านั้น: โค้ดที่อ้างถึงแต่ไม่เคยรัน ไม่ได้สร้าง request หรือ archive record สำหรับ target ของมันเลย แม้สคริปต์ต้นทางจะถูกเก็บไว้ก็ตาม
ลองใช้ Thunderbit เพื่อดึงข้อมูลเว็บ Get Started Free
คำถามที่พบบ่อย
WARC กับ WACZ ต่างกันอย่างไรในที่นี้? WARC เก็บ request, response และ record ที่เกี่ยวข้องที่ดึงมาได้ ส่วน WACZ คือการแพ็ก WARC รวมกับ index, page list, metadata และ logs เพื่อใช้แจกจ่ายและรองรับเครื่องมือ replay รีวิวนี้ตรวจทั้งสองแพ็กเกจ แต่ไม่ได้ render replay จริง
ควรประเมิน storage อย่างไร? ให้วัดจากหน้าตัวแทนของของจริงและเก็บองค์ประกอบ WACZ ให้ครบในตัวอย่าง รวมทั้ง logs และ indexes ตัวเลขอัตราส่วนในบทความนี้มาจาก response body ที่เล็กผิดปกติ จึงไม่เหมาะจะนำไปคูณกับจำนวน URL ใน production โดยตรง
มันจะหลุดไปนอกไซต์ที่ผมตั้งไว้ไหม?
จากการทดสอบของผม ค่าเริ่มต้นไม่หลุด ภายใต้ --scopeType prefix ลิงก์ไปยัง hostname อื่นถูก fetch 0 ครั้ง; พอเปลี่ยนเป็น --scopeType any มันถูก fetch 2 ครั้ง ซึ่งพิสูจน์ได้ว่าลิงก์นั้นเข้าถึงได้ และค่า 0 ของ prefix เริ่มต้นเป็นการคุม scope จริง มีรายงาน upstream แบบเปิดเกี่ยวกับการ visit นอก scope ในคอนฟิกอื่นที่ผมไม่ได้ทำซ้ำภายใต้ default ดังนั้นควรตรวจสอบ scope settings ของตัวเอง ไม่ใช่เดาเอา
ต้องทดสอบอะไรบ้างก่อนอ้างว่า replay fidelity ใช้ได้? โหลด WACZ ในระบบ replay ที่จะใช้จริง แล้วเทียบหน้าเว็บที่ render แล้ว, interactions และ subresource ที่จำเป็นกับ capture ต้นทางหรือ live body ที่อยู่ใน WARC จำเป็น แต่เพียงอย่างเดียวไม่พอจะยืนยัน replay ที่ render ถูกต้อง
Browsertrix แปลงหน้า archive เป็นแถวข้อมูลแบบมีโครงสร้างได้ไหม? ไม่ได้ ผลลัพธ์ของมันคือแพ็กเกจ archive ไม่ใช่ตารางของฟิลด์ที่เลือกมา ถ้าส่งมอบต้องเป็นสินค้า ราคา รายชื่อผู้ติดต่อ หรือ schema อื่น คุณยังต้องมีขั้นตอน extraction หลังเก็บ หรือใช้เครื่องมืออีกประเภทที่สร้างข้อมูลเชิงโครงสร้างเป็น output หลัก


