รีวิว Apache Nutch: ขอบเขต 4 ด้านที่เป็นตัวตัดสินว่ามันจะรันได้หรือไม่ และจะค้นหาเจออะไรบ้าง

อัปเดตล่าสุดเมื่อ August 14, 2026
รีวิว Apache Nutch: ขอบเขต 4 ด้านที่เป็นตัวตัดสินว่ามันจะรันได้หรือไม่ และจะค้นหาเจออะไรบ้าง
สรุปด้วย AI

Apache Nutch คือโปรแกรม crawler ของ Apache Software Foundation ที่เริ่มพัฒนาตั้งแต่ปี 2004 เป็นระบบบน JVM ที่สร้างบน Hadoop และทำงานเป็นรอบๆ ไม่ใช่คำสั่งสตรีมมิ่งคำสั่งเดียว: เริ่มจากนำ seed URLs เข้าไปด้วย inject ลงในฐานข้อมูลถาวร จากนั้นวน generate → fetch → parse → updatedb พร้อมช่องเสียบปลั๊กอินสำหรับ protocol, parser, URL filter และ scoring โดยผลลัพธ์ปกติจะส่งต่อไปยัง search index อย่าง Solr หรือ Elasticsearch มากกว่าจะออกมาเป็น CSV ผมทดสอบ Nutch 1.22 กับเว็บทดสอบในเครื่องที่ควบคุมได้อย่างดี — เว็บที่บันทึกทุก request ฝั่งเซิร์ฟเวอร์ ทำให้ประเมินผลจากสิ่งที่เซิร์ฟเวอร์เห็นจริง ไม่ใช่จากสิ่งที่ crawler อ้างว่าได้มา

Apache Nutch คือโปรแกรม crawler ของ Apache Software Foundation ที่เริ่มพัฒนาตั้งแต่ปี 2004 เป็นระบบบน JVM ที่สร้างบน Hadoop และทำงานเป็นรอบๆ ไม่ใช่คำสั่งสตรีมมิ่งคำสั่งเดียว: เริ่มจากนำ seed URLs เข้าไปด้วย inject ลงในฐานข้อมูลถาวร จากนั้นวน generate → fetch → parse → updatedb พร้อมช่องเสียบปลั๊กอินสำหรับ protocol, parser, URL filter และ scoring โดยผลลัพธ์ปกติจะส่งต่อไปยัง search index อย่าง Solr หรือ Elasticsearch มากกว่าจะออกมาเป็น CSV

ผมทดสอบ Nutch 1.22 กับเว็บทดสอบในเครื่องที่ควบคุมได้อย่างดี — เว็บที่บันทึกทุก request ฝั่งเซิร์ฟเวอร์ ทำให้ประเมินผลจากสิ่งที่เซิร์ฟเวอร์เห็นจริง ไม่ใช่จากสิ่งที่ crawler อ้างว่าได้มา ขอบเขต 4 อย่างเป็นตัวกำหนดผลการรันทั้งหมด ได้แก่ เวอร์ชัน JDK, http.agent.name, ขอบเขตการ crawl และการมี parse-js อยู่ใน plugin.includes วนรอบการทำงานเต็มชุดด้วยคอนฟิกที่ทดสอบซ้ำหลายครั้ง; ถ้าเปลี่ยน JDK หรือปล่อยให้ identity ของ agent ว่างไว้ มันจะหยุดก่อนจะไปดึงหน้าที่มีประโยชน์ได้

ขอบเขตด้าน JDK จะโผล่ขึ้นมาก่อนเริ่ม crawl ด้วยซ้ำ Nutch 1.22 รันบน JDK 26.0.1 ในเครื่องนี้ไม่ได้: Hadoop job แรกตายตั้งแต่ Subject.getSubject() เพราะ Java ถอดเส้นทาง SecurityManager ออกไปแล้ว Nutch แพ็ก Hadoop 3.4.2 มาให้ แต่ตัวแก้ไขปัญหาเพิ่งเข้ามาใน Hadoop 3.4.3 หลัง Nutch 1.22 ออกไป 7 วัน ขณะเดียวกัน parse-js เปลี่ยนการกู้คืน literal ในไฟล์ JavaScript 2 จุดจาก 0/2 เป็น 2/2 โดยไม่ต้องเปิดบราวเซอร์

Nutch มีไว้ทำอะไร และไม่ได้มีไว้ทำอะไร

Nutch ไม่ใช่ scraper งานมันไม่ใช่การดึงฟิลด์แบบมีโครงสร้างเป็นหลัก แต่เป็นการค้นหาและดึง URL จำนวนมาก เก็บฐานข้อมูลถาวรของ URL และสถานะต่างๆ ของมัน (crawldb) แล้วส่ง segment ให้เครื่องมืออื่นเอาไปแปลงเป็น index ถ้าคุณชี้มันไปที่แค็ตตาล็อกแล้วหวังจะได้ตารางชื่อกับราคา คุณจะได้ crawldb แทน

สถาปัตยกรรมแบบนี้อธิบายสิ่งที่จะเห็นต่อจากนี้ได้เกือบทั้งหมด Nutch เกิดมาก่อนยุค crawler แบบไฟล์เดียวอยู่ราวสองทศวรรษ และถูกสร้างมาเพื่อปัญหาแบบเดียวกับที่ Hadoop ถูกสร้างมา: crawl หน้าเว็บให้ได้มากกว่าที่เครื่องเดียวจะรับไหว การเอามันไปรันบนแล็ปท็อปเพื่อดึง fixture 12 หน้า ก็เหมือนเช่ารถไฟบรรทุกสินค้ามาย้ายชั้นหนังสือ — ได้ข้อมูลเชิงเปรียบเทียบที่มีประโยชน์เกี่ยวกับตัวรถไฟ แต่ไม่ยุติธรรมถ้าจะคาดหวัง ergonomics แบบจักรยาน

รุ่นปัจจุบันคือ 1.22 ประกาศเมื่อ 17 กุมภาพันธ์ 2026 โค้ดอยู่ภายใต้ Apache-2.0 รีโปมี 3,272 stars และ 8 open issues ตอนที่ผมเช็กเมื่อ 27 กรกฎาคม 2026 และ master เพิ่งถูก push ไปเมื่อ 4 วันก่อนหน้านั้น นี่คือโปรเจกต์ที่ยังดูแลต่อ ไม่ใช่ของที่ถูกทิ้ง — ซึ่งเป็นมุมมองที่ถูกต้องสำหรับปัญหา JDK: เป็นหน้าต่างของแพ็กเกจจิ้งที่ปิดช้าไปหนึ่งสัปดาห์ ไม่ใช่ความละเลย

ตารางเวอร์ชัน: JDK 24+, Hadoop 3.4.2 และการแก้ไขสองบรรทัด

ตัวบล็อกเป็นปฏิสัมพันธ์ของเวอร์ชัน 3 ฝ่าย และส่วนที่คุณควบคุมได้มีแค่ว่า Nutch จะรันบน JDK ตัวไหน Job Hadoop ตัวแรกสุดบน JDK ค่าเริ่มต้นของเครื่องล้มตอนเริ่มต้น:

java.lang.UnsupportedOperationException: getSubject is not supported
    at java.base/javax.security.auth.Subject.getSubject(Subject.java:277)
    at org.apache.hadoop.security.UserGroupInformation.getCurrentUser(UserGroupInformation.java:588)
    at org.apache.nutch.crawl.Injector.inject(Injector.java:473)

return code 255 ไม่มีหน้าไหนถูกดึงเลย bin/nutch inject ยังไม่ทันแตะเครือข่ายด้วยซ้ำ — มันเริ่ม LocalJobRunner ของ Hadoop แล้วถามว่าผู้ใช้ปัจจุบันคือใคร ซึ่งจะไปเรียก Subject.getSubject() และ JEP 486 ทำให้มันกลายเป็น exception ทันทีเมื่อ JDK 24 เอา SecurityManager ออกอย่างถาวร JDK ที่เครื่องผมใช้คือ OpenJDK 26.0.1 ซึ่งเลยเส้นนั้นไปไกลแล้ว

ทางออกแบบเดิมก็ใช้ไม่ได้เหมือนกัน การใส่ -Djava.security.manager=allow ซึ่งเคยเปิดพฤติกรรมเก่าได้ จะถูก VM ปฏิเสธตั้งแต่ก่อนโค้ดของ Nutch จะโหลดด้วยซ้ำ:

Error occurred during initialization of VM
java.lang.Error: A command line option has attempted to allow or enable the Security
Manager. Enabling a Security Manager is not supported.

นั่นคือ return code 1 และเป็นทางตันโดยตั้งใจ — เพราะ flag นี้ถูกถอดออกไปพร้อมกับฟีเจอร์ดังกล่าว

ต้นตออยู่ที่ Hadoop เวอร์ชันที่ Nutch 1.22 แพ็กมาให้ ปัญหา getSubject ถูกติดตามไว้ใน HADOOP-19212 และแก้แล้วใน Hadoop 3.4.3 และ 3.5.0 แต่ Nutch 1.22 ยัง bundle hadoop-common-3.4.2 มา Nutch 1.22 ออกวันที่ 17 กุมภาพันธ์ 2026 และ Hadoop 3.4.3 ตามออกมาราวหนึ่งสัปดาห์หลังจากนั้น

และนี่ก็ไม่ใช่ปัญหาเรื่องการต้องมี Solr หรือ Hadoop cluster ถึงจะทำอะไรได้เลย หลายคนชอบคิดว่า Nutch ต้องมี Hadoop cluster และ Solr ที่รันอยู่ก่อนถึงจะทำงานได้ ซึ่งไม่จริง local mode ใช้ LocalJobRunner ของ Hadoop แบบ in-process — ไม่ต้องมี HDFS daemon, ไม่ต้องมี YARN, ไม่ต้องมี cluster ทั้งหมด inject → generate → fetch → parse → updatedb ทำงานบนเครื่องเดียวได้โดยไม่ต้องติดตั้งอะไรเพิ่ม ขอบเขตของ JDK เป็นเพียงปัญหาเวอร์ชันของไลบรารีที่ bundle มาด้วย และมันหยุดคุณก่อนที่คำถามเรื่อง infrastructure พวกนั้นจะเริ่มขึ้นด้วยซ้ำ

ตารางเวอร์ชันที่ใช้งานจริง มีการวัดครบทั้ง 3 แถว:

JDK ที่ใช้คำสั่งผลลัพธ์
OpenJDK 26.0.1bin/nutch injectล้มเหลว, rc=255 — UnsupportedOperationException: getSubject is not supported
OpenJDK 26.0.1bin/nutch inject + -Djava.security.manager=allowล้มเหลว, rc=1 — VM ปฏิเสธการเริ่มทำงาน
OpenJDK 17.0.20 (LTS)bin/nutch injectสำเร็จ, rc=0 — Total new urls injected: 1

วิธีแก้มี 2 คำสั่ง ติดตั้ง JDK แบบ LTS แล้วชี้ Nutch ไปที่มัน:

brew install openjdk@17
export NUTCH_JAVA_HOME=/opt/homebrew/opt/openjdk@17

เพราะเป็นแบบ keg-only จึงไม่ไปแตะ JDK ค่าเริ่มต้นของระบบ CI ของ Nutch เองก็ใช้ Java 17 และโปรเจกต์ได้ ประกาศอย่างเป็นทางการ ว่า 1.22 คือ release สุดท้ายที่รันบน Java 11 ได้ และ 1.23 จะบังคับใช้ Java 17 ดังนั้น JDK แบบ LTS ไม่ใช่ workaround แต่มันคือคอนฟิกที่รองรับ ความไม่ตรงกันคือระหว่างสิ่งที่ Nutch รองรับกับสิ่งที่ brew install openjdk ส่งมาให้ในปี 2026 ซึ่งเป็นคนละคำถาม แต่บังเอิญชนกันตรงคำสั่งแรกสุด

หลังจากนั้นทุกอย่างที่ทดสอบต่อจากนี้รันบน OpenJDK 17.0.20 ซึ่ง cycle ทั้งหมดทำงานได้เรียบร้อย

การตั้งค่าแบบวัดได้: 396 MB และ property เดียวที่บล็อกทุกอย่าง

คำว่า “หนัก” เป็นคำที่คนชอบใช้กับ Nutch แต่ถ้าไม่ชั่งจริงก็ไม่มีความหมาย นี่คือสิ่งที่ binary distribution ของ Nutch 1.22 เมื่อแตกไฟล์แล้วมีอยู่จริง:

รายการNutch 1.22 binary distribution
ขนาดหลังแตกไฟล์≈396 MB
จำนวน jar ใน lib/188 (≈113 MB)
— ในจำนวนนี้เป็น Hadoop stack ที่ bundle มา13
ไดเรกทอรีปลั๊กอิน78
จำนวน jar ภายในไดเรกทอรีปลั๊กอินเหล่านั้น533
ไฟล์คอนฟิก35
สคริปต์ใน bin/2 — crawl และ nutch

ถ้าเทียบสเกลกับ crawler สมัยใหม่อย่าง katana จะมาเป็น binary เดียวราว 50 MB ไม่มี JVM และไม่มี external jars

แล้วก็มีประตูด่านหนึ่งที่ไม่มีใครบอกไว้ nutch-site.xml ที่แถมมานั้นว่างเปล่า และ http.agent.name ถูกตั้งค่าเริ่มต้นเป็นสตริงว่าง ถ้าไม่กำหนด คำสั่ง crawl แรกของผมจะดึงได้ 0 paths และบันทึกว่า:

ERROR Fetcher: No agents listed in 'http.agent.name' property.

แค่ตั้ง property นี้ตัวเดียว — อย่างอื่นไม่ต้องแตะ — มันก็เปลี่ยนเป็น fetch ได้จริง ถ้าปล่อยให้ property ว่าง คำสั่งจะจบแบบไม่ดึงหน้า และ log จะรายงาน error เรื่อง agent-name ข้างต้น ไม่ได้เงียบหายไปเฉยๆ

คอนฟิกขั้นต่ำที่ใช้งานได้จริงมี 3 อย่าง: conf/nutch-site.xml (agent name, plugin set, scope), conf/regex-urlfilter.txt (host scoping) และไฟล์ seed URL แค่นี้ไม่เยอะหรอก แค่มีมากกว่าคำสั่ง crawler run <url> อยู่ 3 ไฟล์

มันหาเจออะไรบ้าง: จุดตัดสินอยู่ที่ปลั๊กอินตัวนั้น

System diagram: What it found: the plugin toggle that matters

เว็บทดสอบมี endpoint อยู่ 3 กลุ่มที่ตั้งใจทำให้ต่างกัน และพฤติกรรมของ Nutch แยกผลออกจากกันได้ชัดเจน:

  • Class A — ลิงก์ HTML ธรรมดา (4 หน้า และเส้นทางต่อกันลึก 3 ชั้น)
  • Class B — endpoint ที่มีอยู่แค่เป็น string literal อยู่ในไฟล์ JavaScript ที่ถูกลิงก์มา: แบบหนึ่งเป็น call argument fetch('/api/js-endpoint-7') อีกแบบเป็น assignment const other = "/api/js-endpoint-8"
  • Class C — endpoint ที่มีอยู่หลังจาก JavaScript ทำงานแล้ว inject ลง DOM เท่านั้น

ผลลัพธ์จาก log การ hit ฝั่งเซิร์ฟเวอร์ ทำซ้ำ 3 รอบ:

คอนฟิกปลั๊กอินClass A (ลิงก์ HTML)Class B (JS-file literals)Class C (runtime DOM)
ค่าเริ่มต้นที่แถมมา — parse-(html|tika)4/4 (recall 1.0)0/2 (recall 0.0)ไม่ถึง
เปิด parse-jsparse-(html|tika|js)4/4 (recall 1.0)2/2 (recall 1.0)ไม่ถึง

ได้ผลเหมือนกันทุกครั้งทั้ง 3 รอบ Deterministic

สิ่งที่หลายคนประเมินต่ำไปคือการกระโดดของ class B Nutch หา endpoint ที่ฝังอยู่ใน JavaScript เจอทั้งสองแบบ โดยไม่ต้องรันบราวเซอร์ อาศัยการสแกน JavaScript แบบ regex ของปลั๊กอิน parse-js ไฟล์ app.js เองถูก fetch ทั้งในคอนฟิกทั้งสองชุด — เพราะ Nutch มอง <script src> เป็น outlink อยู่แล้ว ความต่างทั้งหมดจึงอยู่ที่มีอะไรอ่าน เนื้อหาในไฟล์ เพื่อหา string ที่หน้าตาเหมือน URL หรือไม่ เปิดปลั๊กอินนี้ มันก็จับได้ทั้งสองรูปแบบ literal

บน fixture นี้ default ของ Nutch และโหมดมาตรฐานของ katana ไปถึงชุด class A เหมือนกัน ขณะที่ Nutch เปิด parse-js และ katana เปิด -jc ไปถึง class A และ B โดยไม่ต้องใช้บราวเซอร์ แต่ในบทความนี้ไม่ได้บันทึกเวอร์ชันและคำสั่งเต็มของ katana ดังนั้นผลนั้นจึงเป็นบริบทประกอบ มากกว่าจะเป็น benchmark แบบเป๊ะๆ ของโปรดักต์

Class C คือเพดานที่ตรงไปตรงมา ไม่มีคอนฟิกแบบ static ใดไปถึงได้ ซึ่งก็สมเหตุสมผล: การกู้ endpoint ที่มีอยู่หลัง script ทำงานแล้ว จำเป็นต้องรัน script จริงๆ ผมลองสลับ protocol-http เป็น protocol-htmlunit ซึ่งเป็น protocol ของ Nutch ที่รัน JavaScript ด้วย Java ล้วนๆ มันโหลดและรันได้โดยไม่ crash แต่ใน harness เดิม 4 รอบ มันจบรอบได้แค่รอบเดียว ดึงได้แค่ seed page กับ app.js ไม่ไปถึง A/B/C และรอบที่สองรายงานว่า 0 records selected for fetching นั่นคือ probe ที่ตั้งค่าไม่พอใช้งาน ไม่ใช่คำตัดสินต่อความสามารถของ HtmlUnit สิ่งที่สรุปได้แคบกว่านั้นคือ: การสลับไปใช้ protocol ที่รัน JavaScript ได้ ไม่ใช่การเปลี่ยนแบบเสียบแทนได้ทันที และ class C ก็ยังไม่ถูกเข้าถึงในทุกคอนฟิกที่ผมลอง

การควบคุม crawl และพฤติกรรมเมื่อล้มเหลว

Depth ไม่ใช่ flag ใน Nutch ไม่มี --depth 3 depth คือจำนวนรอบ generate → fetch → parse → updatedb ที่คุณรัน เพราะรอบ R จะ fetch frontier ที่ค้นเจอในรอบ R-1 ชุดทดสอบ depth chain ยืนยันเรื่องนี้ชัดเจน:

จำนวนรอบที่รันเส้นทางที่ลึกที่สุดที่ไปถึง
2/depth/1
3/depth/2
4/depth/3

ตรงไปตรงมาและเป็นกลไกชัดเจน แต่แปลว่าความลึกเป็นตัวนับ loop ในสคริปต์ของคุณ ไม่ใช่พารามิเตอร์

ทีนี้มาถึงกับดัก ค่าเริ่มต้นที่แถมมาของ Nutch คือ db.ignore.external.links=false ควบคู่กับ URL filter แบบเปิดกว้าง +. ซึ่งหมายความว่า crawl ค่าเริ่มต้นจะตามลิงก์ออกไปนอก host ต้นทางของคุณ ผม seed หน้าที่มีลิงก์ไปยัง path ที่อยู่ใน scope หนึ่งอัน และลิงก์ไปยัง hostname อื่นอีกอัน ผลคือ crawl ไปดึง host ภายนอกจริง สัญญาณสองทางยืนยันตรงกัน: crawldb ของ Nutch ติดสถานะ db_fetched และ counter ฝั่งเซิร์ฟเวอร์ของ host นั้นก็นับ hit ได้

ถ้าจะให้อยู่ในขอบเขต ต้องตั้งเอง และทั้งสองวิธีก็ใช้ได้จริง:

คอนฟิกexternal host ใน crawldbhit ฝั่งเซิร์ฟเวอร์ของ external hostอยู่ในขอบเขตไหม
db.ignore.external.links=false (ค่าเริ่มต้นที่แถมมา)db_fetched+1ไม่
db.ignore.external.links=trueไม่มี0ใช่
กฎ host ใน regex-urlfilter.txt (+^http://127.0.0.1: แล้วตามด้วย -.)ไม่มี0ใช่

ถ้าคุณ crawl แค่ไซต์เดียว ให้ตั้งอย่างใดอย่างหนึ่งก่อนเริ่มรันจริง ข้อควรระวังของวิธีทดลองนี้คือมีความไวต่อโหลดของเซิร์ฟเวอร์ในเครื่อง ดังนั้น 3 แถวนั้นมาจากการรันที่ไม่มีอย่างอื่นแตะ fixture เลย พฤติกรรมจริงนั้นชัดเจนและมีสัญญาณอิสระสองทางรองรับ ส่วนค่าที่เห็นในแต่ละแถวเป็นผลจากรันที่สะอาดหนึ่งครั้ง ไม่ใช่ค่าเฉลี่ยจากหลายรอบ

Sitemap เป็นขั้นตอนแยกต่างหาก ความสุภาพของ crawler เปิดอยู่ — crawl ปกติจะไปดึง /robots.txt — แต่ sitemap เองต้องสั่งต่างหาก:

วิธีมีการ request /sitemap.xml ไหมendpoint ที่มีอยู่แค่ใน sitemap
crawl ปกติไม่เคยถูก request0/2
bin/nutch sitemap รันตรงกับ crawldbถูก fetch2/2 entries ถูก inject, recall เต็ม
โหมด known-files แบบ inline ของ katana -kf กับ fixture เดียวกัน บน host แบบ IPไม่ได้บันทึก0/2

มันเป็นโมเดลคนละแบบกับ crawler ที่ดึง known files แบบ inline และคุณต้องเสียคำสั่งเพิ่มอีกหนึ่งครั้ง แต่ก็ทำงานได้ครบ

พฤติกรรมเล็กๆ สองอย่างยังคงดีอยู่

การจัดการ error: crawl หน้าที่ลิงก์ไปยัง 500 และ 404 จบรอบทั้งหมดได้เรียบร้อย ยังดึง class A ครบทั้ง 4 หน้า และบันทึก failure แต่ละแบบต่างกันชัดเจน:

response ที่เสียหายซึ่งถูกลิงก์จากหน้าสถานะใน crawldb
500db_unfetched (ยังมีสิทธิ์ลองใหม่)
404db_gone

ไม่มีอะไรพังทั้งชุด

ความสุภาพในการ crawl: ถ้าใช้หนึ่งเธรดต่อคิว ช่องว่างเวลาระหว่างการ fetch host เดียวกันจะตามค่าที่ตั้งไว้:

fetcher.server.delayช่องว่างมัธยฐานระหว่าง fetch ของ host เดียวกัน
1.0 วินาที1.009 s (ต่ำสุด 1.006 s)
0.00.002 s

ปุ่มนี้ทำงานตรงตามชื่อ ค่าเริ่มต้นที่แถมมาคือ 5.0 วินาที ซึ่งค่อนข้างระมัดระวัง และก็น่าจะเหมาะกับเครื่องมือที่ออกแบบมาเพื่อ crawl เซิร์ฟเวอร์ของคนอื่น

ต้นทุนของการทำงานแบบ batch ในหน่วยวินาที

ทุกคำสั่งของ Nutch คือ JVM ใหม่หนึ่งชุด ข้อเท็จจริงข้อนี้กำหนดโครงสร้างเวลาโดยรวมแรงกว่าเรื่อง fetch เสียอีก

เฟส (ต่อรอบ)มัธยฐานเป็นวินาที
inject (ครั้งเดียว)1.81
generate3.93
fetch2.82
parse1.78
updatedb1.81
หนึ่งรอบเต็ม12.14

เพดานต่อ job ที่แท้จริง — ค่าเริ่มต้นขั้นต่ำของ JVM startup บวก Hadoop initialization วัดจากเฟสงานเบาที่สุด — อยู่ที่ประมาณ 1.77 วินาที คูณด้วย 4 คำสั่งต่อรอบ แล้วบวก inject ครั้งแรก ภาพรวมของการ crawl ทั้งชุดจะออกมาประมาณนี้:

เครื่องมือcrawl fixture 12 หน้า ที่ depth 4จำนวน process
Nutchประมาณ 45 วินาที (ผมวัดได้ 45.8 s และ 45.0 s จาก 2 คอนฟิก)ราว 17 ครั้งที่ JVM ถูก launch ซึ่งแทบไม่มีอันไหนกำลังทำงานเครือข่ายอยู่
katana โหมด standard กับ fixture เดียวกันประมาณ 13 วินาที1 process

ช่องว่างนี้ไม่ได้มาจากอัตราการ fetch เพราะทั้งสองเครื่องมือร้องขอหน้าเว็บชุดเดียวกันเป็นจำนวนใกล้เคียงกัน แต่มาจากสถาปัตยกรรม Nutch ต้องจ่ายต้นทุนคงที่ต่อเฟสเพราะแต่ละเฟสถูกออกแบบเป็น MapReduce job บน crawl เล็กๆ ในเครื่องเดียว ค่า setup จึงครองเวลาไปแทบทั้งหมด ต้นทุนคงที่นี้ควรจะกลายเป็นสัดส่วนที่เล็กลงเมื่อ job ใหญ่ขึ้น แต่การทดสอบนี้ไม่ได้วัดสเกลที่ Nutch กับ katana จะเริ่มคุ้มคนละแบบ หรืออัตราส่วนอาจพลิกกลับหรือไม่

ข้อดีและข้อเสีย

ข้อดี

  • การค้นพบแบบ static มีความแน่นอน: class HTML 4/4, depth chain 3/3, ได้ผลเหมือนกันทุกครั้งจากการรันซ้ำ 3 รอบ
  • parse-js กู้ endpoint ที่อยู่ใน JavaScript file literal ได้ 2/2 โดย ไม่ต้องใช้บราวเซอร์ ทั้งแบบ call argument และ assignment
  • มีตัวควบคุม scope ที่ยืนยันผลได้ 2 แบบ ซึ่งทำให้ crawl อยู่ในขอบเขตได้จริง (db.ignore.external.links และ host rule ใน regex-urlfilter)
  • การ ingest sitemap ผ่าน bin/nutch sitemap ทำให้ได้ recall เต็ม 2/2 สำหรับ endpoint ที่ normal crawl มองข้ามไปหมด
  • ทนต่อความล้มเหลวได้ดี: 500 และ 404 ถูกจัดการด้วยสถานะ crawldb ที่ต่างกัน และ crawl ยังเดินต่อได้
  • ในการรัน local นี้ ช่วงเวลาระหว่าง request host เดียวกันสอดคล้องกับ delay ที่ตั้งไว้ 1.0 วินาที ส่วนค่าเริ่มต้นที่แถมมาคือ 5.0 วินาที
  • Apache-2.0, มีการดูแลต่อเนื่อง, 78 plugins และ crawldb แบบถาวรที่เก็บสถานะราย URL ข้ามรอบการทำงาน
  • รันใน local mode ได้โดยไม่ต้องมี cluster, ไม่มี HDFS และไม่ต้องติดตั้ง Solr

ข้อเสีย

  • รันไม่ได้บน JDK 24 หรือใหม่กว่า เพราะโดนผลจากการถอด SecurityManager (ผมวัดความล้มเหลวบน 26.0.1) — Hadoop 3.4.2 ที่ bundle มาเก่ากว่าตัวแก้ไข upstream และ escape flag ก็หายไปแล้ว ดังนั้นการ pin LTS JDK จึงเป็นข้อจำเป็น ไม่ใช่เรื่องความชอบ
  • แตกไฟล์แล้วใหญ่ประมาณ 396 MB, มี library jar 188 ไฟล์, directory ปลั๊กอิน 78 อัน และไฟล์คอนฟิก 35 ไฟล์
  • ทุกคำสั่งเปิด JVM ใหม่ ทำให้มี overhead คงที่ราว 1.77 วินาทีต่อเฟส; crawl depth 4 ของ 12 หน้าใช้เวลาประมาณ 45 วินาที เทียบกับ crawler ไฟล์เดียวที่ราว 13 วินาทีบนงานเดียวกัน
  • ค่าเริ่มต้นที่แถมมาตามลิงก์ไปยัง external host; ถ้าจะให้อยู่แค่ไซต์เดียวต้องตั้งเอง
  • http.agent.name ถูกส่งมาว่าง และ fetcher จะไม่ยอมทำงานจนกว่าจะตั้งค่า
  • ไม่มี depth flag — depth คือจำนวนรอบที่คุณต้องคุมเอง
  • endpoint ที่ต้องรัน JavaScript จริงเพื่อโผล่ใน DOM ไปไม่ถึงในทุกคอนฟิกที่ทดสอบ และการสลับไปใช้ protocol ที่รัน JavaScript ได้ไม่ใช่การเปลี่ยนแบบเสียบแทนทันที
  • ผมทดสอบแค่ local mode บน host เดียวกับ fixture ขนาดเล็ก ส่วน distributed/HDFS mode, Solr indexing, hostdb, resume และ incremental re-crawl scheduling ยังอยู่นอกการทดสอบรอบนี้ — ให้ถือว่ายังไม่ทดสอบ ไม่ใช่ว่ารับรองว่าใช้ได้

ใครควรใช้ และใครควรเลี่ยง

Nutch คุ้มค่าเมื่อ “การ crawl” คือส่วนที่ยากจริงๆ ถ้าคุณกำลังสร้าง search index, ทำ crawl หลายโดเมนแบบกว้าง, ต้องการฐานข้อมูล URL แบบถาวรที่มีสถานะราย URL และ semantics ของการ retry หรือคาดว่าจะกระจายงานไปหลายเครื่องในอนาคต นี่คือ infrastructure ที่ทำงานเฉพาะทางแบบนี้มานานตั้งแต่ก่อนที่ทางเลือกจำนวนมากจะมีเสียอีก ระบบปลั๊กอินทำให้คุณเปลี่ยนพฤติกรรม protocol, parser, filter และ scoring ได้โดยไม่ต้อง fork อะไรเลย ค่าเริ่มต้นด้านความสุภาพก็ตั้งมาระมัดระวังในระดับที่บ่งบอกว่าผู้ดูแลคิดเรื่องการเป็นพลเมืองที่ดีบนเว็บไว้มาก

ควรเดินหนีถ้าคุณต้องการข้อมูลแบบมีโครงสร้างจากไม่กี่หน้า Nutch จะ fetch และ parse ให้ แล้วส่ง crawldb กับ segments มาให้คุณ และคาดหวังว่าคุณจะมี indexer ของตัวเอง ควรเดินหนีถ้าเป้าหมายของคุณเป็น single-page app ที่ render ฝั่ง client — class C ไม่เคยไปถึงเลยในทุกคอนฟิกที่ผมรัน ควรเดินหนีถ้าทีมของคุณไม่ได้ใช้ JVM เพราะคุณกำลังจะเพิ่ม Java toolchain, การ pin JDK แบบ LTS และ jar 396 MB เข้าไปในสแตกที่เดิมไม่มีสิ่งเหล่านี้เลย และถ้างานคือ “crawl เว็บไซต์หนึ่งแห่ง ลึก 4 ชั้น สัปดาห์ละครั้ง” คุณจะเสียเวลากับรอบ loop และไฟล์คอนฟิกมากกว่าที่ crawl นั้นควรได้รับ

สำหรับคนส่วนใหญ่ที่กำลังหาสคริปเตอร์ นั่นคือกรณีที่เกิดขึ้นจริงบ่อยที่สุด ซึ่งไม่ใช่คำตำหนิ Nutch แต่มันคือความไม่เข้ากันระหว่างเครื่องมือกับงาน ถ้าอยากเห็นภาพรวมของเครื่องมือฝั่งนี้ให้กว้างขึ้นกว่าเดิม บทความ rundown of open-source scrapers และ best web scraping GitHub projects ของเราจะครอบคลุมตัวเลือกที่เบากว่านี้ละเอียดกว่า

ทางเลือกอื่น รวมถึงตำแหน่งของสแตกเราด้วย

เริ่มจากกรอบที่แฟร์ก่อน: Nutch ฟรี ใช้ไลเซนส์ Apache self-host ได้ และเป็นของคุณไปตลอดโดยไม่มีค่าใช้จ่ายต่อ request ข้อดีนี้เป็นข้อได้เปรียบจริง และไม่มีอะไรด้านล่างนี้มาลบมันได้

รีวิวที่เกี่ยวข้อง: รีวิว Browsertrix Crawler

ในโลก open-source การเปรียบเทียบขึ้นอยู่กับว่าคุณกำลัง optimize อะไร ถ้าคุณต้องการ framework ภาษา Python ที่ควบคุม crawl ได้และมีแนวคิด request-first, Scrapy เป็นตัวที่ใกล้เคียงกว่าสำหรับหลายโปรเจกต์ บทความนี้ไม่ได้วัด footprint ตอนติดตั้งด้วยมาตรฐานเดียวกัน ถ้าคุณอยากได้ Go crawler ตัวกะทัดรัดที่ไม่ต้องมีบราวเซอร์ Colly ก็เป็นอีกทรงหนึ่งที่น่าพิจารณา ถ้าปัญหาของคุณคือการแปลงหน้าเว็บให้เป็น content ที่พร้อมใช้กับ LLM มากกว่าการค้นหา URL, Crawl4AI จะอยู่คนละชั้นของปัญหา

บริการแบบ managed อย่าง Thunderbit ย้ายการ fetch, rendering และ extraction ไปอยู่หลัง API ขณะที่ Nutch ให้คุณควบคุม crawl state และ infrastructure เอง Thunderbit ไม่ได้ถูกทดสอบกับ fixture นี้ ดังนั้นตรงนี้จึงเป็นการเปรียบเทียบในมุม ownership model มากกว่าการอ้างว่า recall หรือ dynamic-page performance เท่ากัน

การแลกเปลี่ยนคือความเป็นเจ้าของกับภาระงานรองรับ และไม่ใช่เรื่องเล็ก Nutch ให้คุณคุมทุกอย่างได้ มี crawldb แบบถาวร ออกแบบมาให้ scale แบบ cluster ได้ และไม่มี marginal cost — แต่ต้องแลกกับ JVM, การ pin JDK แบบ LTS, jar 396 MB, รอบการทำงานแบบ loop และชั้น indexing ของคุณเอง ส่วน managed API ให้ structured output ตั้งแต่ครั้งแรกที่เรียก และไม่ต้องมี infrastructure — แต่ต้องแลกกับ การคิดค่าบริการต่อครั้ง และการควบคุม frontier ของ crawl ที่น้อยกว่า ถ้างานของคุณคือ “index 50 ล้านหน้า” โมเดลของ Nutch ถูกต้อง และ API จะดูไร้เหตุผล แต่ถ้างานคือ “ดึง record แบบมีโครงสร้างจากหน้า product 200 หน้าให้เสร็จภายในวันพฤหัส” ก็ตรงกันข้าม

ลองใช้ Thunderbit เพื่อดึงข้อมูลเว็บ

บทสรุป

Apache Nutch คุ้มค่าที่จะพิจารณาถ้าคุณทำ crawl ต่อเนื่อง หลายโดเมน และมี JVM infrastructure อยู่แล้ว บน fixture นี้ การค้นหาแบบ static มีความแน่นอนข้ามการรันซ้ำ parse-js หา endpoint ใน JavaScript แบบ literal ได้ทั้งสองจุด ความล้มเหลวถูกสะท้อนอยู่ใน crawldb และระยะห่างของ request ที่เห็นก็ตรงกับ delay ที่ตั้งไว้

ประเมินต้นทุนเริ่มต้นให้ตรงไปตรงมา Nutch 1.22 ใช้ไม่ได้ในเครื่องนี้บน JDK 26.0.1; OpenJDK 17.0.20 คือคอนฟิก LTS ที่ยืนยันแล้วในการรีวิวนี้ ส่วน Java 21 ยังไม่ได้ทดสอบ จากนั้นตั้ง http.agent.name, กำหนด scope ให้ชัดเจน และคำนึงถึงเพดานคงที่ราว 1.77 วินาทีต่อเฟสที่สังเกตได้ในการรัน local ขนาดเล็กครั้งนี้ ว่าคุ้มหรือไม่ขึ้นอยู่กับระยะเวลา ขอบเขต และความจำเป็นของสถานะถาวรในการ crawl

ลองใช้ Thunderbit เพื่อดึงข้อมูลเว็บ Get Started Free

คำถามที่พบบ่อย

ทำไม Apache Nutch ถึงล้มด้วยข้อความ "getSubject is not supported"? บน JDK 24 หรือใหม่กว่า JEP 486 ทำให้ Subject.getSubject() โยน exception ทันที ขณะที่ Hadoop 3.4.2 ที่ bundle มากับ Nutch ยังเรียกมันอยู่ ดังนั้น Hadoop job แรกจึงล้มก่อนจะได้ fetch หน้าใดๆ และทางลัดเก่า -Djava.security.manager=allow ก็ไม่ช่วยเปิด VM ได้แล้ว ให้ใช้คอนฟิก Java 17 ที่ยืนยันแล้ว และตั้ง NUTCH_JAVA_HOME; Java 21 อาจรองรับ แต่การรีวิวนี้ไม่ได้รัน full cycle บนมัน

ควรรัน Nutch 1.22 บน Java เวอร์ชันไหน? Java 17 คือคำตอบที่ปลอดภัยที่สุด — CI ของ Nutch ใช้เป้าหมายนี้ และผมทดสอบแล้วว่ารันได้เรียบร้อยบน OpenJDK 17.0.20 Java 11 ก็ยังรองรับสำหรับ 1.22 อยู่เช่นกัน แม้โปรเจกต์จะประกาศแล้วว่า 1.23 จะต้องใช้ Java 17 ทุกเวอร์ชันตั้งแต่ JDK 24 ขึ้นไปจะรันไม่ได้ การติดตั้ง Homebrew แบบ keg-only (brew install openjdk@17) พร้อม NUTCH_JAVA_HOME จะทำให้ JDK ค่าเริ่มต้นของระบบไม่ถูกแตะต้อง

Nutch crawl เว็บไซต์ที่ใช้ JavaScript หนักๆ ได้ไหม? ได้บางส่วน และความต่างตรงนี้สำคัญ ถ้าเปิดปลั๊กอิน parse-js Nutch จะหา endpoint ที่มีอยู่แค่เป็น string literal ในไฟล์ JavaScript ที่ลิงก์มาได้ทั้งสองจุด — 2/2 โดยไม่ต้องใช้บราวเซอร์ แต่ถ้าใช้ชุดปลั๊กอินค่าเริ่มต้น มันจะหาไม่เจอเลย ส่วน endpoint ที่จะโผล่ขึ้นมาหลัง JavaScript ทำงานและแก้ DOM เท่านั้น ก็ยังแตะไม่ถึงในทุกคอนฟิกแบบ static ที่ผมทดสอบ และการสลับไปใช้ HtmlUnit protocol ก็ไม่ใช่การเปลี่ยนแบบเสียบแทนได้ทันทีในรอบทดสอบของผม สำหรับแอปที่ render ฝั่ง client ให้เตรียมใช้ protocol ที่รัน JavaScript ได้จริงพร้อมงาน config ที่จริงจัง หรือใช้เครื่องมืออื่น

Nutch ต้องติดตั้ง Hadoop กับ Solr ไหม? ไม่ต้อง local mode ใช้ LocalJobRunner ของ Hadoop แบบ in-process — ไม่ต้องมี cluster, ไม่ต้องมี HDFS daemon, ไม่ต้องมี YARN — และทั้ง cycle inject → generate → fetch → parse → updatedb ทำงานบนเครื่องเดียวได้โดยไม่ต้องติดตั้งอะไรเพิ่ม Solr เป็นปลายทาง indexing ที่นิยม แต่ตัว crawl เองไม่ต้องพึ่งมัน อย่างไรก็ตาม Hadoop jars ถูก bundle มาให้จริงๆ (13 ตัว เวอร์ชัน 3.4.2) ซึ่งนั่นแหละคือเหตุผลที่ปัญหา compatibility กับ JDK เกิดขึ้นตั้งแต่แรก

จะหยุดไม่ให้ Nutch crawl เว็บไซต์อื่นได้อย่างไร? ต้องตั้งเอง เพราะค่าเริ่มต้นที่แถมมาไม่ได้ป้องกันไว้ Nutch 1.22 มาพร้อม db.ignore.external.links=false และ URL filter ที่เปิดกว้าง และในการทดสอบของผม crawl ค่าเริ่มต้นตามลิงก์ไปยัง host อื่นแล้วดึงมันมาจริงๆ ให้ตั้ง db.ignore.external.links=true ใน nutch-site.xml หรือเพิ่มกฎ host ใน conf/regex-urlfilter.txt เช่น +^https://example\.com/ แล้วตามด้วย -. ทั้งสองวิธีทำให้ crawl อยู่ในขอบเขตได้จริง โดยยืนยันจาก crawldb ของ Nutch เองและ log การ request ของเซิร์ฟเวอร์อีกฝั่ง

Ke
Ke
CTO ที่ Thunderbit | นักวิทยาศาสตร์ข้อมูลอาวุโสและผู้เชี่ยวชาญด้านแมชชีนเลิร์นนิง ด้วยประสบการณ์เกือบสิบปีในด้านแมชชีนเลิร์นนิงและวิทยาศาสตร์ข้อมูล เคเฉินเป็นศิษย์เก่ามหาวิทยาลัยโคลัมเบีย และอดีตนักวิทยาศาสตร์ข้อมูลอาวุโสที่ Walmart Labs ด้วยความเชี่ยวชาญลึกซึ้งที่ได้รับการยอมรับจากเพื่อนร่วมสายงานใน Python, R, Java และสถิติ เขาจึงแบ่งปันมุมมองที่ผ่านการพิสูจน์มาแล้วในการพัฒนาอัลกอริทึม AI ที่ซับซ้อนจากแนวคิดไปสู่สถาปัตยกรรมระดับใช้งานจริง
สารบัญ
Thunderbit · เอเจนต์ข้อมูลเว็บด้วย AI

ดึงข้อมูลจากทุกหน้าได้ใน คลิกเดียว

ได้รับความไว้วางใจจากผู้ใช้กว่า 250,000+ คน
มีแพ็กเกจใช้ฟรี
จากหน้าเว็บสู่สเปรดชีต
อธิบายสิ่งที่คุณต้องการ — AI Agent ของ Thunderbit จะดึงข้อมูลให้และส่งออกไปยัง Excel, Google Sheets, Airtable หรือ Notion เริ่มใช้ได้ฟรี
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week