diamond-in commited on
Commit
9ff601e
ยท
verified ยท
1 Parent(s): 2f1e9a4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +288 -55
app.py CHANGED
@@ -3,95 +3,328 @@ from selenium import webdriver
3
  from selenium.webdriver.chrome.service import Service
4
  from selenium.webdriver.chrome.options import Options
5
  from selenium.webdriver.common.by import By
 
 
6
  from webdriver_manager.chrome import ChromeDriverManager
 
7
  import time
 
 
8
 
9
- def _make_driver():
10
- chrome_options = Options()
11
- chrome_options.binary_location = "/usr/bin/chromium" # use HF Chromium
12
- chrome_options.add_argument("--headless")
13
- chrome_options.add_argument("--no-sandbox")
14
- chrome_options.add_argument("--disable-dev-shm-usage")
15
 
16
- # webdriver-manager downloads the matching driver
17
- service = Service(ChromeDriverManager().install())
18
- driver = webdriver.Chrome(service=service, options=chrome_options)
19
- driver.set_page_load_timeout(15)
20
- return driver
21
 
22
- def browse_and_extract(url: str, selector: str = "body") -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  try:
24
- driver = _make_driver()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  driver.get(url)
26
- elem = driver.find_element(By.CSS_SELECTOR, selector)
 
 
 
 
27
  text = elem.text
28
- driver.quit()
 
 
 
 
29
  return text
30
  except Exception as e:
 
 
 
 
 
 
31
  return f"Error: {e}"
32
 
33
- def screenshot(url: str) -> str:
 
 
34
  try:
35
- driver = _make_driver()
 
36
  driver.get(url)
 
 
 
 
37
  path = "/tmp/screenshot.png"
38
  driver.save_screenshot(path)
39
- driver.quit()
 
 
 
 
40
  return path
41
  except Exception as e:
42
- return f"Error: {e}"
 
 
 
 
 
 
43
 
44
- def click(url: str, selector: str) -> str:
 
 
45
  try:
46
- driver = _make_driver()
47
- driver.get(url)
48
- elem = driver.find_element(By.CSS_SELECTOR, selector)
 
 
 
 
 
 
49
  elem.click()
 
50
  time.sleep(2)
51
  title = driver.title
52
- driver.quit()
53
- return f"Clicked {selector}, new title: {title}"
 
 
 
 
54
  except Exception as e:
 
 
 
 
 
 
55
  return f"Error: {e}"
56
 
57
- def fill(url: str, selector: str, text: str) -> str:
 
 
58
  try:
59
- driver = _make_driver()
60
- driver.get(url)
61
- elem = driver.find_element(By.CSS_SELECTOR, selector)
 
 
 
 
 
 
62
  elem.clear()
63
  elem.send_keys(text)
64
- driver.quit()
 
 
 
65
  return f"Filled {selector} with '{text}'"
66
  except Exception as e:
 
 
 
 
 
 
67
  return f"Error: {e}"
68
 
69
- with gr.Blocks() as demo:
70
- gr.Markdown("# ๐ŸŒ MCP Browser (Selenium + Chromium)")
71
-
72
- with gr.Tab("Extract Text"):
73
- url_in = gr.Textbox(label="URL")
74
- sel_in = gr.Textbox(label="CSS Selector", value="body")
75
- out = gr.Textbox(label="Extracted Text", lines=15)
76
- gr.Button("Run").click(browse_and_extract, [url_in, sel_in], out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- with gr.Tab("Screenshot"):
79
- url_in2 = gr.Textbox(label="URL")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  out_img = gr.Image(label="Screenshot")
81
- gr.Button("Take Screenshot").click(screenshot, url_in2, out_img)
82
-
83
- with gr.Tab("Click"):
84
- url_in3 = gr.Textbox(label="URL")
85
- sel_in3 = gr.Textbox(label="CSS Selector")
86
- out3 = gr.Textbox(label="Result")
87
- gr.Button("Click").click(click, [url_in3, sel_in3], out3)
88
-
89
- with gr.Tab("Fill"):
90
- url_in4 = gr.Textbox(label="URL")
91
- sel_in4 = gr.Textbox(label="CSS Selector")
92
- txt_in4 = gr.Textbox(label="Text to Fill")
93
- out4 = gr.Textbox(label="Result")
94
- gr.Button("Fill").click(fill, [url_in4, sel_in4, txt_in4], out4)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  if __name__ == "__main__":
97
- demo.launch(mcp_server=True)
 
3
  from selenium.webdriver.chrome.service import Service
4
  from selenium.webdriver.chrome.options import Options
5
  from selenium.webdriver.common.by import By
6
+ from selenium.webdriver.support.ui import WebDriverWait
7
+ from selenium.webdriver.support import expected_conditions as EC
8
  from webdriver_manager.chrome import ChromeDriverManager
9
+ from webdriver_manager.core.os_manager import ChromeType
10
  import time
11
+ import logging
12
+ import os
13
 
14
+ # Set up logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
 
 
 
17
 
18
+ # Global browser session (optional - for persistent sessions)
19
+ _driver = None
 
 
 
20
 
21
+ def _make_driver(persistent=False):
22
+ """Create Chrome driver with proper configuration"""
23
+ global _driver
24
+
25
+ # If persistent and driver exists, return it
26
+ if persistent and _driver:
27
+ try:
28
+ # Check if driver is still alive
29
+ _driver.title
30
+ return _driver
31
+ except:
32
+ # Driver is dead, create new one
33
+ _driver = None
34
+
35
  try:
36
+ chrome_options = Options()
37
+
38
+ # Detect Chromium binary location
39
+ chromium_paths = [
40
+ "/usr/bin/chromium",
41
+ "/usr/bin/chromium-browser",
42
+ "/usr/bin/google-chrome",
43
+ "/usr/bin/google-chrome-stable"
44
+ ]
45
+
46
+ chromium_binary = None
47
+ for path in chromium_paths:
48
+ if os.path.exists(path):
49
+ chromium_binary = path
50
+ logger.info(f"Found Chromium at: {path}")
51
+ break
52
+
53
+ if chromium_binary:
54
+ chrome_options.binary_location = chromium_binary
55
+
56
+ # Essential headless options for Hugging Face Spaces
57
+ chrome_options.add_argument("--headless")
58
+ chrome_options.add_argument("--no-sandbox")
59
+ chrome_options.add_argument("--disable-dev-shm-usage")
60
+ chrome_options.add_argument("--disable-gpu")
61
+ chrome_options.add_argument("--disable-web-security")
62
+ chrome_options.add_argument("--disable-features=VizDisplayCompositor")
63
+ chrome_options.add_argument("--disable-setuid-sandbox")
64
+
65
+ # Performance options
66
+ chrome_options.add_argument("--memory-pressure-off")
67
+ chrome_options.add_argument("--max_old_space_size=4096")
68
+ chrome_options.add_argument("--disable-background-timer-throttling")
69
+ chrome_options.add_argument("--disable-renderer-backgrounding")
70
+ chrome_options.add_argument("--disable-features=TranslateUI")
71
+ chrome_options.add_argument("--disable-ipc-flooding-protection")
72
+
73
+ # Window size
74
+ chrome_options.add_argument("--window-size=1920,1080")
75
+
76
+ # Try multiple driver installation methods
77
+ try:
78
+ # Method 1: Use system chromium-driver if available
79
+ if os.path.exists("/usr/bin/chromedriver"):
80
+ service = Service("/usr/bin/chromedriver")
81
+ logger.info("Using system chromedriver")
82
+ else:
83
+ # Method 2: Use webdriver-manager with ChromeType.CHROMIUM
84
+ service = Service(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install())
85
+ logger.info("Using webdriver-manager chromedriver")
86
+ except Exception as e:
87
+ logger.warning(f"ChromeType.CHROMIUM failed: {e}")
88
+ # Method 3: Fallback to regular Chrome driver
89
+ service = Service(ChromeDriverManager().install())
90
+ logger.info("Using fallback Chrome driver")
91
+
92
+ driver = webdriver.Chrome(service=service, options=chrome_options)
93
+ driver.set_page_load_timeout(30)
94
+ driver.implicitly_wait(10)
95
+
96
+ if persistent:
97
+ _driver = driver
98
+
99
+ return driver
100
+
101
+ except Exception as e:
102
+ logger.error(f"Failed to create driver: {e}")
103
+ raise
104
+
105
+ def close_persistent_driver():
106
+ """Close the persistent driver if it exists"""
107
+ global _driver
108
+ if _driver:
109
+ try:
110
+ _driver.quit()
111
+ except:
112
+ pass
113
+ _driver = None
114
+ return "Persistent browser closed"
115
+ return "No persistent browser to close"
116
+
117
+ def browse_and_extract(url: str, selector: str = "body", use_persistent: bool = False) -> str:
118
+ """Browse URL and extract text from selector"""
119
+ driver = None
120
+ try:
121
+ driver = _make_driver(persistent=use_persistent)
122
+ logger.info(f"Navigating to: {url}")
123
  driver.get(url)
124
+
125
+ # Wait for element to be present
126
+ wait = WebDriverWait(driver, 10)
127
+ elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
128
+
129
  text = elem.text
130
+ logger.info(f"Extracted {len(text)} characters")
131
+
132
+ if not use_persistent and driver:
133
+ driver.quit()
134
+
135
  return text
136
  except Exception as e:
137
+ logger.error(f"Error in browse_and_extract: {e}")
138
+ if not use_persistent and driver:
139
+ try:
140
+ driver.quit()
141
+ except:
142
+ pass
143
  return f"Error: {e}"
144
 
145
+ def screenshot(url: str, use_persistent: bool = False) -> str:
146
+ """Take screenshot of URL"""
147
+ driver = None
148
  try:
149
+ driver = _make_driver(persistent=use_persistent)
150
+ logger.info(f"Taking screenshot of: {url}")
151
  driver.get(url)
152
+
153
+ # Wait for page load
154
+ time.sleep(2)
155
+
156
  path = "/tmp/screenshot.png"
157
  driver.save_screenshot(path)
158
+ logger.info(f"Screenshot saved to: {path}")
159
+
160
+ if not use_persistent and driver:
161
+ driver.quit()
162
+
163
  return path
164
  except Exception as e:
165
+ logger.error(f"Error in screenshot: {e}")
166
+ if not use_persistent and driver:
167
+ try:
168
+ driver.quit()
169
+ except:
170
+ pass
171
+ return None
172
 
173
+ def click(url: str, selector: str, use_persistent: bool = False) -> str:
174
+ """Click element on page"""
175
+ driver = None
176
  try:
177
+ driver = _make_driver(persistent=use_persistent)
178
+
179
+ # Navigate to URL if not persistent or new URL
180
+ if not use_persistent or driver.current_url != url:
181
+ logger.info(f"Navigating to: {url}")
182
+ driver.get(url)
183
+
184
+ wait = WebDriverWait(driver, 10)
185
+ elem = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector)))
186
  elem.click()
187
+
188
  time.sleep(2)
189
  title = driver.title
190
+ current_url = driver.current_url
191
+
192
+ if not use_persistent and driver:
193
+ driver.quit()
194
+
195
+ return f"Clicked {selector}\nNew title: {title}\nCurrent URL: {current_url}"
196
  except Exception as e:
197
+ logger.error(f"Error in click: {e}")
198
+ if not use_persistent and driver:
199
+ try:
200
+ driver.quit()
201
+ except:
202
+ pass
203
  return f"Error: {e}"
204
 
205
+ def fill(url: str, selector: str, text: str, use_persistent: bool = False) -> str:
206
+ """Fill text into form field"""
207
+ driver = None
208
  try:
209
+ driver = _make_driver(persistent=use_persistent)
210
+
211
+ # Navigate to URL if not persistent or new URL
212
+ if not use_persistent or driver.current_url != url:
213
+ logger.info(f"Navigating to: {url}")
214
+ driver.get(url)
215
+
216
+ wait = WebDriverWait(driver, 10)
217
+ elem = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
218
  elem.clear()
219
  elem.send_keys(text)
220
+
221
+ if not use_persistent and driver:
222
+ driver.quit()
223
+
224
  return f"Filled {selector} with '{text}'"
225
  except Exception as e:
226
+ logger.error(f"Error in fill: {e}")
227
+ if not use_persistent and driver:
228
+ try:
229
+ driver.quit()
230
+ except:
231
+ pass
232
  return f"Error: {e}"
233
 
234
+ def execute_js(url: str, script: str, use_persistent: bool = False) -> str:
235
+ """Execute JavaScript on page"""
236
+ driver = None
237
+ try:
238
+ driver = _make_driver(persistent=use_persistent)
239
+
240
+ # Navigate to URL if not persistent or new URL
241
+ if not use_persistent or driver.current_url != url:
242
+ logger.info(f"Navigating to: {url}")
243
+ driver.get(url)
244
+
245
+ result = driver.execute_script(script)
246
+
247
+ if not use_persistent and driver:
248
+ driver.quit()
249
+
250
+ return f"Script executed. Result: {result}"
251
+ except Exception as e:
252
+ logger.error(f"Error in execute_js: {e}")
253
+ if not use_persistent and driver:
254
+ try:
255
+ driver.quit()
256
+ except:
257
+ pass
258
+ return f"Error: {e}"
259
 
260
+ # Gradio Interface
261
+ with gr.Blocks() as demo:
262
+ gr.Markdown("""
263
+ # ๐ŸŒ MCP Browser (Selenium + Chromium)
264
+
265
+ **Fixed version** with proper ChromeDriver management for Chromium v140+
266
+
267
+ โœ… Features:
268
+ - Auto-detects ChromeDriver version
269
+ - Optional persistent browser sessions
270
+ - JavaScript execution
271
+ - Better error handling
272
+ """)
273
+
274
+ with gr.Tab("๐Ÿ” Extract Text"):
275
+ with gr.Row():
276
+ url_in = gr.Textbox(label="URL", value="https://example.com")
277
+ sel_in = gr.Textbox(label="CSS Selector", value="body")
278
+ persist_check = gr.Checkbox(label="Use Persistent Browser", value=False)
279
+ out = gr.Textbox(label="Extracted Text", lines=10)
280
+ gr.Button("Extract").click(browse_and_extract, [url_in, sel_in, persist_check], out)
281
+
282
+ with gr.Tab("๐Ÿ“ธ Screenshot"):
283
+ with gr.Row():
284
+ url_in2 = gr.Textbox(label="URL", value="https://example.com")
285
+ persist_check2 = gr.Checkbox(label="Use Persistent Browser", value=False)
286
  out_img = gr.Image(label="Screenshot")
287
+ gr.Button("Take Screenshot").click(screenshot, [url_in2, persist_check2], out_img)
288
+
289
+ with gr.Tab("๐Ÿ‘† Click"):
290
+ with gr.Row():
291
+ url_in3 = gr.Textbox(label="URL")
292
+ sel_in3 = gr.Textbox(label="CSS Selector", placeholder="button#submit")
293
+ persist_check3 = gr.Checkbox(label="Use Persistent Browser", value=False)
294
+ out3 = gr.Textbox(label="Result", lines=4)
295
+ gr.Button("Click").click(click, [url_in3, sel_in3, persist_check3], out3)
296
+
297
+ with gr.Tab("โœ๏ธ Fill"):
298
+ with gr.Row():
299
+ url_in4 = gr.Textbox(label="URL")
300
+ sel_in4 = gr.Textbox(label="CSS Selector", placeholder="input#username")
301
+ txt_in4 = gr.Textbox(label="Text to Fill")
302
+ persist_check4 = gr.Checkbox(label="Use Persistent Browser", value=False)
303
+ out4 = gr.Textbox(label="Result", lines=2)
304
+ gr.Button("Fill").click(fill, [url_in4, sel_in4, txt_in4, persist_check4], out4)
305
+
306
+ with gr.Tab("๐Ÿ’ป Execute JS"):
307
+ with gr.Row():
308
+ url_in5 = gr.Textbox(label="URL", value="https://example.com")
309
+ persist_check5 = gr.Checkbox(label="Use Persistent Browser", value=False)
310
+ script_in5 = gr.Textbox(
311
+ label="JavaScript Code",
312
+ lines=5,
313
+ value="return document.title + ' - ' + document.URL;"
314
+ )
315
+ out5 = gr.Textbox(label="Result", lines=3)
316
+ gr.Button("Execute").click(execute_js, [url_in5, script_in5, persist_check5], out5)
317
+
318
+ with gr.Tab("๐Ÿ”ง Browser Control"):
319
+ close_out = gr.Textbox(label="Status")
320
+ gr.Button("Close Persistent Browser").click(close_persistent_driver, [], close_out)
321
+
322
+ gr.Markdown("""
323
+ ### Usage Tips:
324
+ 1. **Persistent Browser**: Keep the same browser session across multiple actions
325
+ 2. **CSS Selectors**: Use standard CSS selectors (e.g., `#id`, `.class`, `tag`)
326
+ 3. **JavaScript**: Execute any JS code and get return values
327
+ """)
328
 
329
  if __name__ == "__main__":
330
+ demo.launch(mcp_server=True)