Membuat Web Scraper dengan Ruby (Output: HTML) Level 2
PERHATIAN!
Data yang penulis gunakan adalah data yang bersifat free public data. Sehingga, siapa saja dapat mengakses dan melihat tanpa perlu melalui layer authentikasi.
Penyalahgunaan data, bukan merupakan tanggung jawab dari penulis seutuhnya.
Sekenario Masalah
Blog post ini adalah modifikasi dari post sebelumnya yang berjudul, βMembuat Web Scraper dengan Ruby (Output: HTML)β.
Permasalahan dengan script sebelumnya adalah tidak dapat mendapatkan hasil.
Laporan ini saya dapatkan dari seorang teman, yaitu mas Rejka Permana di Telegram.
Ternyata, setelah saya cek website dari target belajar, desain dari website sudah berubah.
Sekarang menjadi seperti ini.
Tampilan yang sekarang, tentunya tidak dapat difetch menggunakan CSS selector yang sebelumnya. Karena markup dari HTML sudah berubah.
Lantas saya pun mencoba untuk memodifikasi script tersebut.
Pemecahan Masalah
Tidak ada cara lain selain memodifikasi CSS selector.
Namun, kali ini, saya akan memanfaatkan Ruby Class sekaligus membuat script menjadi lebih Object Oriented.
Tujuannya agar apabila terjadi perubahan lagi, dapat lebih mudah untuk dimaintain.
Langkah pertama adalah, saya me-rename file scraper.rb
menjadi main.rb
.
Kemudian membuat 2 file baru yaitu scaper.rb
dan template.rb
.
π ruby-web-scraper-dosen/
ββ π daftar_dosen.html
ββ π Gemfile
ββ π Gemfile.lock
ββ π main.rb ποΈ
ββ π scraper.rb ποΈ
ββ π template.rb ποΈ
main.rb
adalah aktor utama yang akan kita running.
scraper.rb
adalah Scraper Class yang akan berisi logic dari proses scraping (backend).
template.rb
adalah file yang akan menggenerate template (frontend).
Oke, selanjutnya adalah isi dari ketiga file tersebut.
Ngoding Session
Meskipun sebelumnya sudah pernah dilakukan, saya akan coba menulis kembali dari awal. Agar teman-teman yang baru mengikuti dari blog post ini tidak begitu kebingungan.
Initialisasi Gemfile
Buat file dengan nama Gemfile
. dan kita akan memasang gem yang diperlukan di dalam file ini.
1source 'https://rubygems.org'23gem 'httparty', '~> 0.18.1'4gem 'nokogiri', '~> 1.10', '>= 1.10.9'5gem 'byebug', '~> 11.1', '>= 11.1.3'
Setelah memasang gem pada Gemfile, kita perlu melakukan instalasi gem-gem tersebut.
$ bundle install
Proses bundle install di atas akan membuat sebuah file baru bernama Gemfile.lock
yang berisi daftar dependensi dari gem yang kita butuhkan βdaftar requirementsβ.
main.rb
Selanjutnya adalah si tokoh utama.
1require 'httparty'2require 'nokogiri'3require 'byebug'4require_relative './scraper'5require_relative './template'67def main8 begin9 target_url = "http://baak.universitasmulia.ac.id/dosen/"10 unparsed_page = HTTParty.get(target_url)11 rescue SocketError12 puts "ERROR: Target URL tidak dikenal (salah alamat)"13 exit14 end1516 parsed_page = Nokogiri::HTML(unparsed_page)1718 # daftar semua dosen19 dosens = Scraper.new(parsed_page).fetch_all2021 # daftar dosen pria22 dosens_pria = Scraper.new(parsed_page).fetch_by_gender('pria')2324 # daftar dosen wanita25 dosens_wanita = Scraper.new(parsed_page).fetch_by_gender('wanita')2627 # byebug2829 # template30 Template.new(dosens, dosens_pria, dosens_wanita).create_html3132 puts "TOTAL SELURUH DOSEN : #{dosens.count} orang"33 puts "TOTAL DOSEN PRIA : #{dosens_pria.count} orang"34 puts "TOTAL DOSEN WANITA : #{dosens_wanita.count} orang"35end3637main3839# Create index.html from daftar_dosen.html for rendering on netlify & vercel40%x(cp -f daftar_dosen.html index.html)
scraper.rb
1class Scraper23 attr_reader :parsed_page, :gender4 attr_writer :dosens56 def initialize(parsed_page)7 @parsed_page = parsed_page8 end910 def fetch_all11 dosens = Array.new12 dosen_listings = @parsed_page.css('div.elementor-widget-wrap p')13 dosen_listings[1..-2].each do |dosen_list|14 collect_dosen(dosen_list, dosens)15 end1617 return dosens18 end1920 def fetch_by_gender(gender)21 if gender == 'pria'22 index = 923 elsif gender == 'wanita'24 index = 1025 else26 puts 'Gender Not Qualified!'27 end2829 dosens = Array.new30 dosen_listings = @parsed_page.css('div.elementor-widget-wrap')[index].css('p')31 dosen_listings.each do |dosen_list|32 collect_dosen(dosen_list, dosens)33 end3435 return dosens36 end3738 def collect_dosen(dosen_list, dosens)39 nama_nidn_dosen = dosen_list&.text&.gsub(/(^\w.*?:)|(NIDN :\s)/, "").strip40 dosen = {41 nama_dosen: nama_nidn_dosen&.gsub(/[^A-Za-z., ]/i, ''),42 nidn_dosen: nama_nidn_dosen&.gsub(/[^0-9]/i, '')43 }4445 if dosen[:nama_dosen] != nil46 dosens << dosen47 end48 end4950end
template.rb
1class Template23 require 'date'45 attr_accessor :dosens, :dosens_pria, :dosens_wanita67 def initialize(dosens, dosens_pria, dosens_wanita)8 @dosens = dosens9 @dosens_pria = dosens_pria10 @dosens_wanita = dosens_wanita11 end1213 def create_html14 File.delete("daftar_dosen.html") if File.exist?("daftar_dosen.html")15 File.open("daftar_dosen.html", "w") do |f|16 f.puts '<!DOCTYPE html>'17 f.puts '<html lang="en">'18 f.puts '<head>'19 f.puts '<meta charset="UTF-8">'20 f.puts '<meta name="viewport" content="width=device-width, initial-scale=1">'21 f.puts "<title>Daftar Dosen Universitas Mulia Balikpapan(#{dosens.count} dosen)</title>"22 f.puts '</head>'23 f.puts '<body>'24 f.puts '<h1>Daftar Dosen UM BPPN</h1>'25 f.puts "<p>Data terakhir diparsing: #{Date.today}</p>"2627 f.puts '''28 <p>Made with β€ by <a href="https://bandithijo.github.io">Rizqi Nur Assyaufi</a> - 2020/07/12<br>29 Powered by <a href="http://ruby-lang.org">Ruby</a> |30 Source Code on <a href="https://github.com/bandithijo/ruby-web-scraper-dosen">GitHub</a></p>31 '''3233 f.puts '<div class="tab">'34 ['Semua Dosen', 'Dosen Pria', 'Dosen Wanita'].each.with_index(1) do |dosen, index|35 f.puts "<button class='tablinks' onclick=\"openTab(event, 'tab#{index}')\">#{dosen}</button>"36 end37 f.puts '</div>'3839 f.puts '<div id="tab1" class="tabcontent active">'40 f.puts '<h2>Daftar Semua Dosen</h2>'41 f.puts "<p style='margin-top:-12px;'>Jumlah Seluruh Dosen: #{dosens.size} orang</p>"42 f.puts '<input type="text" id="inputDosens" onkeyup="cariDosens()" placeholder="Cari nama dosen..">'43 f.puts '<table id="tableDosens">'44 dosens.each.with_index(1) do |dosen, index|45 f.puts '<tr>'46 f.puts "<td>#{dosen[:nama_dosen]}</td>"47 f.puts "<td>#{dosen[:nidn_dosen]}</td>"48 f.puts '</tr>'49 end50 f.puts '</table>'51 f.puts '</div>'5253 f.puts '<div id="tab2" class="tabcontent">'54 f.puts '<h2>Daftar Dosen Pria</h2>'55 f.puts "<p style='margin-top:-12px;'>Jumlah Dosen Pria: #{dosens_pria.size} orang</p>"56 f.puts '<input type="text" id="inputDosensPria" onkeyup="cariDosens()" placeholder="Cari nama dosen pria..">'57 f.puts '<table id="tableDosensPria">'58 dosens_pria.each.with_index(1) do |dosen, index|59 f.puts '<tr>'60 f.puts "<td>#{dosen[:nama_dosen]}</td>"61 f.puts "<td>#{dosen[:nidn_dosen]}</td>"62 f.puts '</tr>'63 end64 f.puts '</table>'65 f.puts '</div>'6667 f.puts '<div id="tab3" class="tabcontent">'68 f.puts '<h2>Daftar Dosen Wanita</h2>'69 f.puts "<p style='margin-top:-12px;'>Jumlah Dosen Wanita: #{dosens_wanita.size} orang</p>"70 f.puts '<input type="text" id="inputDosensWanita" onkeyup="cariDosens()" placeholder="Cari nama dosen wanita..">'71 f.puts '<table id="tableDosensWanita">'72 dosens_wanita.each.with_index(1) do |dosen, index|73 f.puts '<tr>'74 f.puts "<td>#{dosen[:nama_dosen]}</td>"75 f.puts "<td>#{dosen[:nidn_dosen]}</td>"76 f.puts '</tr>'77 end78 f.puts '</table>'79 f.puts '</div>'8081 f.puts '''82 <style>83 :root {84 --fg-color: #000;85 --bg-color: #fff;86 --a-color: #0000ff;87 }88 ::placeholder {89 color: var(--fg-color);90 opacity: 0.5;91 }92 body {93 background-color: var(--bg-color);94 color: var(--fg-color);95 font-family: Arial;96 font-size: 12px;97 }98 a, a:visited {99 color: var(--a-color);100 }101 table,th,td {102 border: 1px solid var(--fg-color);103 border-collapse: collapse;104 }105 td {106 padding: 3px;107 }108 td:nth-child(2) {109 font-family: monospace;110 text-align: center;111 }112 .tab {113 overflow: hidden;114 }115 .tab button {116 background-color: inherit;117 float: left;118 border: none;119 outline: none;120 cursor: pointer;121 padding: 5px 5px 5px 0;122 transition: 0.3s;123 font-family: inherit;124 font-size: inherit;125 color: inherit;126 margin-right: 10px;127 }128 .tab button.active {129 text-decoration: underline;130 }131 .tabcontent {132 display: none;133 }134 input:focus, textarea:focus, select:focus{135 background-color: var(--bg-color);136 color: var(--fg-color);137 outline: none;138 }139 #inputDosens, #inputDosensPria, #inputDosensWanita {140 background-color: var(--bg-color);141 width: 30%;142 padding: 0;143 border: 1px solid var(--bg-color);144 margin: 0 0 12px 0;145 font-family: inherit;146 font-size: 12px;147 }148 @media screen and (width: 360px) {149 table, #inputDosens, #inputDosensPria, #inputDosensWanita {150 width: 100%;151 }152 }153 </style>154 '''155156 f.puts '''157 <script>158 // Sumber: https://www.w3schools.com/howto/howto_js_tabs.asp159 function openTab(evt, tabNumber) {160 var i, tabcontent, tablinks;161 tabcontent = document.getElementsByClassName("tabcontent");162 for (i = 0; i < tabcontent.length; i++) {163 tabcontent[i].style.display = "none";164 }165 tablinks = document.getElementsByClassName("tablinks");166 for (i = 0; i < tablinks.length; i++) {167 tablinks[i].className = tablinks[i].className.replace(" active", "");168 }169 document.getElementById(tabNumber).style.display = "block";170 evt.currentTarget.className += " active";171 }172173 // Sumber: https://www.w3schools.com/howto/howto_js_filter_table.asp174 function cariDosens() {175 var input, filter, table, tr,176 inputPria, filterPria, tablePria, trPria,177 inputWanita, filterWanita, tableWanita, trWanita,178 td, i, txtValue;179 '''180181 ['', 'Pria', 'Wanita'].each do |dosen|182 f.puts """183 input#{dosen} = document.getElementById('inputDosens#{dosen}');184 filter#{dosen} = input#{dosen}.value.toUpperCase();185 table#{dosen} = document.getElementById('tableDosens#{dosen}');186 tr#{dosen} = table#{dosen}.getElementsByTagName('tr');187 for (i = 0; i < tr#{dosen}.length; i++) {188 td = tr#{dosen}[i].getElementsByTagName('td')[0];189 if (td) {190 txtValue = td.textContent || td.innerText;191 if (txtValue.toUpperCase().indexOf(filter#{dosen}) > -1) {192 tr#{dosen}[i].style.display = '';193 } else {194 tr#{dosen}[i].style.display = 'none';195 }196 }197 }198 """199 end200201 f.puts '''202 }203 </script>204 '''205206 f.puts '</body>'207 f.puts '</html>'208 end209 end210211end
Hasilnya
Demo
Untuk demonstrasi, teman-teman dapat mengunjungi alamat di bawah ini.
https://daftar-dosen-umb.vercel.app
Source
Bagi yang memerlukan source codenya, dapat mengunjungin alamat di bawah ini.
https://github.com/bandithijo/ruby-web-scraper-dosen
Pesan Penulis
Sepertinya, segini dulu yang saya tuliskan.
Penjelasan dari masing-masing blok kode akan saya tuliskan pada kesempatan yang lain yaa.
Mudah-mudahan kalau teman-teman mampir ke post ini, sudah ada penjelasan per blok kodenya.
Terima kasih sudah mampir.
(^_^)
Referensi
-
Itβs Time To HTTParty!
Diakses tanggal: 2020/08/20 -
nokogiri.org
Diakses tanggal: 2020/08/20