Zhofang commited on
Commit
e9eb928
·
verified ·
1 Parent(s): 007ecad

Update app.py

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