Post

Web Scraping Data Kost di Malang dari Mamikos dengan Selenium

Web Scraping Data Kost di Malang dari Mamikos dengan Selenium

Web scraping memungkinkan kita mengambil data dari halaman web secara otomatis. Salah satu studi kasus menarik adalah mengambil data kost dari situs populer seperti Mamikos. Dalam tutorial ini, kita akan menggunakan Selenium untuk melakukan scraping data kost di Kota Malang karena kontennya bersifat lazy-load dan dinamis, sehingga tidak bisa langsung diambil hanya dengan requests atau BeautifulSoup saja.


Tentang Mamikos

Mamikos adalah platform pencarian kost online terbesar di Indonesia yang memudahkan pencari kost menemukan hunian sesuai kebutuhan mereka. Mamikos menyediakan informasi lengkap mengenai:

  • Tipe kost (Putra, Putri, Campur)
  • Harga sewa per bulan/tahun
  • Lokasi dan jarak ke kampus/tempat kerja
  • Fasilitas seperti Wi-Fi, kamar mandi dalam, AC, dapur, dan lain-lain
  • Rating, ulasan, dan foto kamar

Situs ini menggunakan infinite scroll dan tombol ‘Lihat lebih banyak lagi’ untuk menampilkan lebih banyak hasil, yang artinya kontennya dimuat secara dinamis menggunakan JavaScript. Oleh karena itu, scraping data dari Mamikos membutuhkan pendekatan berbasis browser seperti Selenium.


Tujuan

  • Mengambil data kost bulanan di Kota Malang dari situs Mamikos.
  • Menangani pemuatan konten dinamis (lazy load dan pagination).
  • Menyimpan hasil dalam format CSV.

Tools dan Teknologi

Library / ToolFungsi
SeleniumKontrol browser untuk scraping halaman dinamis
BeautifulSoupMemparsing HTML dan mengambil elemen data
PandasMenyimpan data ke dalam bentuk tabel (CSV)
timeMemberi delay agar konten dimuat dengan benar

Kode Lengkap Web Scraping Mamikos

1. Import Library

Pertama-tama kita harus impor dulu semua library yang dibutuhkan. Selenium itu buat ngontrol browser (kayak ngeklik tombol atau scroll halaman), BeautifulSoup dipakai buat baca HTML dari halaman web, pandas buat nyimpen datanya ke CSV, dan time dipakai untuk kasih jeda agar elemen web sempat dimuat.

1
2
3
4
5
6
7
8
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import pandas as pd
import time

2. Setup Driver

Fungsi ini kita pakai buat bikin browser otomatis yang udah dikonfigurasi. Di sini kita tambahkan beberapa opsi biar situs gak langsung curiga kalau kita bot. Misalnya, user-agent kita ubah supaya terlihat kayak pengguna Chrome biasa.

1
2
3
4
5
6
7
def setup_driver():
    options = webdriver.ChromeOptions()
    options.add_argument("--disable-gpu")
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
    return webdriver.Chrome(options=options)

3. Fungsi Klik Tombol “Lihat Lebih Banyak Lagi”

Langkah selanjutnya, program bakal menjalankan fungsi expand_all_listings() untuk nge-klik tombol “Lihat lebih banyak lagi” beberapa kali. Tujuannya jelas: supaya hasil kost yang muncul makin banyak.

1
2
3
4
5
6
7
8
9
10
11
def expand_all_listings(driver, max_clicks=5):
    for _ in range(max_clicks):
        try:
            load_button = WebDriverWait(driver, 5).until(
                EC.element_to_be_clickable((By.CLASS_NAME, "nominatim-list__see-more"))
            )
            ActionChains(driver).move_to_element(load_button).click().perform()
            time.sleep(2)
        except:
            print("Tidak ada tombol 'Lihat lebih banyak lagi', lanjut ke scroll...")
            break

4. Fungsi Scroll Otomatis

Setelah itu, program lanjut scroll ke bawah beberapa kali buat memicu teknik lazy load-nya — ini penting karena data kost baru dimuat saat halaman di-scroll.

1
2
3
4
5
6
7
8
9
def scroll_page(driver, times=5, pause=2):
    last_height = driver.execute_script("return document.body.scrollHeight")
    for _ in range(times):
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(pause)
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            break
        last_height = new_height

5. Fungsi Pengaman Ambil Teks

Menghindari error saat elemen HTML tidak ditemukan, fungsi ini mengembalikan teks atau default.

1
2
def get_text_or_default(tag, default="Tidak ada"):
    return tag.text.strip() if tag else default

6. Ekstrak Data Kost dari HTML

Fungsi ini mem-parsing elemen-elemen data dari setiap kost card: gambar, nama, tipe, lokasi, fasilitas, harga, dan rating.

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
def extract_data_from_page(html):
    soup = BeautifulSoup(html, "html.parser")
    kost_cards = soup.select('div[data-testid="kostRoomCard"]')
    print(f"\U0001F4E6 Total kost ditemukan: {len(kost_cards)}")

    hasil = []

    for card in kost_cards:
        img = card.select_one('img[data-testid="roomCardCover-photo"]')
        image_url = img.get("src") or img.get("data-src") if img else "Tidak ada gambar"

        name = get_text_or_default(card.select_one(".rc-info__name"))
        tipe = get_text_or_default(card.select_one(".rc-overview__label"), "Tidak diketahui")
        lokasi = get_text_or_default(card.select_one(".rc-info__location"))
        rating = get_text_or_default(card.select_one(".rc-overview__rating-text"), "Tidak ada rating")

        fasilitas_tags = card.select('span[data-testid="roomCardFacilities-facility"]')
        fasilitas = ", ".join(tag.get_text(strip=True) for tag in fasilitas_tags) if fasilitas_tags else "Tidak ada"

        harga_diskon = get_text_or_default(card.select_one(".rc-price__text"), "Tidak ada harga")
        harga_awal = get_text_or_default(card.select_one(".rc-price__additional-discount-price"), "Tidak ada")

        hasil.append([name, tipe, lokasi, fasilitas, rating, harga_awal, harga_diskon, image_url])

    return hasil

7. Bagian Utama Program (Main Execution)

Setelah dipastikan semua elemen data kost udah muncul, program akan ngecek keberadaan elemen HTML bernama kostRoomCard (yaitu setiap kotak kost di halaman). Kalau elemen ini berhasil ditemukan, program lanjut ke proses ekstraksi data dengan memanggil extract_data_from_page() yang akan parsing data satu per satu. Data yang dikumpulkan kemudian diubah jadi DataFrame dengan pandas, lalu disimpan sebagai file CSV bernama kost_malang_mamikos.csv. Terakhir, browser ditutup dan jumlah data yang berhasil diambil akan ditampilkan.

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
if __name__ == "__main__":
    url = "https://mamikos.com/cari/malang-kota-malang-jawa-timur-indonesia/all/bulanan/0-15000000"
    browser = setup_driver()
    browser.get(url)
    time.sleep(3)

    expand_all_listings(browser)
    scroll_page(browser, times=6)

    try:
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, 'div[data-testid="kostRoomCard"]'))
        )
    except:
        print("Elemen data kost tidak ditemukan!")

    kost_data = extract_data_from_page(browser.page_source)
    browser.quit()

    df = pd.DataFrame(kost_data, columns=[
        "Nama Kost", "Tipe Kost", "Lokasi", "Fasilitas",
        "Rating", "Harga Sebelum Diskon", "Harga Setelah Diskon", "Gambar"
    ])
    df.to_csv("kost_malang_mamikos.csv", index=False, encoding="utf-8-sig")

    print("Data berhasil disimpan ke kost_malang_mamikos.csv")
    print(df.shape)

Hasil

Output FileDeskripsi
kost_malang_mamikos.csvData kost dari Mamikos kota Malang

Kendala & Solusi

MasalahSolusi
Lazy loading menyebabkan data tidak lengkapGunakan scroll otomatis
Elemen tidak muncul saat parsingTambahkan delay dengan WebDriverWait
Situs mendeteksi botGunakan user-agent dan disable-blink-features

Rencana Lanjutan

  • Menambahkan filter berdasarkan harga maksimal/minimal.
  • Menyimpan ke database SQL.
  • Menambahkan deteksi otomatis jumlah halaman.

Referensi

This post is licensed under CC BY 4.0 by the author.