Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

สร้างเว็บสครัปเปอร์แรกด้วย Ruby: คู่มือขั้นตอน (ภาค 1)

ใน圈子 Ruby (Rubyland) มีสอง Gem ที่ครองความนิยมในเว็บสครัปปิ้งมาหลายปี: Nokogiri และ Mechanize เราจะใช้บทความละ Gem ก่อนจะนำไปปฏิบัติจริงกับตัวอย่าง

หัวข้อที่ครอบคลุม

  • เว็บสครัปปิ้งคืออะไร?
  • ขออนุญาต
  • ปัญหาที่ต้องแก้
  • Nokogiri
  • การดึงข้อมูล (Extraction)
  • หน้าเว็บ (Pages)
  • API
  • การนำทางโหนด (Node Navigation)

เว็บสครัปปิ้งคืออะไร?

มีคำที่หรูหรามากกว่า "เว็บสครัปปิ้ง" หรือ "สกรีนสครัปปิ้ง" เช่น "เว็บฮาร์เวสติ้ง" (Web harvesting) และ "เว็บดาต้าอีกซ์แทร็กชัน" (Web data extraction) ซึ่งบอกเลยว่ากำลังทำอะไรอยู่ เราสามารถทำให้การดึงข้อมูลจากหน้าเว็บเป็นอัตโนมัติ—และไม่ได้ยากอย่างที่คิด

ในทางหนึ่ง เครื่องมือเหล่านี้ช่วยให้คุณเลียนแบบและทำให้การเล่นเว็บของมนุษย์เป็นอัตโนมัติ คุณเขียนโปรแกรมที่ดึงเฉพาะข้อมูลที่สนใจ การระบุเป้าหมายข้อมูลเฉพาะจุดง่ายเกือบเท่า CSS selectors

สองสามปีก่อน ผมสมัครคอร์สวิดีโอออนไลน์ที่มีวิดีโอสั้นๆ หลายล้านคลิป แต่ไม่มีตัวเลือกดาวน์โหลดแบบกลุ่ม ต้องเข้าแต่ละลิงก์เองและกด 'save as' เอง นั่นคือ "เว็บสครัปปิ้งแบบมนุษย์" —สิ่งที่เราต้องทำเมื่อขาดความรู้ในการทำอัตโนมัติ คอร์สเนื้อหาดี แต่วิธีใช้งานน่เบื่อเกินไป จึงไม่ได้ใช้บริการต่อ

วันนี้ผมไม่สนใจ UX ที่ทำให้สมองละลายแบบนั้นแล้ว สคริปต์สครัปที่ดาวน์โหลดให้อัตโนมัติใช้เวลาเขียนไม่เกินไม่กี่นาที ไม่เป็นไรเลย!

ลองแยกขั้นตอนสั้นๆ: 1) ดึงหน้าเว็บที่มีข้อมูลที่ต้องการ 2) ค้นหาและระบุข้อมูลที่ต้องการดึง 3) จัดเป้าหมาย สไลด์ข้อมูลถ้าจำเป็น และตัดสินใจว่าจะเก็บยังไง ไว้ที่ไหน HTML ที่เขียนดีมักเป็นกุญแจสำคัญทำให้กระบวนการง่ายและสนุก สำหรับการดึงข้อมูลซับซ้อน markup ที่โครงสร้างแย่จะทำให้ปวดหัว

API เป็นอย่างไร? คำถามดี ถ้ามี API ของบริการนั้น มักไม่ต้องเขียนสครัปเปอร์เอง วิธีนี้เหมาะกับเว็บที่ไม่มี API โดยไม่มี API นี่มักเป็นวิธีเดียวที่ทำให้การดึงข้อมูลอัตโนมัติได้

การสครัปทำงานอย่างไร? คำตอบสั้น: โดยการเดินทาง (traverse) โครงสร้างข้อมูลแบบต้นไม้ (tree data structures) Nokogiri สร้างโครงสร้างเหล่านี้จากเอกสารที่คุณให้ และให้คุณระบุเป้าหมายบิทที่สนใจเพื่อดึงออกมา CSS เป็นภาษาสำหรับการเดินทางต้นไม้ ค้นหาโครงสร้างต้นไม้ เราจึงนำมาใช้สำหรับดึงข้อมูลได้

มีแนวทางและโซลูชันเยอะมาก Rubyland มีสอง Gem ที่ครองแสงสปอตไล่หลายปี หลายคนยังคงใช้ Nokogiri และ Mechanize สำหรับ HTML scraping ทั้งคู่ทนทาน สะดวก และสามารถทำได้มาก เราจะดูทั้งคู่ แต่ก่อนหน้านั้น ผมอยากพูดถึงปัญหาที่จะแก้ในตอนท้ายของซีรีส์นี้

ขออนุญาต (Permission)

ก่อนเริ่มสครัป ต้องแน่ใจว่ามีอนุญาตจากเว็บไซต์ที่จะเข้าถึงข้อมูล หากเว็บมี API หรือ RSS feed อาจง่ายกว่าและก़กฎหมายกว่า

ไม่ใช่ทุกคนชอบถูกสครัปมาก (massive scraping) — เข้าใจได้ ทำความเข้าใจเว็บนั้นๆ ให้ดี อย่าลงเอยทำให้ตัวเองปวดหัว โอกาสทำลายสลายร้ายแรงน้อย แต่เสี่ยงโดยไม่รู้ตัวไม่ควรทำ

ปัญหาที่ต้องแก้

ผมต้องสร้าง podcast ใหม่ การออกแบบไม่เป็นไปตามใจ และเกลียดวิธีเผยแพร่โพสต์ใหม่ ดูด WYSIWYGs! บทบาทย้อนหลัง: สองปีก่อน ผมสร้าง podcast รุ่นแรก อยากเล่น Sinatra สร้างของเบาๆ พบปัญหาไม่คาดคิด เพราะทำนี่เองเกือบทั้งหมด

มาจาก Rails เรียนรู้มาก แต่เสียดายที่ไม่ได้ใช้ static site ที่ deploy ผ่าน GitHub Pages ได้ การ deploy ตอนใหม่และดูแลรักษาขาดความเรียบง่ายที่ต้องการ จึงตัดสินใจโฟกัสผลิตเนื้อหา podcast แทน

ช่วงฤดูร้อนนี้ เริ่มจริงจัง สร้างเว็บ Middleman host บน GitHub Pages สำหรับซีซั่น 2 อยากมีของใหม่: дизайн เรียบง่าย, Markdown สำหรับเผยแพร่ตอนใหม่, ไม่ต้องต่อสู้กับ Heroku — สวรรค์! แต่มี 139 ตอนเก่าที่ต้อง import และแปลงก่อนใช้กับ Middleman ได้

Middleman ใช้ไฟล์ .markdown ที่มี frontmatter สำหรับข้อมูล — แทนที่ database การโอนย้ายด้วยมือ 139 ตอน ไม่ใช่ทางเลือก นี่คือหน้าที่ของคอมพิวเตอร์ ผมต้องหาวิธี parse HTML เว็บเก่า สครัปเนื้อหาที่เกี่ยวข้อง และโอนไปเป็น blog posts สำหรับเผยแพร่ podcast บน Middleman

ดังนั้น 3 บทความต่อไป จะแนะนำเครื่องมือที่ใช้ใน Rubyland สำหรับงานแบบนี้ และสุดท้ายจะแสดงโซลูชันของผม

Nokogiri

แม้คุณจะเพิ่งเริ่ม Ruby/Rails โอกาสสูงมากที่เคยได้ยิน Gem นี้ ชื่อดังและจดจำง่าย ไม่แน่ใจว่ามากน้อยคนรู้ว่า "nokogiri" ภาษาญี่ปุ่นแปลว่า "เลื่อย" (saw)

ชื่อนี้เหมาะสมเมื่อเข้าใจว่ามันทำอะไร: ผู้สร้างคือ Tenderlove, Aaron Patterson Nokogiri แปลงเอกสาร XML และ HTML เป็นโครงสร้างข้อมูล — โครงสร้างแบบต้นไม้ (tree data structure) ให้ละเอียด กรุงและมี interface สวย ใช้ได้ดีมากสำหรับ HTML scraping ทุกแบบ

Nokogiri ไม่ได้แค่ parse HTML; XML ก็ได้ ให้ทั้ง XPath (XML path Language) และ CSS interfaces สำหรับ traverse เอกสาร XPath คือ query language สำหรับเลือกโหนดจาก XML CSS selectors คุ้นเคยกว่าสำหรับมือใหม่ เหมือน CSS ที่เขียน style ใช้ target ส่วนของหน้าเว็บที่สนใจได้ง่ายมาก แค่บอก Nokogiri ว่าอยากได้อะไร

หน้าเว็บ (Pages)

สิ่งแรกที่ต้องทำคือดึงหน้าเว็บที่สนใจ ระบุประเภทเอกสาร Nokogiri ที่จะ parse — XML หรือ HTML:

some_scraper.rb

Nokogiri::XML และ Nokogiri::HTML รับ IO objects หรือ String objects โค้ดข้างบนเปิดและดึงหน้าเว็บที่กำหนดด้วย open-uri แล้วโหลดโครงสร้าง XML หรือ HTML เข้า Nokogiri document XML ไม่ใช่สิ่งที่มือใหม่เจอบ่อย แนะนำให้โฟกัส HTML parsing

ทำไมต้อง open-uri? โมดูลนี้ใน Ruby Standard Library ให้ดึงเว็บได้ง่าย เพราะรับ IO objects จึงใช้ open-uri ได้สะดวก

API

ลองทำตัวอย่างเล็กๆ:

at_css

some_podcast_scraper.rb

โค้ดนี้ครอบคลุมทุกขั้นตอนของเว็บสครัปปิ้ง — แต่ในระดับไมโคร เราตัดสินใจ URL และเว็บที่ต้องการ โหลดเข้า Nokogiri document แล้วเปิดหน้านั้นและ target ส่วนเฉพาะ

ที่นี่ผมอยากรู้ title ตอนล่าสุด ใช้ at_css กับ CSS selector h2.post-title เพื่อระบุจุดดึงข้อมูล เมธอดนี้ดึงเฉพาะ element เดียว ให้ selector ทั้งหมด — ไมใช่สิ่งที่ต้องการ จึงใช้เมธอด text ดึง inner text เท่านั้น ดู output เปรียบเทียบ header และ text ด้านล่าง

Output

ตัวอย่างนี้ใช้งานจำกัด แต่มีส่วนผสมทุกอย่างที่ต้องเข้าใจ ง่ายและทรงพลัง ลองดูอะไรทำได้อีก

ข้อควรระวัง!

ถ้าคุณมือใหม่ ไม่แน่ใจจะ target HTML ยังไง แนะนำค้นหาวิธี inspect เนื้อหาเว็บใน browser ทุก browser ทำให้ง่ายมาก วันนี้

Chrome: คลิกขวาที่ element เลือก inspect จะเปิดหน้าต่างเล็กด้านล่างแสดง DOM แบบ X-ray มีออปชันเยอะ แนะนำให้ศึกษาเอง เวลาที่คุ้มค่า!

css

เมธอด css ให้ไม่ใช่แค่ element เดียว แต่อย่างที่ตรงกับเกณฑ์ค้นหาบนหน้านั้น สะดวกและตรงไปตรงมา!

some_scraper.rb

Output

ความต่างเล็กน้อย: loop raw headers ก่อน แล้วค่อยดึง inner text ด้วย text Nokogiri หยุดที่ท้ายหน้า ไม่พยายามติดตาม pagination เอง

ลองดูข้อมูลเพิ่ม: วันที่และ subtitle แต่ละตอน แค่ขยายตัวอย่างข้างบน ทำทีละขั้นตอน ได้ชิ้นเล็กทำงาน แล้วค่อยเพิ่มความซับซ้อน

some_scraper.rb

Output

ตอนนี้มีข้อมูลเล่นได้แล้ว จัดโครงสร้างหรือปรับแต่งยังไงก็ได้ ข้างบนแสดงข้อมูลให้อ่านง่าย ไ่น่นใช้ regex กับ text ก็เจาะลึกได้ จะรายละเอียดตอนแก้ปัญหา podcast จริง ไม่ใช่สอน regexp แต่อาจเห็นใน action — ไม่เยอะเกินไปจนสมองไหล

แอตทริบิวต์ (Attributes)

อาจจะดีถ้าดึง href ของแต่ละตอนด้วย ไมยากเลย

some_scraper.rb

จุดสำคัญคือ [:href] และ podcast_url ถ้า tag [:] จะดึง attribute จาก selector ได้ ผมแยกตัวแปรเพื่อให้เห็นชัดเจน

เพื่อ URL สมบูรณ์ ใช้ตัวแปร root domain สร้าง full URL

Output

สะดวกใช่ไหม? ดึง [:class] ของ selector ได้เหมือนกัน

ถ้าโหนดมีหลาย class จะได้ลิสต์ทั้งหมด

การนำทางโหนด (Node Navigation)

  • parent
  • children
  • previous_sibling
  • next_sibling

เราคุ้นเคย tree structures ใน CSS หรือ jQuery จะเสียดายถ้า Nokogiri ไม่มี API สะดวกสำหรับเดินใน tree

some_scraper.rb

Output

เห็นเองได้ว่า powerful มาก — โดยเฉพาะ .parent ที่รวบรวมมาหมดในครั้งเดียว แทนที่จะ define โหนดหลายตัวเอง

ยัง chain ได้สำหรับ traversal ซับซ้อน แต่ควรทำให้เรียบง่าย "Keep it simple, stupid!"

some_scraper.rb

Output

ข้อคิดสุดท้าย

Nokogiri ไม่ได้ใหญ่มาก แต่มีอะไรให้มาก แนะนำลองเล่นกับสิ่งที่เรียนรู้ และขยายความรู้ผ่าน documentation เมื่อชนกำแพง แต่อย่าทำให้ตัวเองปวดหัว!

บทนำเล็กๆ นี้ควรช่วยให้เข้าใจได้ดีว่า ทำอะไรได้บ้าง และทำงานยังไง หวังว่าคุณจะสำรวจต่อเอง และสนุกกับมัน ดังที่คุณจะพบเอง มันคือเครื่องมืออุดมสมบูรณ์ที่ให้มากไม่มีที่สิ้นสุด