Spaces:
Running
Running
File size: 14,976 Bytes
bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e 967a8f9 bb3c19e e9eb928 bb3c19e e9eb928 bb3c19e e9eb928 |
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
import os
import time
import gradio as gr
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import WebDriverException # Import specific exception
import tempfile
import logging
import atexit # Untuk cleanup saat skrip exit
from threading import Lock # Untuk thread safety jika Gradio menangani request secara konkuren
# --- Konfigurasi ---
CHROME_INSTANCE_TIMEOUT = 600 # Detik (10 menit)
# Konfigurasi logging dasar
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class ChromeDriverManager:
"""Mengelola satu instance WebDriver Chrome yang persisten dengan timeout."""
def __init__(self, timeout_seconds):
self.driver = None
self.last_used_time = None
self.timeout_seconds = timeout_seconds
self.lock = Lock() # Lock untuk mencegah race condition saat mengakses/membuat driver
self.options = self._configure_options()
logging.info(f"ChromeDriverManager diinisialisasi dengan timeout {timeout_seconds} detik.")
# Daftarkan fungsi cleanup untuk dijalankan saat skrip Python keluar
atexit.register(self.quit_driver)
def _configure_options(self):
"""Konfigurasi opsi Chrome (tanpa ukuran jendela awal)."""
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--remote-debugging-port=9222') # Opsional
# Jangan set window size di sini, akan diatur per permintaan
# chrome_options.add_argument('--window-size=1920,1080')
return chrome_options
def _initialize_driver(self):
"""Membuat instance WebDriver baru."""
logging.info("Menginisialisasi instance WebDriver Chrome baru...")
try:
# Pertimbangkan menggunakan webdriver-manager jika perlu
# from selenium.webdriver.chrome.service import Service as ChromeService
# from webdriver_manager.chrome import ChromeDriverManager
# service = ChromeService(executable_path=ChromeDriverManager().install())
# driver = webdriver.Chrome(service=service, options=self.options)
driver = webdriver.Chrome(options=self.options)
self.last_used_time = time.time()
logging.info("Instance WebDriver baru berhasil dibuat.")
return driver
except Exception as e:
logging.error(f"Gagal menginisialisasi WebDriver: {e}", exc_info=True)
# Set driver ke None jika gagal agar dicoba lagi nanti
self.driver = None
self.last_used_time = None
return None
def _is_driver_alive(self):
"""Memeriksa apakah driver masih responsif."""
if self.driver is None:
return False
try:
# Perintah ringan untuk memeriksa koneksi, misal mendapatkan URL saat ini
_ = self.driver.current_url
return True
except WebDriverException as e:
# Tangkap error spesifik yang mengindikasikan driver mati/tidak terhubung
logging.warning(f"Driver tampaknya tidak responsif: {e}")
return False
except Exception as e:
# Tangkap error lain yang mungkin terjadi
logging.warning(f"Error tidak terduga saat memeriksa status driver: {e}")
return False
def get_driver(self, requested_width, requested_height):
"""Mendapatkan instance driver yang ada atau membuat yang baru jika perlu."""
with self.lock: # Pastikan operasi ini thread-safe
current_time = time.time()
needs_new_driver = False
if self.driver is None:
logging.info("Tidak ada driver aktif, membuat yang baru.")
needs_new_driver = True
elif not self._is_driver_alive():
logging.warning("Driver yang ada tidak responsif, membuat yang baru.")
self.quit_driver(acquire_lock=False) # Tutup driver lama (lock sudah dipegang)
needs_new_driver = True
elif self.last_used_time and (current_time - self.last_used_time > self.timeout_seconds):
logging.info(f"Driver melewati batas waktu idle ({self.timeout_seconds}s). Membuat yang baru.")
self.quit_driver(acquire_lock=False) # Tutup driver lama (lock sudah dipegang)
needs_new_driver = True
if needs_new_driver:
self.driver = self._initialize_driver()
# Jika inisialisasi gagal, driver akan None, tangani di luar
if self.driver is None:
logging.error("Gagal membuat instance driver baru setelah mencoba.")
return None # Kembalikan None untuk menandakan kegagalan
# Jika driver berhasil didapatkan (baru atau lama)
if self.driver:
try:
# Atur ukuran jendela sesuai permintaan SETIAP kali driver digunakan
logging.info(f"Mengatur ukuran jendela driver ke {requested_width}x{requested_height}")
self.driver.set_window_size(requested_width, requested_height)
# Update waktu penggunaan terakhir HANYA jika berhasil mendapatkan/menggunakan driver
self.last_used_time = current_time
logging.info("Menggunakan instance driver yang ada/baru.")
except WebDriverException as e:
logging.error(f"Gagal mengatur ukuran jendela atau driver bermasalah: {e}. Menutup driver.", exc_info=True)
self.quit_driver(acquire_lock=False) # Tutup driver bermasalah
self.driver = None # Pastikan driver di-reset
return None # Kembalikan None untuk menandakan kegagalan
else:
# Kasus di mana _initialize_driver gagal dan self.driver masih None
logging.warning("Tidak dapat menyediakan instance driver.")
return self.driver # Kembalikan driver (bisa None jika gagal total)
def quit_driver(self, acquire_lock=True):
"""Menutup instance WebDriver jika ada."""
# Fungsi ini bisa dipanggil dari atexit atau saat timeout/error,
# jadi perlu penanganan lock yang fleksibel
if acquire_lock:
acquired = self.lock.acquire(timeout=5) # Coba kunci, timeout jika macet
if not acquired:
logging.error("Gagal mendapatkan lock untuk quit_driver. Melewati penutupan.")
return
try:
if self.driver:
logging.info("Menutup instance WebDriver...")
try:
self.driver.quit()
logging.info("WebDriver berhasil ditutup.")
except Exception as e:
logging.error(f"Error saat menutup WebDriver: {e}", exc_info=True)
finally:
# Selalu set ke None meskipun quit gagal
self.driver = None
self.last_used_time = None
else:
logging.debug("Tidak ada instance WebDriver aktif untuk ditutup.") # Log level debug
finally:
if acquire_lock and acquired: # Hanya release jika lock didapatkan di sini
self.lock.release()
# --- Buat instance manager global ---
chrome_manager = ChromeDriverManager(timeout_seconds=CHROME_INSTANCE_TIMEOUT)
# Function to capture a screenshot using the managed driver
def capture_screenshot_gradio(url, window_width, window_height, internal_delay):
"""
Mengambil screenshot halaman web menggunakan instance Chrome yang dikelola.
"""
# Validasi URL sederhana
if not url.startswith(('http://', 'https://')):
logging.info(f"Menambahkan 'http://' ke URL: {url}")
url = 'http://' + url
# Validasi input ukuran dan delay
try:
window_width = int(window_width)
window_height = int(window_height)
internal_delay = int(internal_delay)
if window_width <= 0 or window_height <= 0 or internal_delay < 0:
raise ValueError("Width, Height harus positif dan Delay tidak boleh negatif")
except (ValueError, TypeError) as e:
logging.error(f"Input tidak valid untuk ukuran/delay: {e}")
raise gr.Error(f"Input tidak valid: Lebar, Tinggi harus angka positif dan Delay tidak boleh negatif.")
logging.info(f"Permintaan screenshot untuk URL: {url}")
logging.info(f"Opsi: Lebar={window_width}, Tinggi={window_height}, Delay={internal_delay} detik")
driver = None # Inisialisasi driver di scope ini
screenshot_path = None
try:
# Dapatkan driver dari manager (akan membuat baru atau menggunakan yang ada)
# Ukuran jendela akan diatur di dalam get_driver
driver = chrome_manager.get_driver(window_width, window_height)
# Jika get_driver gagal mengembalikan driver
if driver is None:
raise gr.Error("Gagal mendapatkan atau menginisialisasi instance WebDriver Chrome.")
logging.info(f"Navigasi ke URL: {url}")
# Set page load timeout untuk mencegah hang jika halaman sangat lambat
driver.set_page_load_timeout(60) # Timeout 60 detik untuk memuat halaman
try:
driver.get(url)
except Exception as page_load_error:
logging.error(f"Timeout atau error saat memuat URL {url}: {page_load_error}")
# Pertimbangkan apakah driver harus ditutup jika hanya load gagal
# chrome_manager.quit_driver() # Opsional: tutup driver jika load gagal total
raise gr.Error(f"Gagal memuat URL {url}: {page_load_error}")
# 1. Tunggu hingga status dokumen 'complete'
logging.info("Menunggu document.readyState menjadi 'complete'...")
try:
WebDriverWait(driver, 45).until( # Timeout sedikit lebih lama
lambda d: d.execute_script('return document.readyState') == 'complete'
)
logging.info("document.readyState sudah 'complete'.")
except Exception as wait_error:
logging.warning(f"Timeout atau error saat menunggu readyState complete untuk {url}: {wait_error}")
# Lanjutkan saja, mungkin halaman masih bisa di-screenshot sebagian
# 2. Tambahkan delay internal setelah 'complete'
if internal_delay > 0:
logging.info(f"Menunggu delay tambahan selama {internal_delay} detik...")
time.sleep(internal_delay)
logging.info("Delay selesai.")
else:
logging.info("Tidak ada delay tambahan (0 detik).")
# Buat file sementara untuk menyimpan screenshot
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
screenshot_path = tmp_file.name
logging.info(f"Mengambil screenshot ke: {screenshot_path}")
driver.save_screenshot(screenshot_path)
logging.info("Screenshot berhasil diambil.")
return screenshot_path
except WebDriverException as wd_error:
# Jika terjadi error spesifik WebDriver, kemungkinan besar instance-nya bermasalah
logging.error(f"WebDriverException terjadi saat memproses {url}: {wd_error}", exc_info=True)
logging.warning("Menutup instance driver karena WebDriverException.")
chrome_manager.quit_driver() # Paksa tutup driver yang bermasalah
# Hapus file sementara jika ada
if screenshot_path and os.path.exists(screenshot_path):
try: os.remove(screenshot_path)
except OSError: pass
raise gr.Error(f"Terjadi masalah dengan browser: {wd_error}")
except Exception as e:
logging.error(f"Terjadi kesalahan umum saat memproses URL {url}: {e}", exc_info=True)
# Hapus file sementara jika ada
if screenshot_path and os.path.exists(screenshot_path):
try:
os.remove(screenshot_path)
logging.info(f"Membersihkan file sementara yang gagal: {screenshot_path}")
except OSError as remove_err:
logging.error(f"Gagal menghapus file sementara {screenshot_path}: {remove_err}")
# Tidak perlu menutup driver di sini kecuali errornya WebDriverException (sudah ditangani)
raise gr.Error(f"Gagal mengambil screenshot: {e}")
# finally:
# TIDAK ADA driver.quit() di sini lagi. Dikelola oleh ChromeDriverManager.
# Cukup pastikan instance manager dibuat di luar dan atexit terdaftar.
# logging.debug("Blok finally capture_screenshot_gradio selesai.") # Untuk debugging jika perlu
# --- Membuat Antarmuka Gradio (Sama seperti sebelumnya) ---
# 1. Definisikan Komponen Input
url_input = gr.Textbox(
label="Masukkan URL Website",
placeholder="contoh: https://www.google.com atau example.com"
)
width_input = gr.Number(label="Lebar Jendela (px)", value=1920, minimum=300, step=10, info="Lebar viewport browser.")
height_input = gr.Number(label="Tinggi Jendela (px)", value=1080, minimum=200, step=10, info="Tinggi viewport browser.")
delay_input = gr.Number(label="Delay Tambahan (detik)", value=5, minimum=0, step=1, info="Waktu tunggu ekstra setelah halaman 'lengkap'.")
# 2. Definisikan Komponen Output
output_image = gr.Image(type="filepath", label="Hasil Screenshot")
# 3. Membuat instance Interface Gradio
iface = gr.Interface(
fn=capture_screenshot_gradio,
inputs=[url_input, width_input, height_input, delay_input],
outputs=output_image,
title="Pengambil Screenshot Website Cepat (Chrome Persisten)",
description="Masukkan URL, atur ukuran jendela dan delay. Instance Chrome akan berjalan hingga 10 menit untuk mempercepat respons.",
allow_flagging='never',
concurrency_limit=None, # <--- DITAMBAHKAN DI SINI
examples=[
["https://gradio.app", 1280, 720, 3],
["https://github.com", 1920, 1080, 5],
["https://www.google.com/maps", 1024, 768, 8]
]
)
# Menjalankan aplikasi Gradio
if __name__ == "__main__":
print("Menjalankan aplikasi Gradio dengan instance Chrome persisten...")
print(f"Instance Chrome akan ditutup otomatis setelah {CHROME_INSTANCE_TIMEOUT} detik tidak aktif atau saat aplikasi berhenti.")
print("Pastikan Google Chrome dan ChromeDriver yang sesuai sudah terinstall.")
print("Anda mungkin perlu menginstal: pip install selenium webdriver-manager gradio") # webdriver-manager jika digunakan
iface.launch()
print("Aplikasi Gradio ditutup.")
# atexit akan memanggil chrome_manager.quit_driver() secara otomatis di sini |