Heritrix คือ open-source archival crawler ของ Internet Archive — ซอฟต์แวร์สายเดียวกับที่อยู่เบื้องหลัง Wayback Machine และใช้งานจริงมานานกว่าสองทศวรรษ หน้าที่ของมันคือบันทึกสิ่งที่เว็บไซต์ส่งกลับมาให้ได้อย่างแม่นยำลงในไฟล์ WARC เพื่อให้ย้อนกลับมาเปิดดูได้อีกหลายปีข้างหน้า เบื้องหลังคือเครื่องมือ Java ที่ crawl ทุกงานถูกประกอบเป็นกราฟของ Spring bean ในรูปแบบ XML และจัดการ lifecycle ของงานทั้งหมดผ่าน REST API มันไม่ใช่ scraper: ไม่มี syntax สำหรับเลือกข้อมูล ไม่มีการ map ฟิลด์ และไม่มีแถวข้อมูลปลายทางให้คุณเอาไปใช้ได้ทันที
ผมทดสอบเวอร์ชัน 3.16.0 กับ fixture ในเครื่องที่ควบคุมได้ และไล่เช็กสิ่งที่มันเขียนออกมาทีละ record แบบบรรทัดต่อบรรทัด สิ่งที่เด่นที่สุดคือความครบถ้วน: URI ที่ดึงมา 20 รายการให้ WARC record รวม 61 รายการ โดยทุก response มี payload digest และ capture IP และทุก request ถูกเชื่อมกลับไปยัง response ที่เกี่ยวข้อง โดยไม่ต้องแตะค่าคอนฟิกอะไรเลย ส่วนประสบการณ์การใช้งานกลับสวนทางกันอย่างสิ้นเชิง — ตัวติดตั้งขนาด 41 MB, ไฟล์ jar 114 ไฟล์ (lib/ มีทั้งหมด 142 ไฟล์; อีก 28 ไฟล์เป็น LICENSE และ NOTICE ที่แนบมา), และไฟล์ job config ราว 750 บรรทัดก่อนจะเริ่ม fetch แรก — แม้การทดสอบทั้งหมดนี้จะรันแบบ headless ผ่าน curl โดยไม่ต้องคลิกหน้าเว็บ UI เลยก็ตาม
ผลด้านพื้นที่จัดเก็บสะท้อนค่าเริ่มต้นอีกจุดที่ไม่ค่อยเห็นชัด URLs สองรายการใน fixture ส่งเนื้อหาแบบไบต์ต่อไบต์เหมือนกัน แต่โปรไฟล์มาตรฐานกลับ fetch และ archive ทั้งสองรายการแบบเต็ม ๆ: ได้ response record สองรายการและไม่มี revisit record เลย แม้ response ทั้งสองจะมี payload digest เดียวกันก็ตาม การทำ content-digest deduplication จะทำงานก็ต่อเมื่อเพิ่ม history processors เข้าไปแล้วเท่านั้น; ค่าเริ่มต้นของโปรไฟล์ไม่ได้เปิดใช้ และมันช่วยประหยัดพื้นที่จัดเก็บ ไม่ใช่แบนด์วิดท์
Heritrix จริง ๆ แล้วคืออะไร
การเก็บถาวรไม่ใช่การดึงข้อมูล และนี่คือจุดที่หลายคนสับสน Heritrix ไม่ได้ส่งคืนข้อมูลเป็นตาราง ไม่มี CSV ให้ตอนจบ สิ่งที่มันเก็บคือบทสนทนาผ่าน HTTP เอง — ทั้ง headers, body และ metadata ของการจับข้อมูล — ในรูปแบบที่ออกแบบมาเพื่อการอนุรักษ์ระยะยาว และนี่คือ reference implementation ของงานแบบนี้โดยตรง ถ้าจะขอให้มันทำรายการราคาสินค้า ก็เหมือนขอให้ stenographer ในศาลสรุปคดีให้คุณ
ตัวเลขล่าสุด ณ 27 กรกฎาคม 2026: repo นี้มี 3,285 stars และ 36 open issues และ 3.16.0 คือ release ล่าสุดที่ปล่อยเมื่อ 2026-07-03 ซึ่งเป็น build เดียวกับที่ผมทดสอบพอดี เพราะฉะนั้นสิ่งที่เขียนไว้ตรงนี้ไม่ใช่การบ่นเรื่องเวอร์ชันเก่า เรื่องไลเซนส์มีจุดที่ต้องระวังเล็กน้อย: ไฟล์ LICENSE เป็น Apache-2.0 ตรงไปตรงมา แต่ตัวตรวจจับของ GitHub ระบุเป็น "Other" เพราะไฟล์ third-party ที่แนบมาบางส่วนมีเงื่อนไขของตัวเอง หาก Heritrix จะถูกเอาไปใช้ในผลิตภัณฑ์เชิงพาณิชย์ เรื่องนี้ควรให้ฝ่ายกฎหมายใช้เวลาตรวจสักห้านาที ไม่ใช่ดูแค่ badge ด้านข้างแล้วจบ
ข้อเท็จจริงเชิงโครงสร้างข้อหนึ่งกำหนดทุกอย่างที่เหลือ: Heritrix ไม่ได้ขับ browser มัน fetch ผ่าน HTTP และดึงลิงก์จากไบต์ที่ตอบกลับมา เทียบกับอีกเครื่องมือในสาย archiving ยุคใหม่อย่าง Browsertrix Crawler ซึ่งทำตรงข้าม — ใช้ Chromium จริง และบันทึกสิ่งที่ browser ทำจริง ทั้งสองตัวเขียนออก WARC เหมือนกัน แต่รีวิวนี้วัดเฉพาะเส้นทางแบบไม่ใช้ browser ของ Heritrix เท่านั้น ไม่ได้ benchmark ขนาดหรือ throughput ของทั้งสองระบบ
Chains, beans และ SURT: crawl ถูกประกอบขึ้นอย่างไรจริง ๆ

เบื้องหลัง Heritrix job หนึ่งงานคือ Spring application context ไม่ใช่แค่ "ตั้งค่าด้วย Spring" แต่มัน คือ กราฟของ Spring bean ที่เขียนเป็น XML และทุกส่วนของ crawl สามารถสลับเปลี่ยนได้เป็น bean
Frontier คือที่เก็บคิว URI แยกตาม host การแบ่งแบบนี้คือเหตุผลที่ politeness ทำงานในแบบที่มันทำ และจะมีความสำคัญมากในภายหลัง
Processor chains ทำงานเป็น 3 ขั้น: candidate chain (URI ที่ค้นพบควรถูก schedule หรือไม่), fetch chain (DNS, robots, HTTP fetch, ดึงลิงก์), และ disposition chain (เขียนลง WARC, อัปเดตสถานะ) การเพิ่มความสามารถให้ Heritrix โดยทั่วไปหมายถึงการแทรก processor bean ให้ถูกจุดใน chain ที่ถูกต้อง ซึ่งก็เป็นวิธีที่ผมเปิด dedup ในการทดสอบนี้
Scope คือ stack ของ DecideRules ที่ทำงานบน SURT — Sort-friendly URI Reordering Transform ซึ่งจะแปลง http://www.example.com/a ให้เป็น http://(com,example,www,)/a เพื่อให้ prefix ของ hostname เรียงเป็นลำดับแบบลำดับชั้นได้ Default scope จะถูกสร้างจาก SURT prefix ของ seed ที่ใส่เข้าไป กฎต่าง ๆ จะอนุมัติหรือปฏิเสธตามลำดับ และตัวที่แมตช์ล่าสุดคือผู้ชนะ
WARC writer อยู่ใน disposition chain ส่วน politeness อยู่ใน frontier และควบคุมด้วยตัวเลข 3 ค่า: delayFactor, minDelayMs, maxDelayMs การทำตาม robots เป็น policy string บน fetcher
ทั้งหมดนี้ควบคุมได้ผ่าน REST API ซึ่งกลายเป็นเรื่องสำคัญกว่าที่ผมคาดไว้มาก
การติดตั้งคือส่วนที่หนักที่สุดของประสบการณ์ทั้งหมด
สิ่งที่ต้อง deploy ก่อนจะ fetch แรก:
| มิติของการตั้งค่า | Heritrix 3.16.0 |
|---|---|
| ขนาดไฟล์ distribution tarball | ประมาณ 41 MB |
จำนวนไฟล์ใน lib/ หลังแตกไฟล์ | 142 ไฟล์ทั้งหมด: 114 ไฟล์ .jar และ 28 ไฟล์ LICENSE/NOTICE |
| สิ่งที่ถูกเปิดขึ้นเมื่อสตาร์ต | Java engine พร้อม embedded Jetty web UI บน https://localhost:8443 หลัง self-signed certificate |
| เวลาจนพร้อมใช้งานผ่าน REST บนเครื่องผม | ประมาณสิบวินาที |
job config แบบมาตรฐาน (crawler-beans.cxml) | Spring bean XML ราว 750 บรรทัด |
job config ส่วนใหญ่คุณแทบไม่ต้องแตะ แต่ก็ข้ามไม่ได้ และมีสองฟิลด์ที่ต้องใส่ก่อนให้ crawler เริ่ม fetch อะไรได้เลย: seed ของคุณ และ metadata.operatorContactUrl ค่าเริ่มต้นเป็นเพียง placeholder และจะไม่ crawl จนกว่าจะเปลี่ยนเป็น URL จริงที่ระบุว่าใครเป็นผู้รัน crawl นี้
ข้อกำหนดนี้สร้างจุดรับผิดชอบขึ้นมา: ผู้ปฏิบัติงานต้องใส่ contact URL ก่อน crawler จะทำงาน มันไม่ได้ยืนยันว่าตัวตนถูกต้อง การ crawl ได้รับอนุญาต หรือเป็นไปตามกฎที่เกี่ยวข้องหรือไม่ แต่ก็ทำให้ข้อมูลติดต่อเป็นส่วนหนึ่งของงาน ไม่ใช่แค่ธรรมเนียมที่ใส่หรือไม่ใส่ก็ได้
สองข้อค้นพบตอนตั้งค่าที่ทำให้ผมแปลกใจจริง ๆ
มันรันบน JDK ใหม่กว่าขั้นต่ำที่เอกสารระบุ เอกสาร Getting Started ระบุ Java 17 หรือใหม่กว่า แต่ Heritrix 3.16.0 บูตขึ้นมา เปิด REST API และทำ crawl ใน fixture นี้จนจบได้บน OpenJDK 26.0.1 โดยไม่ต้องใช้ --add-opens, --enable-preview หรือ workaround ของ Security Manager นี่เป็นผลจาก macOS arm64 เครื่องเดียว ไม่ใช่ compatibility matrix แต่ก็ยืนยันว่า build ที่ทดสอบนี้ไม่ได้ติดอยู่แค่ JDK 17 บนโฮสต์นี้
คุณไม่จำเป็นต้องแตะ web UI เลย lifecycle ของงานทั้งหมดอยู่ใน REST และผม automate ทั้งกระบวนการด้วย curl: สร้างงาน, PUT ไฟล์ beans, build, launch, unpause, poll จน controller state เป็น FINISHED, terminate, teardown นั่นคือคำตอบจริง ๆ ของคำถามที่ว่า Heritrix ใช้ใน pipeline ได้ไหม — ได้ และทำได้แบบ headless โดยไม่ต้องคลิกอะไรบน browser เลย บทความส่วนใหญ่ชอบแคปหน้าจอ Jetty UI แล้วทำเหมือนมันคือ interface หลัก แท้จริงแล้วมันเป็นเพียงความสะดวก ไม่ใช่ข้อบังคับ
หมายเหตุเฉพาะโฮสต์หนึ่งข้อ: Mac เครื่องนี้ใช้ HTTP proxy ระดับระบบผ่าน Surge Java client ของ Heritrix รับ proxy นั้นไปด้วย และส่ง traffic fixture แม้แต่ 127.0.0.1 ผ่าน proxy ทำให้ได้ response 503 ทั้งที่ OS มี exception list และ NO_PROXY แล้ว การสตาร์ต JVM ด้วย -Djava.net.useSystemProxies=false ทำให้ผลลัพธ์เปลี่ยนจาก 2×503 เป็น 18×200 นี่เป็นปฏิสัมพันธ์กับสภาพแวดล้อม ไม่ใช่ defect ของ Heritrix; flag นี้สำคัญเฉพาะกรณีที่ไม่ต้องการให้ระบบสืบทอด proxy settings เดิม
สิ่งที่ลงเอยใน archive จริง ๆ
ผมรันโปรไฟล์ default มาตรฐานกับ fixture ที่ควบคุมได้ — เซิร์ฟเวอร์ local ที่มีชุด endpoint ที่รู้แน่ เช่น หน้า HTML, chain ความลึก 3 ชั้น, robots.txt และ sitemap รวมถึงเส้นทาง 404 และ 500 ที่ตั้งใจทำไว้ — แล้วจึง parse WARC ที่ได้ทีละ record แทนที่จะเชื่อสรุปสั้น ๆ บนหน้าจอ
URI ที่ fetch มา 20 รายการให้ record ทั้งหมด 61 รายการ:
| ประเภท WARC record | จำนวน | เก็บอะไรไว้ |
|---|---|---|
warcinfo | 1 | provenance ระดับ crawl เขียนครั้งเดียวต่อไฟล์ |
response | 20 | HTTP response เต็มรูปแบบ ทั้ง headers และ body |
request | 20 | คำขอที่ Heritrix ส่งจริง |
metadata | 20 | คำอธิบายการจับข้อมูลของ Heritrix เอง |
ได้สัดส่วน response/request/metadata แบบ 1:1:1 ต่อ URI ตั้งแต่ค่าเริ่มต้น โดยผมไม่ต้องตั้งค่าอะไรเพิ่มเติม และเมื่อเจาะตรวจแต่ละ record ความครบถ้วนก็ยังยืนอยู่ได้:
| ตรวจต่อ record | จำนวน | ทำไมถึงสำคัญ |
|---|---|---|
payload digest ที่ขึ้นต้นด้วย sha1: บน response | 20/20 | — |
WARC-IP-Address บน response | 20/20 | คือ IP ต้นทางที่เนื้อหาถูกส่งมาจริง ซึ่งมีประโยชน์มากในอีกหลายปีข้างหน้าเมื่อโดเมนอาจเปลี่ยนมือไปแล้ว |
request record เชื่อมกับ response ผ่าน WARC-Concurrent-To | 20/20 | ไม่ใช่แค่ "เกือบทั้งหมด" แต่ครบทุกอัน |
และสถานะ HTTP ถูกเก็บไว้ตรงตัว รวมถึงสถานะที่ไม่น่าดูด้วย: 200 OK, 404 Not Found, และ 500 Internal Server Error ปรากฏเป็น status line จริงใน response ที่เก็บไว้ ไม่ได้ถูกทิ้งไปเพราะถือว่าเป็นความล้มเหลว
ประเด็นสุดท้ายนี้แยก archive ออกจาก scraping ได้ชัดกว่าสิ่งอื่นทั้งหมด scraper มอง 500 เป็น error ที่จะลองใหม่หรือข้ามไป แต่ archiver มองมันเป็น สิ่งที่เซิร์ฟเวอร์ตอบในตอนนั้น ซึ่งเป็นข้อเท็จจริงที่ควรเก็บไว้ Heritrix Output อธิบายโครงสร้าง record นี้ไว้แล้ว แต่สิ่งที่ผมไม่ค่อยเห็นที่ไหนคือการวัดจำนวน record จริงและการเชื่อมโยงแบบ 20/20 บนชุด endpoint ที่รู้แน่ ซึ่งผลลัพธ์ก็เป็นไปตามนั้น
ผลของ dedup ที่วัดได้ทั้งสองแบบ

fixture ของผมให้ /dup/one และ /dup/two ส่ง body ที่เหมือนกันแบบไบต์ต่อไบต์ คนละ URL แต่เนื้อหาเดียวกัน — ซึ่งเป็นกรณีที่ content-digest dedup ถูกออกแบบมาเพื่อลดความซ้ำโดยตรง ผมรันสองรอบ: ครั้งหนึ่งด้วย stock profile และอีกครั้งหลังเพิ่ม digest-history chain (BdbContentDigestHistory พร้อม ContentDigestHistoryLoader ใน fetch chain และ ContentDigestHistoryStorer หลัง WARC writer)
| โปรไฟล์มาตรฐาน | เมื่อเพิ่ม ContentDigestHistory chain | |
|---|---|---|
response record เต็มที่ถูกเขียน | 2 | 1 |
revisit record ที่ถูกเขียน | 0 | 1 |
| payload digest ร่วมกัน | ใช่ (ทั้งคู่) | ใช่ |
| profile ของ revisit | — | identical-payload-digest |
เมื่อใช้ค่ามาตรฐาน ทั้งสอง response ได้ digest เดียวกัน แต่ไม่มี history processor ใดนำข้อมูลนั้นไปใช้ และ payload ทั้งสองก็ถูกเขียนแบบเต็มทุก byte การเพิ่ม chain เข้าไปทำให้การจับครั้งที่สองกลายเป็น WARC revisit record ที่ชี้ไปยัง identical payload digest ซึ่งเป็นพฤติกรรมที่ WARC 1.1 specification นิยามไว้สำหรับ revisit โดยตรง
เรื่องนี้ไม่ได้เป็นความลับอะไร หน้า wiki Duplication Reduction Processors ระบุชัดว่า skipIdenticalDigests มีค่าเริ่มต้นเป็น false และ dedup แบบไม่ยึด URL ต้องใช้ loader และ storer beans เหล่านั้น นี่ไม่ใช่พฤติกรรมที่ซ่อนอยู่แล้วถูกค้นพบใหม่; แทบทุกอย่างที่วัดในที่นี้เป็นพฤติกรรมของ Heritrix ที่มีเอกสารรองรับ เพียงแต่มีตัวเลขจากการทดสอบภาคสนามแนบมาด้วย ช่องว่างอยู่ระหว่างเอกสารกับสิ่งที่ผู้คน เชื่อ และจากประสบการณ์ของผม คนมักจะเชื่อว่า "Heritrix dedups" จบเลย โดยไม่ใส่เครื่องหมายดอกจันเรื่องคอนฟิก
มีข้อสรุปตามมาสองข้อที่ควรจำให้ขึ้นใจ:
Dedup เกิดตอนเขียน ไม่ใช่ตอนประหยัดแบนด์วิดท์ ข้อนี้เป็นเรื่องของกลไกมากกว่าตัวเลขที่ผมวัดแยกต่างหาก แต่เหตุผลตรงไปตรงมา: จะเปรียบเทียบ digest ได้ก็ต่อเมื่อข้อมูลถูกส่งมาถึงแล้ว ดังนั้น URL ที่สองจึงยังต้อง fetch จากต้นทางเหมือนเดิม การเปิด chain จะลดสิ่งที่คุณเก็บ ไม่ได้ลดสิ่งที่ถ่ายโอนหรือภาระที่เซิร์ฟเวอร์ปลายทางต้องส่ง คนที่กันงบ dedup ไว้เป็นเหตุผลเรื่องความสุภาพต่อเซิร์ฟเวอร์หรือเรื่อง bandwidth จึงมองกลับด้าน
การประเมินพื้นที่จัดเก็บที่ตั้งอยู่บนคำว่า "เดี๋ยวมัน dedup เอง" อาจผิดหนักได้ ถ้าคุณกำลัง archive เว็บไซต์ที่มี template ซ้ำเยอะ ๆ — PDF ที่ mirror กัน, landing page แบบ boilerplate, หรือหน้า print-view ของบทความเดียวกัน — แล้วคำนวณดิสก์โดยสมมติว่า body ที่เหมือนกันจะถูกยุบรวม โปรไฟล์มาตรฐานอาจใช้พื้นที่มากกว่าที่ประเมินไว้มาก fixture แบบสอง URL ชุดนี้บอกแค่พฤติกรรมเริ่มต้น ไม่ได้บอกผลในระดับล้าน URI; ผลนั้นขึ้นกับอัตราการซ้ำ ขนาด payload และรูปแบบ recrawl ด้วย
Scope และ robots ทำงานตรงตามที่สัญญาไว้
คำพูดของ crawler เองเกี่ยวกับสิ่งที่มันไม่ได้ fetch มีค่าน้อยมาก ดังนั้นทั้งสองการทดสอบนี้จึงวัดด้วยตัวนับฝั่งเซิร์ฟเวอร์ — คือให้ target server นับ request เอง โดยไม่ขึ้นกับสิ่งที่ Heritrix log ไว้
| ตัวควบคุม | เงื่อนไข | จำนวน hit ฝั่งเซิร์ฟเวอร์บน target | สิ่งที่ crawl log แสดง |
|---|---|---|---|
| Scope | Default scope; ผม seed หน้าเว็บที่ลิงก์ไปยัง host อีกตัวที่มี SURT authority ต่างกัน | 0 | host ที่อยู่นอก scope ไม่ปรากฏเลย — หมายความว่ามันถูกปฏิเสธตั้งแต่ discovery ไม่ได้ถูก enqueue แล้วค่อย fail |
| Robots | ค่าเริ่มต้นให้ obey policy; หน้าแรกลิงก์ไปยัง /robots-denied/secret ซึ่ง robots.txt ของ fixture ไม่อนุญาต | 0 | ถูกบันทึกว่าโดนบล็อก (robots.txt เองถูก fetch แล้ว) |
| Robots | ควบคุม: เปลี่ยน robotsPolicyName เป็น ignore แล้วรันใหม่ | 1 | — |
Scope ในขณะเดียวกัน host ที่อยู่ใน scope ก็ถูก crawl ตามปกติ แปลว่า scope ทำงานเป็นระเบียบ ไม่ใช่ crawl พัง ข้อควรระวังคือ: ในที่นี้ผมทดสอบเฉพาะแขน default-scope ไม่ได้มี positive control ที่ขยาย scope ดังนั้นให้อ่านว่าเป็นการยืนยัน design ตามเอกสาร มากกว่าจะเป็นหลักฐานสองด้านแบบสมบูรณ์
Robots ลิงก์นั้นเข้าถึงได้ตลอดเวลา; มีเพียง policy ของ robots ที่สกัดมันไว้ การยอมทำตามนั้นเกิดขึ้นจริง และทางออกฉุกเฉินก็มีจริงเช่นกัน ซึ่งเป็นรูปแบบที่ถูกต้อง — งาน archive บางประเภทมีเหตุผลทางกฎหมายที่ต้อง override robots ได้ และสิ่งนั้นควรต้องพิมพ์ ignore ลงในไฟล์คอนฟิกโดยตั้งใจจริง ๆ
Politeness: 57.7 วินาทีเพื่อ crawl หน้า local 20 หน้า

มีตัวเลขหนึ่งตัวที่ตัดสินว่า Heritrix เหมาะกับโปรเจกต์ของคุณหรือไม่
fixture เดิม, ทดสอบ 3 รอบเหมือนเดิม, บน host เดียวในเครื่องที่ latency ต่ำกว่ามิลลิวินาที:
| การตั้งค่า Politeness | ค่ามัธยฐานของช่วงห่างระหว่าง request บน host เดียวกัน | เวลารวมของ crawl 20 URI |
|---|---|---|
ค่า default ของโปรไฟล์ (delayFactor 5.0, minDelayMs 3000, maxDelayMs 30000) | 3,036 ms (ต่ำสุด 3,021, สูงสุด 9,107 จาก 48 ช่วงที่วัด) | 57.66 s / 57.66 s / 57.70 s (3 รอบ) |
| ปิด politeness ลง | 2 ms | 27 ms (ค่ามัธยฐาน) |
delay ที่เกิดขึ้นจริงไปชน floor ของ minDelayMs พอดี: บน origin ที่ตอบกลับภายในต่ำกว่ามิลลิวินาที delayFactor × fetch-time แทบไม่มีผล และขั้นต่ำคือสิ่งที่กำหนดพฤติกรรมทั้งหมด อัตราส่วนระหว่างสองแถวไม่ค่อยมีประโยชน์ เพราะตัวหารในกรณี politeness เป็นศูนย์มีเพียงหลักสิบมิลลิวินาทีและมี jitter ไปมาต่อรอบ สิ่งที่มั่นคงจริง ๆ คือ floor แบบสัมบูรณ์: Heritrix ค่าเริ่มต้นรอประมาณสามวินาทีระหว่าง request ไป host เดียวกัน ใน fixture นี้ ทำให้ crawl 20 หน้าใช้เวลาราวหนึ่งนาที
ลองนึกภาพแบบจับต้องได้ สมมติห้องสมุดมหาวิทยาลัยต้อง archive เว็บไซต์หน่วยงานรัฐที่มี 50,000 หน้า ก่อนที่ระบบจะถูกยกเลิก และทุกอย่างอยู่บน host เดียว ที่ floor 3 วินาทีต่อ host เท่ากับ 150,000 วินาทีของการรอที่ถูกบังคับ — ประมาณ 42 ชั่วโมง หรือราววันครึ่งถึงสองวันเกือบเต็ม ก่อนจะนับเวลาการ fetch เข้าไปด้วย นี่คือตัวเลขจาก floor ที่ผมวัดได้จริง 3,036 ms — 50,000 x 3.036 s = 151,800 s = 42.2 h; ต่อให้ใช้ขั้นต่ำ 3,000 ms ตามคอนฟิกก็ยังได้ 41.7 h ซึ่งปัดเป็น 42 ไม่ใช่ 41 นี่ไม่ใช่การ crawl ที่ผมรันจริง แต่เป็นคณิตศาสตร์ที่แผนงานของคุณต้องใช้
ต้องยุติธรรมกับ Heritrix: politeness ของมันเป็นแบบ ต่อ host เพราะ frontier แยกคิวตาม host crawl ขนาดใหญ่ที่กระจายไปหลายพันโดเมนจะ parallelize ข้ามคิวเหล่านั้น และไม่ได้ติดเพดานนี้ทั่วทั้งระบบ fixture ของผมเป็น host เดียว ซึ่งคือกรณีที่เลวร้ายที่สุดสำหรับตัวเลขนี้โดยเฉพาะ ถ้าเป้าหมายของคุณคือไซต์ใหญ่เพียงไซต์เดียว กรณีเลวร้ายนั่นแหละคือกรณีของคุณ
และมันเป็นฟีเจอร์ ไม่ใช่บั๊ก การหน่วงเวลาแบบนี้ทำให้ archival crawler กลายเป็นเครื่องมือที่เจ้าของเว็บไซต์ยอมให้ใช้งานได้ แทนที่จะบล็อกทิ้ง การปรับลดมันคือการตัดสินใจที่ไปกระทบเซิร์ฟเวอร์ของคนอื่น และเครื่องมือนี้ทำให้การตัดสินใจนั้นชัดเจน แทนที่จะเป็นแบบเชิงรุกโดยปริยาย
สิ่งที่ผมไม่ได้ทดสอบ
การวัดเหล่านี้ครอบคลุมแค่ระเบียบการ crawl บน fixture ที่ควบคุมได้ และไม่ครอบคลุมอะไรที่กว้างกว่านั้น ข้อจำกัดนอกขอบเขตมีดังนี้:
- Dedup ข้ามการ crawl และ recrawl ผมวัดเฉพาะ content-digest dedup ภายใน crawl เดียว การเก็บ URI history database ข้าม crawl แยกกัน (
FetchHistoryProcessor+PersistLog) เป็นกลไกคนละแบบ และผมไม่ได้ทดสอบมัน - ขนาดงานและเสถียรภาพระยะยาว ไม่มี frontier ระดับล้าน URI, ไม่มี checkpoint-and-restore และไม่มีการรันหลายวัน fixture ของผมวัดความมีวินัย ไม่ได้วัดความอึด
- การจับเนื้อหาแบบเรนเดอร์ด้วย JavaScript ค่าเริ่มต้นของ Heritrix เป็นแบบไม่ใช้ browser ซึ่งนั่นคือสิ่งที่ผมวัด ส่วนพฤติกรรมแบบ browser-based ที่เป็นตัวเลือกยังไม่ได้ทดสอบตรงนี้
- Politeness กับ origin ที่ latency สูง latency ในเครื่องต่ำกว่ามิลลิวินาที ดังนั้น
minDelayMsจึงเป็นตัวกำหนดหลักโดยธรรมชาติ ผลของdelayFactorเมื่อเทียบกับเซิร์ฟเวอร์จริงที่ช้ากว่านี้ยังแยกไม่ออกจากข้อมูลชุดนี้ - การเก็บ sitemap แบบครบถ้วน robots.txt ถูก request และ directive ของ sitemap ถูกทำตาม แต่ผมไม่ได้ยืนยันแยกต่างหากว่าทุก
<loc>ถูกดึงครบหรือไม่
สิ่งที่ควรทำให้ชัดก่อนงาน production แรก
โปรไฟล์มาตรฐานมีรายละเอียดเยอะ แต่การตัดสินใจที่เปลี่ยนความหมายของ archive จริง ๆ แล้วมีไม่มาก เริ่มจาก scope ก่อน Seed จะสร้าง SURT prefix เริ่มต้น และ DecideRules สามารถขยายหรือจำกัดมันตามลำดับ ตรวจสอบลำดับสุดท้ายด้วย URL ตัวอย่างทั้งใน scope และนอก scope จากนั้นยืนยันกับทราฟฟิกฝั่งเซิร์ฟเวอร์หรือ log การร้องขอจากแหล่งอื่นที่เป็นอิสระ Crawl report เพียงอย่างเดียวพิสูจน์ไม่ได้ว่า host ที่ถูกยกเว้นไม่เคยถูกติดต่อเลย
ต่อมาคือการตัดสินใจว่า robots policy และตัวตนของผู้ปฏิบัติงานมีความหมายอย่างไรต่อคอลเลกชัน ค่าเริ่มต้นที่ทดสอบจะ obey กฎ disallow ของ fixture ส่วนการเปลี่ยน robotsPolicyName เป็น ignore ทำให้ path ที่ถูกบล็อกถูก fetch ได้ สวิตช์นี้เรียบง่ายในเชิงกลไกแต่สำคัญมากในเชิงสถาบัน บันทึกไว้ว่าใครอนุมัติและเพราะอะไร ควบคู่กับ operatorContactUrl ที่ใช้งานได้จริง; URL ที่บังคับให้ใส่ช่วยให้เจ้าของเว็บไซต์ติดต่อกลับไปหาผู้ปฏิบัติงานได้ แต่ไม่ได้ให้เหตุผลด้านอำนาจการอนุมัติ
การวางแผนพื้นที่จัดเก็บต้องมีการตัดสินใจชัดเจนของตัวเอง ถ้าคุณต้องการให้ body ที่เหมือนกันกลายเป็น revisit record ให้เพิ่มและตรวจสอบ content-digest history processors ก่อนประเมินขนาด archive chain ที่ทดสอบมีผลต่อ representation หลัง fetch ดังนั้นควรเผื่องบทราฟฟิกฝั่งต้นทางสำหรับทั้งสอง URL เช่นเดิม การ dedup ข้าม crawl เป็นกลไกอีกแบบหนึ่ง และไม่ควรสรุปเอาจากผลสอง URL ใน crawl เดียวนี้ การทำ validation crawl ขนาดเล็กด้วย body ที่รู้แน่ว่าเหมือนกัน เป็นวิธีราคาถูกในการยืนยันว่า bean graph ที่ deploy ออกมาสร้าง record ประเภทที่ต้องการจริง
สุดท้าย ให้มอง politeness เป็น input ของการจัดตารางงาน มากกว่าจะเป็นปุ่มปรับหน้างานนาทีสุดท้าย บน fixture local host เดียว minDelayMs เป็นตัวกำหนดเวลารวมทั้งหมด โปรเจกต์จริงควรคำนวณ floor ต่อ host ที่ตั้งค่าไว้เทียบกับจำนวน target host และเส้นตายของการเก็บข้อมูล จากนั้นทดสอบบน latency ที่ใกล้เคียงของจริง การ crawl แบบกว้างและแบบไซต์เดียวจะกดดัน frontier ที่แยกตาม host แตกต่างกัน รีวิวนี้วัดเฉพาะแบบหลังเท่านั้น และอย่าลืมเก็บ lifecycle แบบ REST ไว้ใน runbook ด้วย: build, launch, unpause, poll, terminate และ teardown เป็น state แยกกันที่ควรติดตามในระบบอัตโนมัติ
ข้อดีและข้อเสีย
ข้อดี
- ความครบถ้วนของ archive ดีมากตั้งแต่ค่าเริ่มต้น: response ครบ 20/20 พร้อม payload digest, capture IP และการเชื่อม request↔response แบบเต็ม โดยไม่ต้องตั้งค่าเพิ่ม
- เก็บ response ที่เป็น error ไว้เป็นข้อเท็จจริง — status line แบบ 200, 404 และ 500 ถูกเก็บตรงตัวทั้งหมด
- ยืนยัน discipline ของ scope ด้วยตัวนับฝั่งเซิร์ฟเวอร์: ไม่มีการ fetch นอก scope ในขณะที่ host ภายใน scope crawl ได้ตามปกติ
- การ obey robots ทำให้ fetch ถูกหยุดจริง และมีทางเลือก
ignoreที่ตั้งใจไว้สำหรับงาน archive ที่จำเป็นต้อง override - ทำงาน headless ผ่าน REST ได้ครบ — สร้าง, build, launch, poll, teardown ผ่าน
curlทั้งหมด ไม่ต้องคลิก UI - รันบน OpenJDK 26.0.1 ได้สะอาด โดยไม่ต้องใส่ JVM flags ซึ่งดีกว่าความเป็นระเบียบของโค้ดเก่า 20 ปีจำนวนมาก
operatorContactUrlที่บังคับให้ใส่ทำให้ crawler ไม่สามารถทำงานแบบไม่ระบุตัวตนได้- ทุกส่วนของ crawl เป็น bean ที่สลับได้ จึงเปิด dedup ได้ด้วยการเพิ่ม bean 3 จุด ไม่ใช่การ fork โค้ด
ข้อเสีย
- content-digest dedup ปิดอยู่โดยค่าเริ่มต้น และเขียน payload ที่เหมือนกันลงไปเต็ม ๆ — เป็นกับดักด้านการวางแผนพื้นที่จัดเก็บจริง ๆ
- การ deploy หนัก: distribution 41 MB, jar 114 ไฟล์, Java engine พร้อม Jetty และ job config Spring ราว 750 บรรทัด
- politeness ค่าเริ่มต้นบังคับ floor ต่อ host ประมาณ 3 วินาที; crawl 20 URI บน host เดียวใช้เวลา 57.7 วินาที
- ไม่มี JavaScript rendering ในเส้นทางมาตรฐาน ดังนั้นเนื้อหาที่อยู่ฝั่ง client อย่างเดียวจะไม่ถูกเก็บ
- พื้นที่คอนฟิกเปิดกว้างและต้องอาศัยความเชี่ยวชาญ; ไม่มีทางลัดห้านาทีเพื่อ crawl แรก
- สิ่งที่ได้ออกมาไม่ใช่ข้อมูลที่มีโครงสร้าง การดึงฟิลด์ออกจาก WARC เป็นอีกโปรเจกต์หนึ่งต่างหาก
ใครควรใช้ และใครควรถอย
Heritrix เหมาะกับสถาบันและทีมที่สิ่งส่งมอบคือ archive เอง เช่น ห้องสมุด หอจดหมายเหตุแห่งชาติ งานเก็บรักษาตามกฎหมายและ compliance กลุ่มวิจัยที่เก็บเว็บเป็นแหล่งข้อมูลปฐมภูมิ หรือใครก็ตามที่ต้องพิสูจน์ให้ได้ในอีกห้าปีข้างหน้าว่า URL หนึ่งตอบกลับอะไรในวันใดวันหนึ่ง ถ้าคำว่า "WARC," "replay" และ "provenance" อยู่ในคลังศัพท์ของคุณแล้ว นี่คือเครื่องมือที่ระบบรอบตัวคุณถูกสร้างมาให้รองรับ และน้ำหนักของมันคือราคาของความเข้ากันได้นั้น
frontier ที่แยกตาม host และประวัติการใช้งานระยะยาวในงาน web archiving ทำให้มันเป็นตัวเลือกที่มีเหตุผลสำหรับ crawl ขนาดใหญ่ข้ามหลายโดเมน นั่นเป็นข้อพิจารณาด้านสถาปัตยกรรมและประวัติโครงการ ไม่ใช่ผลวัดด้านสเกลจาก fixture นี้; throughput ระยะยาว การกู้คืนจาก checkpoint และพฤติกรรมระดับล้าน URI ยังไม่ได้ทดสอบในที่นี้
ไม่ควรใช้ ถ้าคุณต้องการข้อมูลมากกว่า archive ถ้าเป้าหมายคือสเปรดชีตของสินค้า รายการประกาศ หรือรายชื่อผู้ติดต่อ Heritrix จะทำหน้าที่ capture หน้าเว็บได้อย่างงดงาม แล้วคุณต้องไปเขียน pipeline แยกเองเพื่อ parse เอาข้อมูลออกมา — และคุณจะต้องจ่ายด้วย Java engine, Spring config และ floor การสุภาพ 3 วินาทีไปด้วย ไม่ควรใช้เช่นกันถ้าเป้าหมายเป็น single-page app ที่เรนเดอร์ฝั่ง client ซึ่ง non-browser fetcher จะเก็บได้แค่ shell ไม่ใช่เนื้อหา และไม่ควรใช้หากคุณต้องการผลลัพธ์แรกภายในวันนี้ เพราะเส้นโค้งการติดตั้งนั้นมีอยู่จริง
ทางเลือกอื่น และบทบาทของ managed API
ในสาย archiving ตัวคู่เทียบโดยตรงในยุคปัจจุบันคือ Browsertrix Crawler — archiver แบบ browser-based ที่ขับ Chromium และบันทึกสิ่งที่ browser ทำจริง มันสามารถเก็บเนื้อหาที่ JavaScript สร้างขึ้น ซึ่งเป็นสิ่งที่ response HTTP ค่าเริ่มต้นของ Heritrix ไม่มี แต่ก็เพิ่มภาระในการ deploy และ runtime ของ browser เข้าไปด้วย รีวิวนี้ไม่ได้รันเทียบกันแบบ head-to-head ดังนั้นจุดเริ่มต้นของการตัดสินใจคือความต้องการด้านการ capture: ถ้าเป็น state ที่ browser สร้างขึ้น ให้เอนเอียงไปหา browser archiver; ถ้าเป็นทรัพยากร HTTP ทั่วไป Heritrix คือเส้นทางธรรมชาติของมัน
รีวิวที่เกี่ยวข้อง: Browsertrix Crawler review.
สำหรับปัญหาอีกแบบหนึ่งโดยสิ้นเชิง — คุณไม่ได้ต้องการ archive แต่ต้องการข้อมูลที่มีโครงสร้างจากหน้าเว็บ — ให้เปรียบ Heritrix กับระบบ extraction โดยดูที่ output ไม่ใช่ถือว่าเป็น crawler แบบเดียวกัน Heritrix ฟรีและติดตั้งเองได้: คุณรัน JVM เอง ดูแลไฟล์ beans เอง ขนาดดิสก์เอง และจูน politeness เอง โมเดลนี้เหมาะเมื่อสิ่งที่ต้องส่งมอบคือ preservation
Disclosure: Thunderbit เป็นผลิตภัณฑ์ของผู้เผยแพร่ และไม่ได้ถูกทดสอบใน fixture ของ Heritrix นี้ มันอยู่ในหมวด managed extraction: output ของมันคือเนื้อหาของหน้าเว็บหรือ record ที่มีโครงสร้าง ไม่ใช่ไฟล์ WARC ระดับ preservation เลือก archiver เมื่อคุณต้องการการ capture ที่ replay ได้และมี provenance ชัดเจน; พิจารณาบริการ extraction เมื่อสิ่งที่ต้องส่งมอบคือแถวข้อมูลหรือข้อความในเอกสาร และการใช้บริการแบบ managed เป็นสิ่งที่ยอมรับได้
การเก็บถาวรมาพร้อมคำถามด้านสิทธิ์ของตัวเอง และไม่เหมือนกับ scraping Heritrix obey robots.txt เป็นค่าเริ่มต้น และบังคับให้คุณระบุตัวตนก่อนมันจะ fetch ไบต์แรก ซึ่งเป็น baseline ที่ดี — แต่การ crawl ที่ทำตาม robots ไม่ได้แปลว่าได้รับอนุญาตโดยอัตโนมัติ ลิขสิทธิ์ เงื่อนไขการใช้งาน ข้อมูลส่วนบุคคล และภารกิจของสถาบันคุณเอง ล้วนเป็นชั้นที่อยู่ด้านบน และ policy ignore มีไว้สำหรับองค์กรที่มีฐานทางกฎหมายในการใช้งาน ไม่ใช่ปุ่มสบายมือ ถ้าคุณกำลังตั้งโปรแกรมงานเก็บถาวร ควรเคลียร์ขอบเขตอำนาจให้เรียบร้อยก่อนดิสก์จะเต็ม และอ่าน ผลทางกฎหมายของ web scraping และ archiving ไว้ด้วยถ้านี่เป็นพื้นที่ใหม่สำหรับคุณ
ลอง Thunderbit สำหรับการดึงข้อมูลเว็บ
ข้อสรุป
ควรใช้ Heritrix ไหม? ควร — ถ้าสิ่งที่คุณต้องการส่งออกคือ archive และคุณมีคนพร้อมจะเรียนรู้ Spring beans
fixture นี้สนับสนุนการตัดสินใจอย่างชัดเจน: โปรไฟล์มาตรฐานเก็บ response, request และ capture metadata อย่างสม่ำเสมอ; scope และ robots control มีผลต่อ server-side fetch ตามที่ตั้งค่าไว้; และ lifecycle ของงานทั้งหมดรันผ่าน REST ได้ คุณสมบัติเหล่านี้มีประโยชน์มากสำหรับ archival pipeline ภายใต้ขอบเขต single-host ขนาดเล็กที่ทดสอบไว้ที่นี่
ต้นทุนเชิงปฏิบัติก็ชัดเจนพอ ๆ กัน: distribution ของ Java และ Spring configuration ขนาดใหญ่, delay ต่อ host ที่กำหนดไว้ซึ่งกลายเป็นตัวครองเวลาของ crawl ในเครื่อง, และ content-digest deduplication ที่ต้องเพิ่ม history processors เข้าไป ทีมที่ต้องการความเที่ยงตรงของ WARC อาจยอมรับต้นทุนเหล่านี้ได้; แต่ทีมที่ต้องการฟิลด์ที่สกัดออกมาแล้วควรเริ่มในหมวดเครื่องมืออีกแบบหนึ่ง
ก่อน crawl จริงใน production ให้ตรวจ chain ของ dedup, การตั้งค่า politeness, scope, robots policy, ตัวตนของผู้ปฏิบัติงาน และสมมติฐานเรื่องพื้นที่จัดเก็บ เทียบกับ job config จริง โปรไฟล์มาตรฐานเป็นแค่จุดเริ่มต้น ไม่ใช่คำยืนยันโดยนัยเกี่ยวกับตัวเลือกเชิงปฏิบัติการเหล่านั้น
ลอง Thunderbit สำหรับการดึงข้อมูลเว็บ Get Started Free
คำถามที่พบบ่อย
operatorContactUrl ทำให้ crawl ได้รับอนุญาตโดยอัตโนมัติหรือไม่?
ไม่ มันบังคับให้ job ต้องมี URL สำหรับติดต่อ ซึ่งช่วยให้เจ้าของเว็บไซต์มีช่องทางรับผิดชอบกลับมาได้ แต่ไม่ได้ยืนยันสิทธิ์ ความถูกต้องของตัวตน สถานะลิขสิทธิ์ หรือการปฏิบัติตามข้อกำกับใด ๆ สิ่งเหล่านั้นยังเป็นการตัดสินใจเชิง deployment ที่อยู่นอก crawler
content-digest deduplication ลดจำนวน request ที่ส่งไปหา origin หรือไม่? ไม่ ในคอนฟิกที่ทดสอบนี้ ทั้งสอง URL ถูก fetch ก่อนที่จะนำ payload digest มาเปรียบเทียบได้ chain ของ history เปลี่ยนวิธีนำ payload ที่สองไปแทนค่าใน WARC storage เท่านั้น มันไม่ได้ทำให้ URL ที่สองกลายเป็น network request ที่ถูกข้าม
Heritrix รันได้โดยไม่ต้องใช้ web UI หรือไม่?
ได้ lifecycle ที่ทดสอบ — สร้าง, อัปโหลดคอนฟิก, build, launch, unpause, poll, terminate และ teardown — รันผ่าน REST API ด้วย curl ทั้งหมด UI ของ embedded Jetty ไม่ได้จำเป็น
โปรไฟล์มาตรฐานของ Heritrix dedup payload ที่เหมือนกันหรือไม่? ไม่ใน fixture และคอนฟิกที่ใช้ โปรไฟล์มาตรฐานเก็บ response ที่เหมือนกันทั้งสองรายการเป็น response record เต็ม ๆ เมื่อเพิ่ม content-digest history chain เข้าไป การจับครั้งที่สองจึงกลายเป็น revisit record ดังนั้น deduplication เป็นตัวเลือกใน pipeline ที่คุณต้องตั้งค่าและตรวจสอบเอง ไม่ใช่ค่าเริ่มต้นอัตโนมัติ
ควรเลือก delay ของ politeness อย่างไร? ให้มอง timing ที่วัดได้ในที่นี้เป็นการตรวจกลไก ไม่ใช่คำแนะนำสำหรับ production ตั้ง delay จากกฎของไซต์เป้าหมาย ข้อตกลงกับผู้ปฏิบัติงาน ความสามารถของเซิร์ฟเวอร์ วัตถุประสงค์ของ crawl และนโยบาย retry/concurrency ของคุณเอง แล้วค่อยยืนยันช่วงห่างระหว่าง request ไป host เดียวกันจาก log จริง


