Mulai April 2020, bandithijo.com akan menggunakan GitHub subdomain menjadi bandithijo.github.io. Penggunaan domain .com tidak sejalan dengan cara pandang saya terhadap sebuah blog, dimana blog harus bisa tetap hidup tanpa saya. Terima kasih (^_^) (bandithijo, 2020/03/25) ●

Membuat Web Scraper dengan Ruby (Output: POSTGRESQL: COPY FROM CSV)

Ditulis: 2020/06/16
Ruby

بسم الله الرحمن الرحيم

[ ! ] Disclaimer

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.

Prerequisite

Ruby 2.6.6 Rails 5.2.4 PostgreSQL 12.3

Pendahuluan

Web scraping adalah teknik mengambil atau mengekstrak sebagian data dari suatu website secara spesifik, spesifik dalam arti hanya data tertentu saja yang diambil. Script atau program untuk melakukan hal tersebut, disebut dengan web scraper.

Objektif

Catatan kali ini saya akan mendokumentasikan proses dalam membuat web scraper dengan tujuan untuk mengambil data nama-nama dosen yang ada pada website resmi Biro Akademik Universitas Mulia Balikpapan yang ada pada halaman ini.

Hasil yang akan di dapatkan dari script yang akan kita buat adalah daftar nama-nama dosen beserta nidn dalam bentuk tabel di dalam database PostgreSQL.

Penerapan

Langkah awal adalah persiapkan direktori untuk proyek.

Saya akan beri nama ruby-web-scraper-dosen.

Biasakan untuk memberi nama proyek tidak menggunakan karakter spasi.

$ mkdir ruby-web-scraper-dosen

Kemudian masuk ke dalam direktori proyek.

$ cd ruby-web-scraper-dosen

Buat file dengan nama Gemfile. dan kita akan memasang gem yang diperlukan di dalam file ini.

1
2
3
4
5
6
source 'https://rubygems.org'

gem 'httparty',     '~> 0.18.1'
gem 'nokogiri',     '~> 1.10', '>= 1.10.9'
gem 'byebug',       '~> 11.1', '>= 11.1.3'
gem 'pg',           '~> 1.2', '>= 1.2.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–.

Pastikan kalau service dari PostgreSQL sudah berjalan.

$ sudo systemctl status postgresql.service
● postgresql.service - PostgreSQL database server
     Loaded: loaded (/usr/lib/systemd/system/postgresql.service; disabled; vendor preset: disabled)
     Active: active (running) since Thu 2020-06-16 18:49:53 WITA; 1m ago
   Main PID: 36698 (postgres)
      Tasks: 8 (limit: 4610)
     Memory: 28.9M
     CGroup: /system.slice/postgresql.service
             ├─ 36698 /usr/bin/postgres -D /var/lib/postgres/data
             ├─ 36700 postgres: checkpointer
             ├─ 36701 postgres: background writer
             ├─ 36702 postgres: walwriter
             ├─ 36703 postgres: autovacuum launcher
             ├─ 36704 postgres: stats collector
             ├─ 36705 postgres: logical replication launcher
             └─397565 postgres: bandithijo web_scraper [local] idle

Selanjutnya, kita harus membuat database. Teman-tema dapat mengguakan PostBird (Database Management GUI) atau pgcli, saya akan menggunakan pgcli.

Jalankan pgcli,

$ pgcli
Server: PostgreSQL 12.3
Version: 3.0.0
Chat: https://gitter.im/dbcli/pgcli
Home: http://pgcli.com
bandithijo>

Dan buat database dengan nama web_scraper.

bandithijo> CREATE DATABASE web_scraper;
CREATE DATABASE
Time: 0.271s

Setelah database sudah dibuat, sekarang kita akan membuat aktor utamanya.

Beri nama scraper.rb.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# daftar gem yang diperlukan
require 'httparty'
require 'nokogiri'
require 'byebug'

def scraper
  # blok ini bertugas untuk mengambil data dengan output berupa variabel array
  target_url = "http://baak.universitasmulia.ac.id/dosen/"
  unparsed_page = HTTParty.get(target_url)
  parsed_page = Nokogiri::HTML(unparsed_page)
  dosens = Array.new
  dosen_listings = parsed_page.css('div.elementor-widget-wrap')
  dosen_listings.each do |dosen_list|
    dosen = {
      # perlu mengganti ' dengan ` agar tidak mengacaukan proses input data nama
      nama_dosen: dosen_list.css("h2")[0]&.text&.gsub("\n", "")&.gsub(/'/, "`")&.squeeze,
      nidn_dosen: dosen_list.css("h2")[1]&.text&.gsub("\n", "")
    }
    if dosen[:nama_dosen] != nil
      dosens << dosen   # dosens, variable array yang menampung data para dosen
    end
  end
  # aktifkan byebug apabila diperlukan
  #byebug

  # blok ini bertugas untuk membuat file csv yang berisi daftar dosen
  File.open("/data/daftar_dosen.csv", "w") do |f|
    f.puts "id;nama_dosen;nidn_dosen"
    dosens.each.with_index(1) do |dosen, index|
      f.puts "#{index};#{dosen[:nama_dosen]};#{dosen[:nidn_dosen]}"
    end
  end

  begin
    # blok ini bertugas untuk membuat koneksi ke database engine
    conn = PG::Connection.open(dbname: 'web_scraper')

    # blok ini bertugas untuk membuat tabel dan menghapus apabila sudah ada
    conn.exec("DROP TABLE IF EXISTS daftar_dosens")
    conn.exec("CREATE TABLE daftar_dosens(
              id BIGSERIAL NOT NULL PRIMARY KEY,
              nama_dosen VARCHAR(100) NOT NULL,
              nidn_dosen VARCHAR(10))")

    # blok ini bertugas untuk memasukkan data ke dalam tabel database
    conn.exec("COPY daftar_dosens(id, nama_dosen, nidn_dosen) FROM '/data/daftar_dosen.csv' DELIMITER ';' CSV HEADER")
  rescue PG::Error => e
    puts e.message
  ensure
    conn.close if conn
  end

  puts "TOTAL DOSEN: #{dosens.count} orang"
end

scraper

Sebelum kita menjalankan perintah untuk memanggil sang aktor utama, kita perlu menyipakan tempat untuk file .csv yang akan kita simpan pada direktori /data/. Alasan kenapa kita perlu menyiapkan tempat khusus karena permasalahan dengan permission user postgres. Jadi untuk kemudahan, kita siapkan tempat khusus yang dapat digunakan oleh keduabelah pihak baik user kita dan user postgres.

Saya sudah coba mengekspor file .csv ke direktori /tmp/ dan mengimportnya, namun gagal dan mendapatkan pesan error seperti ini.

ERROR:  could not open file "/tmp/daftar_dosen.csv" for reading: No such file or directory

Terkadang juga pesan error nya adalah permission denied.

ERROR:  could not open file "/tmp/daftar_dosen.csv" for reading: Permission Denied

Berdasarkan rekomendasi dari jawaban yang diberikan pada dba.stackexchange: Cannot read from /tmp with PostgreSQL COPY –saya mendemonstrasikan pada dokumentasi video di bawah–.

Maka dari itu saya mengakali dengan membuat sebuah temp direktori yang dapat diakses oleh keduabelah pihak.

Kita perlu membuat direktori /data/ terlebih dahulu.

$ sudo mkdir /data

Kemudian, mount dengan tipe tmpfs.

$ sudo mount -t tmpfs -o rw tmpfs /data

Setelah itu, jalankan dengan perintah,

$ ruby scraper.rb

Apabila berhasil, akan keluar output di terminal seperti ini. Tidak ada error apapun kecuali output jumlah dosen.

TOTAL DOSEN: 138 orang

Sekarang, kita akan punya file .csv yang berada pada direktori /data/daftar_dosen.csv.

id;nama_dosen;nidn_dosen
1;Abdul Fatah;1114107001
2;Abdul Hamid Kurniawan, S.kom., M.TI;1114107001
3;Abi Habibi;1114107001
...
...
136;Teguh Pribadi, S.H., M.H;-
137;Sampara, S.H., M.H.;-
138;Candra Bagus Agung P, S.E., M.M.;-

File .csv ini lah yang akan di import ke dalam database menggunakan SQL Query COPY FROM.

Sekarang coba cek ke database.

Masuk terlebih dahulu ke databse web_scraper.

bandithijo> \c web_scraper;
You are now connected to database "web_scraper" as user "bandithijo"
Time: 0.012s
web_scraper>

Setelah kita berada di dalam database web_scraper kita dapat melihat hasil dari data-data yang sudah diinputkan dengan cara.

web_scraper> SELECT * FROM daftar_dosens
+------+------------------------------------------------+--------------+
| id   | nama_dosen                                     | nidn_dosen   |
|------+------------------------------------------------+--------------|
| 1    | Abdul Fatah                                    | 1114107001   |
| 2    | Abdul Hamid Kurniawan, S.kom., M.TI            | 1114107001   |
| 3    | Abi Habibi                                     | 1114107001   |
| 4    | Abiratno, S.T., M.Sc.                          | 1107017201   |
| 5    | Dr. Agung Sakti Pribadi, S.H., M.H.            | 1131036301   |
| 6    | Alfa                                           | 9911002592   |
...
...
| 133  | Zara Zerina Azizah, S.Pd.I, S.E., M.M          | 1103039301   |
| 134  | Heru Zulkifli, S.Kom., M.Kom                   | 1115027501   |
| 135  | M.Andhi Rohmat Basuki, S.Kom., M.Kom           | -            |
| 136  | Teguh Pribadi, S.H., M.H                       | -            |
| 137  | Sampara, S.H., M.H.                            | -            |
| 138  | Candra Bagus Agung P, S.E., M.M.               | -            |
+------+------------------------------------------------+--------------+
SELECT 138
(END)

Selesai!

[ i ] Informasi

Apabila direktori /data/ yang berlaku sebagai temp direktori sudah tidak digunakan, kita dapat meng-unmount dengan cara,

$ sudo fusermount -u /data

Demonstrasi Video

Referensi

  1. It’s Time To HTTParty!
    Diakses tanggal: 2020/06/16

  2. nokogiri.org
    Diakses tanggal: 2020/06/16

  3. pg documentation
    Diakses tanggal: 2020/06/16

  4. pgcli
    Diakses tanggal: 2020/06/16

  5. Cannot read from /tmp with PostgreSQL COPY, but able to read the same file from another directory with the exact same permissions
    Diakses tanggal: 2020/06/16

Penulis

bandithijo

BanditHijo adalah nama pena saya – meminjam istilah keren dari para penulis. Teman-teman menyebut saya sebagai GNU/Linux Enthusiast. Saya memang gemar mengutak-atik sistem operasi ini. Bukan karena hobi tapi karena saya perlu untuk menggunakannya. Hehe.

- Rizqi Nur Assyaufi

Berlangganan via Email

Jangan sampai ketinggalan kabar dan info terbaru mengenai BanditHijo (R)-Chive.
Ayo bergabung!

d9fa304ba660ef3df99f5ad20c41927b51e6ff4b