রুবিল্যান্ডে (Rubyland) গত কয়েক বছরে দুটি জেম ওয়েব স্ক্র্যাপিং-এর স্পটলাইট দখল করেছে: নোকোগিরি (Nokogiri) এবং মেকানাইজ (Mechanize)। আমরা প্রতিটি নিয়ে একটি আর্টিকেল খরচ করব তারপর একটি প্র্যাক্টিক্যাল উদাহরণের সাথে সেগুলোকে কাজে লাগাব।
বিষয়সূচি
- ওয়েব স্ক্র্যাপিং?
- অনুমতি (Permission)
- সমস্যা (The Problem)
- নোকোগিরি (Nokogiri)
- এক্সট্র্যাকশন (Extraction?)
- পেজ (Pages)
- এপিআই (API)
- নোড নেভিগেশন (Node Navigation)
ওয়েব স্ক্র্যাপিং?
ওয়েব বা স্ক্রিন স্ক্র্যাপিং-এর চেয়ে আরও ফ্যান্সি টার্ম রয়েছে। ওয়েব হারভেস্টিং (web harvesting) এবং ওয়েব ডেটা এক্সট্র্যাকশন (web data extraction) বললে বুঝা যায় আসলে কী হচ্ছে। আমরা ওয়েব পেজ থেকে ডেটা এক্সট্র্যাকশন অটোমেট করতে পারি—এবং এটি उतনা জটিলও নয়।
একটা দৃষ্টিতে, এই টুলগুলো আপনাকে মানব ওয়েব ব্রাউজিং অনুকরণ এবং অটোমেট করতে দেয়। আপনি একটি প্রোগ্রাম লিখেন যা শুধু আপনার আগ্রহের ডেটা বের করে। নির্দিষ্ট ডেটা টার্গেট করা প্রায় CSS সিলেক্টর ব্যবহার করার মতো সহজ।
কয়েক বছর আগে আমি একটি অনলাইন ভিডিও কোর্স সাবস্ক্রাইব করেছিলাম যাতে মিলিয়ন কর্তৃক ছোট ছোট ভিডিও ছিল কিন্তু বাল্কে ডাউনলোড করার অপশন ছিল না। আমাকে নিজে প্রতিটি লিংকে যেতে হতো এবং ভয়ঙ্কর ‘save as’ করতে হতো। এটি এক типаর মানব ওয়েব স্ক্র্যাপিং—যা আমরা প্রায়ই করি যখন আমাদের অটোমেট করার জ্ঞান থাকে না। কোর্সটি তো ঠিক ছিল, কিন্তু এরপর আমি তাদের সার্ভিস ব্যবহার করিনি। সেটা খুব বিরক্তিকর ছিল।
আজকাল, আমি এমন মাইন্ড-মেলটিং UX যত্ন করতাম না। একটি স্ক্রেপার যা আমার জন্য ডাউনলোড করবে সেটি তৈরি করতে আমাকে মাত্র কয়েক মিনিট লাগবে। কোনো বড় ব্যাপার নয়!
শুরু করার আগে খুব সংক্ষেপে ব্রেক ডাউন করি। পুরো প্রক্রিয়া কিছু ধাপে সংক্ষিপ্ত করা যায়। প্রথমে আমরা ঐ পেজটি ফেচ (fetch) করি যাতে আমাদের প্রয়োজনীয় ডেটা থাকে। তারপর ঐ পেজটি সার্চ করে আমরা যে ইনফরমেশন চাই সেটি চিনতে পারি।
শেষ ধাপ হলো এই টুকরোগুলো টার্গেট করা, প্রয়োজনে স্লাইস করা, এবং কীভাবে এবং কোথায় স্ট্রোর করব সেটি ডিসাইড করা। ভালো লিখা HTML প্রায়ই এই প্রক্রিয়াকে সহজ এবং উপভোগ্য করে তোলে। আরও জটিল এক্সট্র্যাকশনের জন্য, খারাপ স্ট্রাকচার্ড মার্কআপের সাথে ডিল করতে হলে কষ্ট হয়।
API সম্পর্কে কী? খুব ভালো প্রশ্ন। যদি আপনার কাছে API সহ কোনো সার্ভিস থাকে, তবে প্রায়শই নিজের স্ক্রেপার লিখতে হয় না। এই পদ্ধতিটি মূলত তাদের জন্য যারা সিরকুলার সুবিধা দেয় না। API ছাড়া, ওয়েবসাইট থেকে ইনফরমেশন অটোমেটিক্যালি বের করার এটি প্রায়শই একমাত্র উপায়।
আপনি জিজ্ঞেস করতে পারেন, স্ক্র্যাপিং আসলে কিভাবে কাজ করে? গভীরে না গিয়ে সংক্ষিপ্ত উত্তর হলো, ট্রি ডেটা স্ট্রাকচার ট্রাভার্স (traverse) করে। নোকোগিরি আপনি যা ডকুমেন্ট খাওয়ান সেটি থেকে ট্রি ডেটা স্ট্রাকচার বানায় এবং আপনাকে আগ্রহের অংশ টার্গেট করে এক্সট্র্যাকশনের সুযোগ দেয়। উদাহরণস্বরূপ, CSS ট্রি ট্রাভার্সাল এবং ট্রি ডেটা স্ট্রাকচার সার্চ করার জন্য লেখা একটি ল্যাঙ্গুয়েজ, এবং আমরা ডেটা এক্সট্র্যাকশনের জন্য এটিকে ব্যবহার করতে পারি।
বাহিরে অনেক অ্যাপ্রোচ এবং সমাধান আছে। রুবিল্যান্ডে দুটি জেম lâuদিন থেকে স্পটলাইটে আছে। অনেকেই এখনো HTML স্ক্র্যাপিংয়ের জন্য নোকোগিরি এবং মেকানাইজের উপর নির্ভর করে। দুটোই পরীক্ষিত এবং প্রমাণিত হয়েছে সহজ ব্যবহারের সাথে সাথে অত্যন্ত সক্ষম হওয়ায়। আমরা উভয়টিই দেখব। কিন্তু তার আগে, আমি এই সিরিজের শেষে যে সমস্যাটি সমাধান করব সেটি নিয়ে একটু কথা বলতে চাই।
অনুমতি (Permission)
স্ক্র্যাপিং শুরু করার আগে, নিশ্চিত হন যে আপনি যেসব সাইট থেকে ডেটা এক্সট্র্যাক্ট করতে চাচ্ছেন সেসব সাইটের অনুমতি আছে। যদি সাইটে API বা RSS ফিড থাকে, তবে সেটা শুধু কন্টেন্ট পেতে সহজ না, হতে পারে সেটিই আইনগতভাবে সঠিক পথ।
সবাই আপনার ভারী স্ক্র্যাপিংকে পছন্দ করবena—যা বোঝা যায়। সেই নির্দিষ্ট সাইট সম্পর্কে নিজেকে শিক্ষিত করুন এবং অনজানে কষ্টে না পড়ুন। সম্ভাবনা কম যে আপনি গুরুতর ক্ষতি করবেন, কিন্তু অজানে রিস্ক নেওয়া পথ নয়।
সমস্যা (The Problem)
আমাকে একটি নতুন পডকাস্ট বানাতে হয়েছিল। ডিজাইন আমার পছন্দমতো ছিল না, এবং নতুন পোস্ট পাবলিশ করার পদ্ধতিটি আমার খুব খারাপ লাগতো। damn WYSIWYGs! একটু কনটেক্সট দিই। প্রায় দুই বছর আগে আমি আমার পডকাস্টের প্রথম ভার্সন বানেছিলাম। আইডিয়া ছিল সিন্যাট্রা (Sinatra) নিয়ে খেলা এবং কিছু সুপার লাইটওয়েট বানানো। আমার টেইলর-মেড (tailor-made) সব কিছু করার কারণে কয়েকটা অপ্রত্যাশিত সমস্যায় পড়েছিলাম।
রেলস (Rails) থেকে আসা হলে, এটি নিশ্চয় একটি শিক্ষামূলক যাত্রা ছিল যা আমি এপ্রিশিয়েট করি, কিন্তু আমি দ্রুত খেদ প্রকাশ করলাম যে স্ট্যাটিক সাইট ব্যবহার না করলে আমার জিটহাব পেজের মাধ্যমে ডিপ্লয় করার সুবিধা পাইনি। নতুন এপিসোড ডিপ্লয় এবং মেইনটেইন করার সাদگی ছিল না যা আমি খুঁজছিলাম। কিছুক্ষণ আমি সিদ্ধান্ত নিলাম যে আমার বড়র মাছ ভাজা আছে এবং নতুন পডকাস্ট ম্যাটেরিয়াল প্রোডিউস করার ওপর ফোকাস করব।
এই গত গরমে আমি সিরিয়াস হয়ে গিয়েছি এবং জিটহাব পেজের মাধ্যমে হোস্ট হওয়া একটি মিডলম্যান (Middleman) সাইটে কাজ শুরু করলাম। শোয়ের সিজন দুইর জন্য আমি কিছু ফ্রেশ চেয়েছিলাম। একটি নতুন, সিম্পলিফাইড ডিজাইন, নতুন এপিসোড পাবলিশ করার জন্য মার্কডাউন (Markdown), এবং হারোকুর (Heroku) সাথে কোয়াই বাজি নয়—হেভেন! সমস্যাটা ছিল আমার ১৩৯টি এপিসোড পড়ে ছিল যা ইম্পোর্ট এবং কনভার্ট করতে হতো যাতে মিডলম্যানের সাথে কাজ করে।
পোস্টের জন্য মিডলম্যান .markdown ফাইল ব্যবহার করে যাতে ফ্রন্টম্যাটার (frontmatter) থাকে ডেটার জন্য—যা বেসিক্যালি আমার ডেটাবেস রিপ্লেস করে। ১৩৯টি এপিসোডের জন্য হাতে এই ট্রান্সফার করা অপশন নয়। এটাই কম্পিউটেশনের কাজ। আমাকে একটি উপায় বের করতে হয়েছিল আমার পুরনো ওয়েবসাইটের HTML পার্স করে, প্রাসঙ্গিক কন্টেন্ট স্ক্র্যাপ করে, এবং সেটিকে ব্লগ পোস্টে ট্রান্সফার করতে হবে যেটি আমি মিডলম্যানে নতুন পডকাস্ট এপিসোড পাবলিশ করার জন্য ব্যবহার করব।
তাই, পরবর্তী তিনটি আর্টিকেলে, আমি রুবিল্যান্ডে এই ধরণের কাজের জন্য সাধারণত ব্যবহৃত টুলগুলো আপনাকে পরিচয় করাব। শেষে, আমার সমাধান দেখিয়ে আপনাকে কিছু প্র্যাক্টিক্যাল কিছু দেখাব।
নোকোগিরি (Nokogiri)
আপনি যদি সম্পূর্ণ নতুন হন রুবি/রেলসে, তবুও সম্ভাবনা খুব বেশি যে আপনি ইতিমধ্যে এই ছোট জেমের নাম শুনেছেন। নামটা অনেকবার.Drop হয়ে থাকে এবং সহজে মনে পড়ে। আমার নিশ্চিত নয় যে অনেকেই জানেন nokogiri জাপানীতে “সো” (saw) মানে।
এটার নাম রাখা যথার্থ কারণ আপনি একবার বুঝলে টুলটি কী করে। এই জেমের স্রষ্টা হলো টেন্ডারলভ (Tenderlove), আーロন প্যাটারসন (Aaron Patterson)। নোকোগিরি XML এবং HTML ডকুমেন্টকে একটি ডেটা স্ট্রাকচারে কনভার্ট করে—আরো সঠিক বলতে একটি ট্রি ডেটা স্ট্রাকচারে। টুলটি ফাস্ট এবং একটি সুন্দর ইন্টারফেসও দেয়। মোটামুটি, এটি একটি খুব শক্তিশালী লাইব্রেরি যা আপনার HTML স্ক্র্যাপিং প্রয়োজনের অধিকাংশই হ্যান্ডেল করে।
আপনি নোকোগিরি শুধু HTML পার্সিংয়ের জন্য ব্যবহার করতে পারবেন না; XMLও ফেয়ার গেম। এটি আপনাকে XPath (XML Path Language) এবং CSS উভয় ইন্টারফেস দেয় ডকুমেন্ট ট্রাভার্স করার জন্য। XML Path Language, সংক্ষেপে XPath, একটি কোয়েরি ল্যাঙ্গুয়েজ। এটি আমাদের XML ডকুমেন্ট থেকে নোড সিলেক্ট করতে দেয়। CSS সিলেক্টর বিগিনারদের জন্য আরও পরিচিত। আপনি যা স্টাইল লিখেন সেটির মতো, CSS সিলেক্টর পেজের নির্দিষ্ট অংশ টার্গেট করার জন্য চমৎকারভাবে সহজ করে দেয়। আপনাকে শুধু নোকোগিরিকে বলতে হবে আপনি কী খুঁজছেন যখন একটি নির্দিষ্ট ডেস্টিনেশন টার্গেট করেন।
পেজ (Pages)
আমাদের যেটি সবসময় দরকার সেটি হলো আমাদের আগ্রহের একচুাল পেজ ফেচ করা। আমরা স্পেসিফাই করি কী ধরনের নোকোগিরি ডকুমেন্ট পার্স করতে চাই—XML বা HTML:
Nokogiri::XML Nokogiri::HTML
some_scraper.rb
require "nokogiri"
require "open-uri"
page = Nokogiri::XML(File.open("some.xml"))
page = Nokogiri::HTML(File.open("some.html"))
Nokogiri::XML এবং Nokogiri::HTML IO অবজেক্ট বা স্ট্রিং অবজেক্ট নিতে পারে। উপরে যা হচ্ছে সেটি সোজা। এটি open-uri ব্যবহার করে নির্দিষ্ট পেজটি ওপেন ও ফেচ করে এবং এর স্ট্রাকচার, XML বা HTML, একটি নতুন নোকোগিরি ডকুমেন্টে লোড করে। XML বিগিনারদের জন্য একদম নতুন কিছু নয়।
তাই, আমি পরামর্শ দিই আপাতত HTML পার্সিংয়ের ওপর ফোকাস করতে। কেন open-uri? রুবি স্ট্যান্ডার্ড লাইব্রেরির এই মডিউল আমাদের সাইটটি বড় ঝামেলা ছাড়া গ্র্যাব করতে দেয়। IO অবজেক্ট ফেয়ার গেম হওয়ার কারণে আমরা সহজে open-uri ব্যবহার করতে পারি।
এপিআই (API)
চলুন একটি মিনি উদাহরণের সাথে এটি প্র্যাকটিসে লাই:
at_css
some_podcast_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
title = header.text
puts "This is the raw header of the latest episode: #{header}"
puts "This is the title of the latest episode: #{title}"
এখানে আমরা যা করেছি সেটি ওয়েব স্ক্র্যাপিংয়ের সাথে জড়িত সমস্ত ধাপকে রিপ্রেজেন্ট করে—শুধু মাইক্রো লেভেলে। আমরা ডিসাইড করি কোন URL দরকার এবং কোন সাইট ফেচ করতে হবে, এবং সেগুলো একটি নতুন নোকোগিরি ডকুমেন্টে লোড করি। তারপর আমরা সেই পেজটি ওপেন করি এবং একটি নির্দিষ্ট সেকশন টার্গেট করি।
এখানে আমি শুধু লেটেস্ট এপিসোডের টাইটল জানতে চেয়েছিলাম। at_css মেথড এবং h2.post-title CSS সিলেক্টর ব্যবহার করে এক্সট্র্যাকশন পয়েন্ট টার্গেট করা হলো আমার যা দরকার ছিল। এই মেথড দিয়ে আমরা শুধু সেই সিঙ্গুলার এলিমেন্ট স্ক্র্যাপ করব। এটি আমাদের পুরো সিলেক্টর দেয়—যা অধিকাংশ সময় ঠিক আমাদের প্রয়োজনীয় নয়। তাই আমরা text মেথড দিয়ে এই নোডের ইন্ডার টেক্সট পোर्शन এক্সট্র্যাক্ট করি। তুলনার জন্য নিচে header এবং text উভয়ের আউটপুট দেখুন।
আউটপুট
This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the title of the latest episode: David Heinemeier Hansson
যদিও এই উদাহরণের অ্যাপ্লিকেশন খুব সীমিত, এতে সব উপাদান, সব ধাপ আছে যা আপনাকে বুঝতে হবে। আমার মতে এটি কতটা সহজ সেটি কুল। যেহেতু এই উদাহরণ থেকে স্পষ্ট নাও হতে পারে, আমি বলতে চাই এই টুল কত শক্তিশালী হতে পারে। চলুন দেখি নোকোগিরি স্ক্রিপ্ট দিয়ে আমরা আর কী কী করতে পারি।
বিশেষ লক্ষ্য করুন!
আপনি যদি বিগিনার হন এবং কীভাবে এই HTML টার্গেট করতে হয় সেটি নিশ্চিত নন, তবে আমি পরামর্শ দিই অনলাইন সার্চ করে ব্রাউজারে ওয়েবসাইটের কন্টেন্ট ইন্সপেক্ট করার পদ্ধতি জানুন। বেসিক্যালি সব বড় ব্রাউজার এই প্রক্রিয়া খুব সহজ করে দিয়েছে আজকাল।
ক্রোমে (Chrome) আপনাকে শুধু ওয়েবসাইটের একটি এলিমেন্টে রাইট-ক্লিক করে ইন্সপেক্ট অপশন বেছে নিতে হয়। এটি আপনার ব্রাউজারের নিচে একটি ছোট উইন্ডো ওপেন করবে যা সাইটের DOM-এর একটি এক্স-রে (x-ray) এর মতো দেখাবে। এর আরও অনেক অপশন আছে, এবং আমি কিছু সময় গুগলে বিতরণ করে নিজেকে শিক্ষিত করার পরামর্শ দিই। এটি সময় সঠিকভাবে বিতরণ!
css
css মেথড আমাদের শুধু একটি এলিমেন্ট দেয় না, বরং পেজে সার্চ ক্রাইটেরিয়ার সাথে মিল খাওয়ানো যেকোনো এলিমেন্ট দেয়। খুব সুন্দর এবং সহজ!
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
headers = page.css("h2.post-title")
headers.each do |header|
puts "This is the raw title of the latest episode: #{header}"
end
headers.each do |header|
puts "This is the title of the latest episode: #{header.text}"
end
আউটপুট
This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw title of the latest episode: <h2 class="post-title"><a href="episodes/136/">James Edward Gray II</a></h2> This is the title of the latest episode: David Heinemeier Hansson This is the title of the latest episode: Zach Holman This is the title of the latest episode: Joel Glovier This is the title of the latest episode: João Ferreira This is the title of the latest episode: Corwin Harrell This is the title of the latest episode: Roberto Machado This is the title of the latest episode: James Edward Gray II
এই উদাহরণে একমাত্র ছোট পার্থক্য হলো আমি রHeader-গুলো প্রথমে ইটারেট করেছি। আমি text মেথড দিয়ে এর ইন্ডার টেক্সটও এক্সট্র্যাক্ট করেছি। নোকোগিরি আটোমেটিক্যালি পেজের শেষে থামে এবং পেজিনেশন কোথাও আটোমেটিক্যালি ফলো করার চেষ্টা করে না।
মান লেন আমরা একটু আরও ইনফরমেশন চাই, যেমন প্রতিটি এপিসোডের তারিখ এবং সাবটাইটল। আমরা উপরের উদাহরণে এক্সপ্যান্ড করে সেটি পেতে পারি। ধাপে ধাপে এগিয়ে যাওয়া ভালো আইডিয়া। একটু কাজ করলে আরো কমপ্লেক্সিটি যোগ করুন।
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
articles = page.css("article.index-article")
articles.each do |article|
header = article.at_css("h2.post-title")
date = article.at_css(".post-date")
subtitle = article.at_css(".topic-list")
puts "This is the raw header: #{header}"
puts "This is the raw date: #{date}"
puts "This is the raw subtitle: #{subtitle}\n\n"
puts "This is the text header: #{header.text}"
puts "This is the text date: #{date.text}"
puts "This is the text subtitle: #{subtitle.text}\n\n"
end
আউটপুট
This is the raw header: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw date: <span class="post-date">Oct 18 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk</h3> This is the text header: David Heinemeier Hansson This is the text date: Oct 18 | 2016 This is the text subtitle: Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk This is the raw header: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw date: <span class="post-date">Oct 12 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication</h3> This is the text header: Zach Holman This is the text date: Oct 12 | 2016 This is the text subtitle: Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication This is the raw header: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw date: <span class="post-date">Oct 10 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source</h3> This is the text header: Joel Glovier This is the text date: Oct 10 | 2016 This is the text subtitle: Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source This is the raw header: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw date: <span class="post-date">Aug 26 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values</h3> This is the text header: João Ferreira This is the text date: Aug 26 | 2015 This is the text subtitle: Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values This is the raw header: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw date: <span class="post-date">Aug 06 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays |</h3> This is the text header: Corwin Harrell This is the text date: Aug 06 | 2015 This is the text subtitle: Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays | This is the raw header: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw date: <span class="post-date">Aug 03 | 2015</span> This is the raw subtitle: <h3 class="topic-list">CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs</h3> This is the text header: Roberto Machado This is the text date: Aug 03 | 2015 This is the text subtitle: CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs This is the raw header: <h2 class="post-title"><a href="episodes/136/">James Edward Gray II</a></h2> This is the raw date: <span class="post-date">Jul 30 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Screencasting | Less Code | Reading code | Getting unstuck | Rails’s codebase | CodeNewbie | Small examples | Future plans | PeepCode | Frequency & pricing</h3> This is the text header: James Edward Gray II This is the text date: Jul 30 | 2015 This is the text subtitle: Screencasting | Less Code | Reading code | Getting unstuck | Rails’s codebase | CodeNewbie | Small examples | Future plans | PeepCode | Frequency & pricing
এই পয়েন্টে, আমাদের ইতিমধ্যে কিছু ডেটা আছে খেলার জন্য। আমরা যেভাবে চাই স্ট্রাকচার বা কাট-ছাঁট করতে পারি। উপরের আউটপুট শুধু আমাদের কী আছে সেটি পঠনযোগ্য ফ্যাশনে দেখাতে। অবশ্যই আমরা রেগুলার এক্সপ্রেশন (regular expressions) দিয়ে text মেথডের সাথে আরও গভীরে যেতে পারি।
আমরা প্রকৃত পডকাস্ট সমস্যাটি সমাধান করার সময় এটিকে আরও বিস্তারিত দেখব। এটা regexp ক্লাস নয়, কিন্তু আপনি এর কিছু একশন দেখবেন—তবে চিন্তা নয়েন, ততটাও নয় যে ব্রেন ব্লিড (brain bleed) করবে।
অ্যাট্রিবিউট (Attributes)
এই স্টেজে যা হ্যান্ডি হতে পারে সেটি হলো প্রতিটি এপিসোডের জন্য href এক্সট্র্যাক্ট করা। এটাও খুব সহজ।
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
articles = page.css("article.index-article")
articles.each do |article|
header = article.at_css("h2.post-title")
date = article.at_css(".post-date")
subtitle = article.at_css(".topic-list")
link = article.at_css("h2.post-title a")
podcast_url = "https://betweenscreens.fm/"
puts "This is the raw header: #{header}"
puts "This is the raw date: #{date}"
puts "This is the raw subtitle: #{subtitle}"
puts "This is the raw link: #{link}\n\n"
puts "This is the text header: #{header.text}"
puts "This is the text date: #{date.text}"
puts "This is the text subtitle: #{subtitle.text}"
puts "This is the raw link: #{podcast_url}#{link[:href]}\n\n"
end
এখানে সবচেয়ে গুরুত্বপূর্ণ দুটি বিষয় হলো [:href] এবং podcast_url। আপনি যদি [:] ট্যাগ অন করুন তবে টার্গেট করা সিলেক্টর থেকে একটি অ্যাট্রিবিউট সহজে এক্সট্র্যাক্ট করতে পারেন। আমি একটু আরও অ্যাবস্ট্র্যাক্ট করেছি, কিন্তু নিচে আরও স্পষ্টভাবে কীভাবে কাজ করে সেটি দেখুন।
... href = article.at_css("h2.post-title a")[:href] ...
একটি কমপ্লিট এবং ইউজফুল URL পেতে, আমি রুট ডোমেনটি একটি ভেরিয়েবলে সেভ করেছি এবং প্রতিটি এপিসোডের জন্য ফুল URL কনস্ট্রাক্ট করেছি।
... podcast_url = "https://betweenscreens.fm/"
puts "This is the raw link: #{podcast_url}#{link[:href]}\n\n" ...
চলুন আউটপুটে এক নজর দিই:
আউটপুট
This is the raw header: <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> This is the raw date: <span class="post-date">Oct 25 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> This is the raw link: <a href="episodes/143/">Jason Long</a> This is the text header: Jason Long This is the text date: Oct 25 | 2016 This is the text subtitle: Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas This is the href: https://betweenscreens.fm/episodes/143/ This is the raw header: <h2 class="post-title"><a href="episodes/142/">David Heinemeier Hansson</a></h2> This is the raw date: <span class="post-date">Oct 18 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk</h3> This is the raw link: <a href="episodes/142/">David Heinemeier Hansson</a> This is the text header: David Heinemeier Hansson This is the text date: Oct 18 | 2016 This is the text subtitle: Rails community | Tone | Technical disagreements | Community policing | Ungratefulness | No assholes allowed | Basecamp | Open source persona | Aspirations | Guarding motivations | Dealing with audiences | Pressure | Honesty | Diverse opinions | Small talk This is the href: https://betweenscreens.fm/episodes/142/ This is the raw header: <h2 class="post-title"><a href="episodes/141/">Zach Holman</a></h2> This is the raw date: <span class="post-date">Oct 12 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication</h3> This is the raw link: <a href="episodes/141/">Zach Holman</a> This is the text header: Zach Holman This is the text date: Oct 12 | 2016 This is the text subtitle: Getting Fired | Taboo | Transparency | Different Perspectives | Timing | Growth Stages | Employment & Dating | Managers | At-will Employment | Tech Industry | Europe | Low hanging Fruits | Performance Improvement Plans | Meeting Goals | Surprise Firings | Firing Fast | Mistakes | Company Culture | Communication This is the href: https://betweenscreens.fm/episodes/141/ This is the raw header: <h2 class="post-title"><a href="episodes/140/">Joel Glovier</a></h2> This is the raw date: <span class="post-date">Oct 10 | 2016</span> This is the raw subtitle: <h3 class="topic-list">Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source</h3> This is the raw link: <a href="episodes/140/">Joel Glovier</a> This is the text header: Joel Glovier This is the text date: Oct 10 | 2016 This is the text subtitle: Digital Product Design | Product Design @ GitHub | Loving Design | Order & Chaos | Drawing | Web Design | HospitalRun | Diversity | Startup Culture | Improving Lives | CURE International | Ember | Offline First | Hospital Information System | Designers & Open Source This is the href: https://betweenscreens.fm/episodes/140/ This is the raw header: <h2 class="post-title"><a href="episodes/139/">João Ferreira</a></h2> This is the raw date: <span class="post-date">Aug 26 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values</h3> This is the raw link: <a href="episodes/139/">João Ferreira</a> This is the text header: João Ferreira This is the text date: Aug 26 | 2015 This is the text subtitle: Masters @ Work | Subvisual | Deadlines | Design personality | Design problems | Team | Pushing envelopes | Delightful experiences | Perfecting details | Company values This is the href: https://betweenscreens.fm/episodes/139/ This is the raw header: <h2 class="post-title"><a href="episodes/138/">Corwin Harrell</a></h2> This is the raw date: <span class="post-date">Aug 06 | 2015</span> This is the raw subtitle: <h3 class="topic-list">Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays |</h3> This is the raw link: <a href="episodes/138/">Corwin Harrell</a> This is the text header: Corwin Harrell This is the text date: Aug 06 | 2015 This is the text subtitle: Q&A | 01 | University | Graphic design | Design setup | Sublime | Atom | thoughtbot | Working location | Collaboration & pairing | Vim advocates | Daily routine | Standups | Clients | Coffee walks | Investment Fridays | This is the href: https://betweenscreens.fm/episodes/138/ This is the raw header: <h2 class="post-title"><a href="episodes/137/">Roberto Machado</a></h2> This is the raw date: <span class="post-date">Aug 03 | 2015</span> This is the raw subtitle: <h3 class="topic-list">CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs</h3> This is the raw link: <a href="episodes/137/">Roberto Machado</a> This is the text header: Roberto Machado This is the text date: Aug 03 | 2015 This is the text subtitle: CEO @ Subvisual | RubyConf Portugal | Creators School | Consultancy | Company role models | Group Buddies | Portuguese startup | Rebranding | Technologies used | JS frameworks | TDD & BDD | Startup mistakes | Culture of learning | Young entrepreneurs This is the href: https://betweenscreens.fm/episodes/137/
নিট, ঠিক নয়? আপনি একইভাবে সিলেক্টরের [:class] এক্সট্র্যাক্ট করতে পারেন।
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
body_classes = page.at_css("body")[:class]
যদি সেই নোডে একাধিক ক্লাস থাকে, তবে আপনি সবগুলো পাবেন।
নোড নেভিগেশন (Node Navigation)
- parent
- children
- previous_sibling
- next_sibling
আমরা CSS বা এমনকি jQuery-তে ট্রি স্ট্রাকচারের সাথে কাজ করার অভ্যস্ত। নোকোগিরি যদি ট্রি মধ্যে ঘুরে ফিরার জন্য হ্যান্ডি API না দিত তবে কষ্ট হতো।
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
header_children = page.at_css("h2.post-title").children
header_parent = page.at_css("h2.post-title").parent
header_prev_sibling = page.at_css("h2.post-title").previous_sibling
puts "#{header}\n\n"
puts "#{header_children}\n\n"
puts "#{header_parent}\n\n"
puts "#{header_prev_sibling}\n\n"
আউটপুট
#header <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> #header_children <a href="episodes/143/">Jason Long</a> #header_parent <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> #header_previous_sibling <span class="post-date">Oct 25 | 2016</span>
আপনি নিজে দেখতে পাচ্ছেন, এটি কিছু খুব শক্তিশালী জিনিস—বিশেষ করে যখন আপনি দেখবেন .parent একবারে কত কিছু সংগ্রহ করতে পেরেছে। হাত দিয়ে এক girone নোড ডিফাইন করার বদলে, আপনি সেগুলো হোলসেল সংগ্রহ করতে পারেন।
আপনি চেইনিং (chaining)ও করতে পারেন আরও জটিল ট্রাভার্সালের জন্য। আপনি চাইলে এটাকে যত জটিল করতে পারেন, কিন্তু আমি সতর্ক করছি জিনিসগুলো সিম্পল রাখতে। এটি খুব দ্রুত একটু আনরুলি (unwieldy) এবং বুঝতে কঠিন হয়ে যেতে পারে। মনে রাখবেন, "Keep it simple, stupid!"
... header_parent_parent = page.at_css("h2.post-title").parent.parent
header_prev_sibling_parent_children = page.at_css("h2.post-title").previous_sibling.parent.children ...
some_scraper.rb
require 'nokogiri'
require "open-uri"
url = 'https://betweenscreens.fm/'
page = Nokogiri::HTML(open(url))
header = page.at_css("h2.post-title")
header_prev_sibling_children = page.at_css("h2.post-title").previous_sibling.children
header_parent_parent = page.at_css("h2.post-title").parent.parent
header_prev_sibling_parent = page.at_css("h2.post-title").previous_sibling.parent
header_prev_sibling_parent_children = page.at_css("h2.post-title").previous_sibling.parent.children
puts "#{header}\n\n"
puts "#{header_prev_sibling_children}\n\n"
puts "#{header_parent_parent}\n\n"
puts "#{header_prev_sibling_parent}\n\n"
puts "#{header_prev_sibling_parent_children}\n\n"
আউটপুট
#header <h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> #header_previous_sibling_children Oct 25 | 2016 #header_parent_parent <li> <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> </li> #header_previous_sibling_parent <article class="index-article"> <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div> </article> #header_previous_sibling_parent_children <span class="post-date">Oct 25 | 2016</span><h2 class="post-title"><a href="episodes/143/">Jason Long</a></h2> <h3 class="topic-list">Open source | Empathy | Lower barriers | Learning tool | Design contributions | Git website | Branding | GitHub | Neovim | Tmux | Design love | Knowing audiences | Showing work | Dribbble | Progressions | Ideas</h3> <div class="soundcloud-player-small"> </div>
শেষ ভাবনা
নোকোগিরি একটি বড় লাইব্রেরি নয়, কিন্তু এটি অনেক কিছু অফার করে। আমি পরামর্শ দিই আপনি যা শিখেছেন সেটি নিয়ে খেলুন এবং ডকুমেন্টেশন থেকে জান 識 বাড়ান যখন আপনি একটি দিয়ালে আটকে যান। কিন্তু নিজেকে কষ্টে না ফেলুন!
এই ছোট ইন্ট্রো আপনাকে ভালোভাবে পথে লায়ে দেবে বুঝতে যে আপনি কী করতে পারেন এবং কীভাবে কাজ করে। আশা করি আপনি নিজে একটু এক্সপ্লোর করবেন এবং এর সাথে মজা করবেন। আপনি নিজে খুঁজে পাবেন, এটি একটি সমৃদ্ধ টুল যা দিয়ে দিয়ে দেয়।