Post updated on 2nd August, 2017 at 06:58 am
ধরেন কোনো একটা ওয়েবসাইটে কিছু ডেটা আছে। যা আপনি আপনার অ্যাপের ইউজারকে দেখাতে চান। এটা করার জন্য অ্যান্ড্রয়েড শেখার শুরুর দিকে মাথায় আসে “ওয়েবভিউতে লোড কইরা দিলেই কাজ শ্যাষ!” এতে কিছুটা কাজ হয়। কিন্তু অনেক সময়েই ঝামেলা থাকে। যেমন আপনি যদি একটা সাইটের কোনো একটা নির্দিষ্ট অংশ বা একটা পেজের নির্দিষ্ট কিছু টেক্সট দেখাতে চান তাহলে কী করবেন? API থাকলে সুন্দর মত API-তে কল দিয়ে ডেটা নিয়ে আসা যায়। কিন্তু API না থাকলে কী করবেন?
এজন্য বুদ্ধি হচ্ছে Web Scraping. অর্থাৎ যেই সাইটের ডেটা শো করতে চাচ্ছেন সেই সাইটে একটা রিকোয়েস্ট পাঠাবেন। স্বাভাবিক ভাবেই সার্ভার রেসপন্স হিসাবে আপনাকে ঐ পেজের সম্পূর্ণ HTML ডেটা পাঠিয়ে দিবে। এরপর আপনাকে বেছে বেছে HTML-CSS এর tag/class ধরে ধরে নির্দিষ্ট ডেটা বের করে নিয়ে আসতে হবে। এজন্য আপনার কিছু HTML/CSS এর জ্ঞান থাকা লাগবে। সম্প্রতি আমাদের টিমের ডেভেলপ করা Editorial Word অ্যাপে jsoup library ব্যবহার করে ওয়েব স্ক্র্যাপিং করেছি।
একটা HTML file এ অসংখ্য ট্যাগ থাকে। এগুলোর থেকে আপনার কাংক্ষিত ট্যাগ কিভাবে বের করবেন? ম্যানুয়াল্যি হয়ত করা সম্ভব কিন্তু কাজ একদমই কমিয়ে দিয়েছে JSOUP নামের একটা লাইব্রেরি। এই লাইব্রেরি ব্যবহার করে সহি-সালামতে ও শান্তিপূর্ণ ভাবে ওয়েব স্ক্র্যাপিং করা সম্ভব।
আমরা অ্যান্ড্রয়েডের অফিসিয়াল সাইটের এই পেজের প্রথম অংশটুকুট স্ক্র্যাপ করব। শুরুতে থাকা হেডলাইনটা ও এরপরের short description স্ক্র্যাপ করে TextView এর মাধ্যমে অ্যাপে দেখাব।
Jsoup Installation
Gradle এর dependencies এ নিচের লাইনটা যোগ করি।
dependencies { [...] compile 'org.jsoup:jsoup:1.10.2' }
যেহেতু আমরা নেটওয়ার্কে কল দিয়ে ডেটা নিয়ে আসব ও পার্স করব এটা একটু সময় সাপেক্ষ ব্যাপার। আর পুরো প্রকৃয়াটি UI blocking একটা task. অর্থাৎ আমরা যদি MainActivity() এর মধ্যে কাজটা করি তাহলে UI block হয়ে থাকবে। তাই সরাসরি UI thread এ এই কাজটা করা যাবে না। এটাকে background task হিসাবে চালাতে হবে। এজন্য আমরা কাজটা করব AsyncTask ক্লাসের একটা সাবক্লাসের ভিতরে।
কাজের ফ্লো হবে এরকমঃ activity থেকে asyncTask কে execute করার মেথড কল দিতে হবে। অন্য একটা ক্লাসে গিয়ে নেটওয়ার্ক কল ও আনুসঙ্গিক হাবিজাবি কাজ হবে। কাজটা যখন শেষ হবে তখন আমরা activity ক্লাসের কাছে Interface ব্যবহার করে final result রিটার্ন করব। এই ফলাফলের ভিত্তিতে TextView তে ডেটাগুলো শো করাবো।
এজন্য কয়েকটা ক্লাস লিখতে হবে।
ArticleModel.java
public class ArticleModel { private String headline; private String article; public ArticleModel(String headline, String article) { this.headline = headline; this.article = article; } public String getHeadline() { return headline; } public String getArticle() { return article; } }
HtmlParser.java
public class HtmlParser extends AsyncTask<String, Void, ArticleModel> { private ParserResponseInterface parserResponseInterface; public HtmlParser(ParserResponseInterface parserResponseInterface){ this.parserResponseInterface = parserResponseInterface; } @Override protected ArticleModel doInBackground(String... params) { String url = params[0]; ArticleModel articleModel = null; String headline; String article = ""; Document pageDocument; Elements elements; Elements articleElements; try { pageDocument = Jsoup.connect(url).get(); elements = pageDocument.select("#body-content"); headline = elements.select("h1").text(); articleElements = pageDocument.select(".wrap .cols .col-1of2 p"); for(Element element: articleElements){ article = article + element.text() + "\n\n"; } articleModel = new ArticleModel(headline, article); } catch (IOException e) { e.printStackTrace(); } return articleModel; } @Override protected void onPostExecute(ArticleModel articleModel) { super.onPostExecute(articleModel); parserResponseInterface.onParsingDone(articleModel); } }
HtmlParser ক্লাস extend করেছে AsyncTask ক্লাসকে। দুটি মেথড override করা হয়েছে। প্রথমটি হচ্ছে মূল কাজ পরিচালনা করার জন্য। নেটওয়ার্ক কল ও পার্সিং এর কাজ doInBackgroun() মেথডের ভিতর হবে। কাজগুলো শেষ হবার পরে onPostExecute() কল হবে। doInBackground() method টি onPostExecute() এর কাছে ArticleModel ক্লাসের একটা অবজেক্ট রিটার্ন করবে। এরপর interface এর মেথডে কল করা articleModel অবজেক্টকে প্যারামিটার হিসাবে পাঠিয়ে দিয়ে। Interface কে implement করতে হবে Activity class এ। overridden method এর ভিতর textView.setText() করতে হবে।
MainActivity থেকে url পাঠানো হয়েছে এক্সিকিউট করার সময়। সেই url এ কল করা হচ্ছে try() এর ভিতরে pageDocument = Jsoup.connect(url).get(); এর মাধ্যমে। pageDocument এ এখন ঐ পুরো পেজের HTML content আছে।
এখন এই লিংকে গিয়ে https://developer.android.com/training/index.html Getting Started থেকে নিচের টেক্সটটুকু সিলেক্ট করে inspect element অপশনে যান। দেখা যাবে Getting Started কথাটা লেখা আছে tag এর ভিতরে। আর < h1 > আছে body-content ক্লাসের ভিতরে। তাই ২৫ নাম্বার লাইনে এই ক্লসের এলিমেন্টগুলো আলাদা করা হয়েছে ও পরের লাইনে h1 কে আলাদা করে টেক্সট হিসাবে নেয়া হয়েছে। নেটওয়ার্ক কলের পরের লাইনে সিলেক্ট করা হচ্ছে body-content ক্লাসের সব element-কে। সুতরাং আমাদের হাতে এখন হেডলাইনটা চলে এসেছে।
এখন পরের paragraph-গুলোর tree hierarchy নিজেই বের করেন। ক্লাসগুলোর ক্রম হচ্ছে এরকমঃ wrap>cols>col-lof2.
col-lof2 এর ভিতরে রয়েছে অনেকগুলো < p > ট্যাগ। তাই সবগুলো < p > ট্যাগকে সিলেক্ট করা হয়েছে এভাবেঃ pageDocument.select(“.wrap .cols .col-1of2 p”); এরপর একটা for each loop চালিয়ে প্যারাগ্রাফগুলো একটা স্ট্রিং এ নেয়া হয়েছে। অতঃপর onPostExecute() মেথডের কাছে রিটার্ন করা হয়েছে articleModel object. বাকি কাজ নিজে নিজেই বুঝে যাবেন।
MainActivity.java
public class MainActivity extends AppCompatActivity implements ParserResponseInterface{ private TextView headlineTextView; private TextView articleTextView; private TextView errorMessageTextView; private ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); headlineTextView = (TextView) findViewById(R.id.headline); articleTextView = (TextView) findViewById(R.id.article); errorMessageTextView = (TextView) findViewById(R.id.errorMessage); progressBar = (ProgressBar) findViewById(R.id.progressBar); //Execute AsyncTask for Parsing HTML new HtmlParser(this).execute("https://developer.android.com/training/index.html"); } @Override public void onParsingDone(ArticleModel articleModel) { progressBar.setVisibility(View.GONE); if(articleModel!=null){ headlineTextView.setText(articleModel.getHeadline()); articleTextView.setText(articleModel.getArticle()); } else errorMessageTextView.setText("Something wrong! Can't parse HTML"); } }
পুরো প্রোজেক্টটা পাওয়া যাবে গিটহাব রিপোজিটরিতে। এখানে একদম সিম্পল একটা উদাহরন দেখানো হয়েছে। JSOUP এর অফিসিয়াল ডকুমেন্টেশন দেখে আরো advance কাজ করা যেতে পারে।
পুনশ্চঃ অনেক সাইটের ডেটা স্ক্র্যাপ করা বৈধ নয়। তাই এ ব্যাপারে সচেতন থাকা বাঞ্ছনীয়। Quora-র এই লেখাটা পড়তে পারেন। বা গুগল সার্চ করেও এর legal issue এর ব্যাপারে পড়াশোনা করতে পারেন।
It is very useful information you have shared in very descriptive way. I just downloaded your project from GitHub and now going to apply for my website. hope this will work.
Thanks a lot
Thank you for your feedback