Phantom.js: Membuat Statistik Repositori Github Sederhana

Ridwan Fajar 3 Juni 2015

Phantom.js: Membuat Statistik Repositori Github Sederhana

Pendahuluan

Phantom.js merupakan sebuah teknologi headless browser yang berbasis Qt5 Webkit, dikembangkan oleh Ariya Hidayat, alat ini menjadi andalan bagi web developer untuk melakukan hal - hal yang berkenaan dengan web development semisal monitoring, statisktik website, web testing, web automation, dan lainnya. Salah satu kegunaan Phantom.js selain dapat mengukur lamanya load page sebuah halaman website, adalah dapat digunakan untuk mengambil elemen tertentu dari suatu halaman dan menciptakan statistik yang kita inginkan.

Bersama Codepolitan, kamu akan mengikuti tulisan ini untuk dapat melakukan hal tersebut dengan Phantom.js. Kamu akan mempelajari beberapa hal berikut ini:

  • menjalankan file Javascript dengan Phantom.js
  • melakukan sedikit manipulasi DOM untuk mengambil informasi yang ada di halaman suatu repositori Github, misalkan mengambil jumlah star dan fork dari repositori Angular.js
  • mengenal beberapa method yang dimiliki oleh object webpage, filesystem, dan phantom
  • membuat file berupa JSON yang berisi informasi hasil pengambilan informasi dari beberapa repositori Github
Sebelum kamu melangkah lebih jauh bersama artikel ini, pastikan kamu sudah memasang Phantom.js di mesin komputer kamu entah itu Windows, Mac, Linux, ataupun BSD. Pastinya ikuti cara instalasinya di web resmi Phantom.js.

[caption id="attachment_4466" align="aligncenter" width="600"]konsep script scrapper informasi repositori github konsep script scrapper informasi repositori github[/caption]

Membuat Kerangka Kode untuk Proses Scrapping

Kerangka Kode yang akan kita buat berisi peng-import-an modul Phantom.js seperti filesystem, kemudian kita juga mendefinisikan daftar URL repositori Github yang akan diambil informasinya. Daftar URL yang akan diproses dibentuk menjadi sebuah array Javascript yang berisi objek dengan atribut name dan repo. Kemudian sebuah array dengan nama result_stat didefinisikan untuk menyimpan hasil proses scrapping di setiap iterasi program.

// melakukan import modul Phantom.js dan mendefinisikan target URL
var fs = require('fs');

var target = [ {'name':'ariya', 'repo':'phantomjs'}, {'name':'yiisoft', 'repo':'yii2'}, {'name':'laravel', 'repo':'laravel'}, {'name':'twbs', 'repo':'bootstrap'}, {'name':'angular', 'repo':'angular.js'}, {'name':'joyent', 'repo':'node'}, {'name':'jquery', 'repo':'jquery'}, {'name':'rails', 'repo':'rails'}, ];

var result_stat = [];

Kita akan menggunakan sebuah fungsi rekursif untuk memproses setiap URL repositori Github. Setiap elemen di array target akan diambil satu persatu kemudian elemen yang berisi URL repositori Github akan diproses oleh fungsi grab_github_stats(). URL yang diproses tersebut akan dihapus dari daftar. Hingga akhirnya program akan berhenti mengambil informasi dari setiap repositori jika array target sudah kosong. Berikut adalah kerangka source code yang akan kamu coba:


// function untuk melakukan pengambilan informasi dari halaman suatu repositori Github
function grab_github_stats(target, callback){
    console.log(target.repo);
    // disini akan ada proses pembukaan halaman setiap repositori Github
    // disini akan ada proses pengambilan informasi dari setiap repositori Github

    callback.apply();
}

// function untuk memproses semua URL dengan grab_github_stats secara rekursif
function process() {
    if (target.length > 0) {
        var repo = target[0];
        target.splice(0, 1);
        grab_github_stats(repo, process);
    } else {

        // disini akan ada proses pembuatan file JSON jika setiap repositori Github sudah diproses

        console.log('End processing....');
        phantom.exit(0);
    }
}

// menjalankan program
process();

Kenapa pemrosesan setiap repositori tidak menggunakan pengulangan for biasa? karena untuk menunggu sebuah halaman web selesai dibuka, kita perlu membuat sebuah kode untuk memastikan apakah halaman tersebut sudah selesai dibuka atau belum. Oleh karena itu rekursif digunakan untuk memproses setiap repositori dan setiap iterasinya dapat menunggu pemrosesan halaman web sepenuhnya karena menunggu halaman web dibuka sepenuhnya (walaupun kita tidak akan melihat halaman web sesungguhnya karena kita menggunakan headless browser :D). Fungsi process() dilewatkan menjadi sebuah fungsi callback yang akan dipanggil di dalam fungsi grab_github_stats().

Simpan kode diatas dengan nama github-repo-stat.js, kemudian silahkan jalankan melalui Terminal / CMD dengan perintah: phantomjs github-repo-stat.js. Berikut adalah tampilah jika Kode program yang kamu salin berhasil dijalankan oleh Phantom.js.


root@ridwan:/home/ridwanbejo/Codepolitan/github-repo-stat# phantomjs github-repo-stat.js 
phantomjs
yii2
laravel
bootstrap
angular.js
node
jquery
rails
End processing....
root@ridwan:/home/ridwanbejo/Codepolitan/github-repo-stat# 

Saat ini kode program yang kita salin belum dapat digunakan untuk mengakses halaman repositori Github. Untuk itu mari kita melangkah ke pembahasan selanjutnya :D.

Mengenal Objek WebPage di Phantom.js

Bagaimana :D? kita hanya baru mengenal phantom.exit(0) saja yah. Sekarang kamu akan sedikit mengenal objek yang dibuat dari modul WebPage. Sebuah objek WebPage diinstansiasi dengan cara melakukan peng-import-an modul webpage sekaligus memanggil method create(). Objek WebPage memiliki banyak sekali method, atribut, dan event. Tapi di artikel ini kita hanya akan menggunakan beberapa saja fitur dari objek WebPage yang akan digunakan yaitu:
  • method create(), untuk membuat sebuah objek WebPage
  • method open(), untuk membuka halaman web. Biasanya parameter yang dilewatkan adalah parameter URL yang akan dibuka, dan anonymous function yang akan melakukan proses secara callback. Fungsi yang dilewatkan ke method open harus menerima sebuah parameter yang mana parameter dari callback tersebut adalah status dari berhasil atau tidaknya dari pembukaan halaman web
  • method close(), untuk menutup halaman web yang sudah dibuka dengan Phantom.js
  • event onLoadStarted, kamu bisa mengaitkan sebuah fungsi ke event ini yang mana fungsi tersebut akan diproses sesaat halaman web yang akan diproses dibuka
  • event onLoadFinished, kamu bisa mengaitkan sebuah fungsi ke event ini yang mana fungsi tersebut akan diproses sesaat halaman web yang akan diproses beres dibuka
Sekarang silahkan buka file github-repo-stat.js, kemudian ganti isi fungsi github_grab_stats() dengan source code berikut:

// function untuk melakukan pengambilan informasi dari halaman suatu repositori Github
function grab_github_stats(target, callback){
    var repoUrl = 'https://github.com/'+target.name+'/'+target.repo;
    var repo_id = target.name+'/'+target.repo;
    var page = require('webpage').create();
page.onLoadStarted = function (){
    console.log(repoUrl + ' is opened....');
}

page.onLoadFinished = function (){
    console.log(repoUrl + ' is closed....');
}

page.open(repoUrl, function(status){
    if (status === "success"){

        // disini akan ada proses pengambilan informasi dari setiap repositori Github 

        page.close();
        callback.apply();
    }
});     

}

Sekarang coba jalankan kembali script tersebut dengan Phantom.js, dan coba lihat hasilnya di terminal / cmd. Kurang lebih hasilnya akan seperti ini:


root@ridwan:/home/ridwanbejo/Codepolitan/github-repo-stat# phantomjs github-repo-stat-part-2.js 
https://github.com/ariya/phantomjs is opened....
https://github.com/ariya/phantomjs is closed....
https://github.com/yiisoft/yii2 is opened....
https://github.com/yiisoft/yii2 is closed....
https://github.com/laravel/laravel is opened....
https://github.com/laravel/laravel is closed....
https://github.com/twbs/bootstrap is opened....
https://github.com/twbs/bootstrap is closed....
https://github.com/angular/angular.js is opened....
https://github.com/angular/angular.js is closed....
https://github.com/joyent/node is opened....
https://github.com/joyent/node is closed....
https://github.com/jquery/jquery is opened....
https://github.com/jquery/jquery is closed....
https://github.com/rails/rails is opened....
https://github.com/rails/rails is closed....
End processing....
You have new mail in /var/mail/root
root@ridwan:/home/ridwanbejo/Codepolitan/github-repo-stat#

Kamu berhasil mengenal objek WebPage dengan baik :D, sekarang saatnya kita mencoba hal yang greget beneran. Silahkan scroll halaman web ini sekarang :D.

Scrapping dengan Menggunakan Kode Javascript

Sebelum memulai scrapping, pastikan kamu sudah cuci mata ke halaman website yang akan kamu tangkap informasinya. Kamu dapat menggunakan developer tools yang terdapat di Firefox ataupun Google Chrome. Kemudian cari elemen dari halaman web tersebut yang akan kamu tangkap. Pastikan id, name, atau selector lain yang akan kamu gunakan dalam pengambilan informasi ada di halaman tersebut.

[caption id="attachment_4468" align="aligncenter" width="600"]contoh penentuan elemen yang akan di scrap contoh penentuan elemen yang akan di scrap[/caption]

Apabila untuk menjalankan javascript di browser yang biasa kamu gunakan, maka kamu menyimpan kode javascript tersebut diapit dengan tag script. Maka di Phantom.js kamu harus menggunakan method evaluate() yang bisa kamu gunakan ketika membuat sebuah objek WebPage. Kita akan mengambil beberapa informasi dari halaman suatu repositori di Github antara lain: watcher, fork, star, commit, contributor, branches, dan release. Di dalam method evaluate(), kamu dapat mengeksekusi kode Javascript yang misalnya berhubungan dengan DOM, tapi jika ingin menampilkan alert, confirm, atau popup Javascript lainnya perlu ada penanganan khusus. Kita tidak akan menggunakan library Javascript seperti JQuery, cukup dengan menggunakan Javascript biasa. Untuk setiap informasi yang akan didapat, kita menggunakan querySelector().innerText untuk mengambil informasi di sebuah elemen yang memiliki atribut yang diingikan. Misal ketika kita melakukan querySelector() pada elemen yang memiliki href="/laravel/laravel" maka elemen yang memiliki atribut tersebut akan diambil isi di dalam elemennya. Begitu pun untuk informasi yang lain.

Untuk melewatkan suatu variabel, kita harus melewatkan parameter pada method evaluate() setelah melewatkan fungsi callback. Nama parameter yang dilewatkan ke evaluate() harus ditangkap oleh fungsi callback. Misal kita melewatkan repo_id ke evaluate(), maka fungsi callback harus menerima sebuah parameter dengan nama yang sama. Akhirnya kita dapat menggunakan repo_id di dalam proses evaluasi kode Javascript yang akan diproses oleh Phantom.js

Setelah informasi di dapat, kita membundelnya menjadi sebuah objek Javascript dan dikembalikan menggunakan return. Objek yang dilemparkan dari dalam evaluate() akan diterima oleh variabel data. Selanjutnya kita mencoba untuk menampilkan isi variabel data ke terminal/cmd, kemudian menyimpannya ke array result_stat dengan menggunakan method push() yang dimiliki sebuah array. Berikut adalah perubahan isi fungsi grab_github_stats() yang baru. Silahkan ubah isi fungsi grab_github_stats() sebelumnya:


// function untuk melakukan pengambilan informasi dari halaman suatu repositori Github
function grab_github_stats(target, callback){
    var repoUrl = 'https://github.com/'+target.name+'/'+target.repo;
    var repo_id = target.name+'/'+target.repo;
    var page = require('webpage').create();

    page.onLoadStarted = function (){
        console.log(repoUrl + ' is opened....');
    }

    page.onLoadFinished = function (){
        console.log(repoUrl + ' is closed....');
    }

    page.open(repoUrl, function(status){
        if (status === "success"){

            // menggunakan evaluate() untuk mengeksekusi kode Javascript
            var data = page.evaluate(function(repo_id){
                var watcher = document.querySelector('[href="/'+repo_id+'/watchers"]').innerText.trim();
                var fork = document.querySelector('[href="/'+repo_id+'/network"]').innerText.trim();
                var star = document.querySelector('[href="/'+repo_id+'/stargazers"]').innerText.trim();
                var commit = document.querySelector('[href="/'+repo_id+'/commits/master"]').innerText.trim();
                var contributor = document.querySelector('[href="/'+repo_id+'/graphs/contributors"]').innerText.trim();
                var branches = document.querySelector('[href="/'+repo_id+'/branches"]').innerText.trim();
                var release = document.querySelector('[href="/'+repo_id+'/releases"]').innerText.trim();

                // mengembalikan objek javascript berisi informasi repositori Github ke variabel data
                return {
                    title: document.title,
                    documentUrl: document.URL,
                    watcher: watcher,
                    fork: fork,
                    star: star,
                    commit: commit,
                    contributor: contributor,
                    branches: branches,
                    release: release,
                }
            }, repo_id);

            // mencetak hasil penangkapan informasi ke terminal/cmd
            console.log(
                        data.documentUrl+"\t"+
                        data.watcher+"\t"+
                        data.fork+"\t"+
                        data.star+"\t"+
                        data.commit+"\t"+
                        data.contributor+"\t"+
                        data.release+"\t"+
                        data.branches
            );

            // menyimpan objek javascript yang berhasil didapat ke array result_stat
            result_stat.push(data);

            page.close();
            callback.apply();
        }
    });     
}

Menyimpan Hasil Scrapping ke JSON

Seiring dengan habisnya URL yang terdapat di array target, maka proses rekursif akan berhenti di fungsi process(). Ketika isi array target habis, maka proses di dalam fungsi process(), akan masuk ke dalam proses pembuatan file JSON yang menyimpan hasil scrapping kita. Melalui modul filesystem kita dapat membuat sebuah file dengan di Phantom.js. Pertama kita tentukan dulu nama file yang akan menyimpan hasil scrapping, kemudian mengubah objek Javascript menjadi string dengan JSON.stringify(), kemudian menulisnya dengan menggunakan method write() yang dimiliki oleh modul filesystem.

Berikut adalah kode terbaru yang ada di dalam fungsi process(). Silahkan ubah isi fungsi process sebelumnya:


// function untuk memproses semua URL dengan grab_github_stats secara rekursif
function process() {
    if (target.length > 0) {
        var repo = target[0];
        target.splice(0, 1);
        grab_github_stats(repo, process);
    } else {
        console.log(JSON.stringify(result_stat));
        var path = 'github-repo-stat-output.json';
        var content = JSON.stringify(result_stat);
        fs.write(path, content, 'w');

        console.log('End processing....');
        phantom.exit(0);
    }
}

Akhirnya kita selesai sudah menyalin dan melihat penjelasan untuk setiap bagian script yang akan kita gunakan untuk scrapping informasi dari suatu halaman repositori di Github. Sekarang mari jalankan kembali script ini di terminal / cmd dengan perintah phantomjs github-repo-stat.js. Maka akan muncul proses seperti pada gambar berikut dan file github-repo-stat-output.json pun akan tercipta di dalam folder tempat script ini berada.

[caption id="attachment_4467" align="aligncenter" width="600"]Hasil akhir dari kode github-repo-stat.js saat dijalankan Hasil akhir dari kode github-repo-stat.js saat dijalankan[/caption]

Apalagi Selanjutnya?

Karena artikel ini hanya membawa kamu untuk berkenalan dengan Phantom.js dan mengajak Anda hingga membuat file JSON saja, kamu dapat mengembangkan script ini untuk berbagai keperluan lainnya. Misal kamu dapat membuat sebuah halaman khusus di website pribadi atau blog pribadi kamu dengan membuat sebuah widget atau halaman yang berisi informasi tadi. Kamu pun dapat menggunakan file JSON hasil scrapping tadi untuk ditempelkan di aplikasi desktop maupun mobile. Semua bisa kamu lakukan dengan kreatifitias terbaik kamu. Semoga bermanfaat dan makin asyik dengan Phantom.js ;().

Referensi

[1] Phantom.js Documentation [2] An Introduction to Phantom.js, oleh Guerilla Labs

 

(rfs)