prince1604 commited on
Commit ·
503011f
1
Parent(s): f2e524e
Update Crawler: Add stealth args and challenge detection for Cloudflare
Browse files- src/crawler.py +59 -14
src/crawler.py
CHANGED
|
@@ -148,7 +148,7 @@ class Crawler:
|
|
| 148 |
|
| 149 |
def _fetch_playwright(self, url):
|
| 150 |
"""
|
| 151 |
-
Fallback Strategy A: Use Playwright to render full page.
|
| 152 |
"""
|
| 153 |
try:
|
| 154 |
from playwright.sync_api import sync_playwright
|
|
@@ -160,54 +160,99 @@ class Crawler:
|
|
| 160 |
# Acquire semaphore to prevent resource exhaustion
|
| 161 |
with self.playwright_semaphore:
|
| 162 |
with sync_playwright() as p:
|
| 163 |
-
#
|
| 164 |
-
# headless=
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
# Context with realistic viewport and locale
|
| 168 |
context = browser.new_context(
|
| 169 |
viewport={'width': 1920, 'height': 1080},
|
| 170 |
-
user_agent=self.ua.random,
|
| 171 |
locale="en-US",
|
| 172 |
timezone_id="America/New_York",
|
| 173 |
-
java_script_enabled=True
|
|
|
|
|
|
|
| 174 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
page = context.new_page()
|
| 177 |
|
| 178 |
# BLOCK RESOURCES for speed: images, fonts, css
|
| 179 |
-
|
|
|
|
|
|
|
| 180 |
|
| 181 |
try:
|
| 182 |
logger.info(f"Playwright navigating to {url}...")
|
| 183 |
-
page.goto(url, timeout=
|
| 184 |
|
| 185 |
-
# Wait for
|
| 186 |
-
page.wait_for_timeout(
|
| 187 |
|
| 188 |
-
#
|
| 189 |
title = page.title()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
logger.info(f"Page Title: {title}")
|
| 191 |
|
| 192 |
-
|
|
|
|
| 193 |
|
| 194 |
# Explicit cleanup
|
| 195 |
page.close()
|
| 196 |
context.close()
|
| 197 |
browser.close()
|
| 198 |
|
| 199 |
-
return
|
| 200 |
|
| 201 |
except Exception as nav:
|
| 202 |
logger.error(f"Playwright navigation error: {nav}")
|
| 203 |
return None
|
| 204 |
finally:
|
| 205 |
-
# Ensure browser is closed even if errors occur
|
| 206 |
try:
|
| 207 |
browser.close()
|
| 208 |
except:
|
| 209 |
pass
|
| 210 |
|
|
|
|
| 211 |
except Exception as e:
|
| 212 |
logger.error(f"Playwright critical error: {e}")
|
| 213 |
return None
|
|
|
|
| 148 |
|
| 149 |
def _fetch_playwright(self, url):
|
| 150 |
"""
|
| 151 |
+
Fallback Strategy A: Use Playwright to render full page with Stealth Mode.
|
| 152 |
"""
|
| 153 |
try:
|
| 154 |
from playwright.sync_api import sync_playwright
|
|
|
|
| 160 |
# Acquire semaphore to prevent resource exhaustion
|
| 161 |
with self.playwright_semaphore:
|
| 162 |
with sync_playwright() as p:
|
| 163 |
+
# Stealth Args to bypass basic detection
|
| 164 |
+
# We keep headless=True for performance, but mask it
|
| 165 |
+
args = [
|
| 166 |
+
"--disable-blink-features=AutomationControlled",
|
| 167 |
+
"--no-sandbox",
|
| 168 |
+
"--disable-setuid-sandbox",
|
| 169 |
+
"--disable-dev-shm-usage",
|
| 170 |
+
"--disable-accelerated-2d-canvas",
|
| 171 |
+
"--no-first-run",
|
| 172 |
+
"--no-zygote",
|
| 173 |
+
"--disable-gpu",
|
| 174 |
+
"--hide-scrollbars",
|
| 175 |
+
"--mute-audio",
|
| 176 |
+
]
|
| 177 |
+
|
| 178 |
+
browser = p.chromium.launch(headless=True, args=args)
|
| 179 |
|
| 180 |
# Context with realistic viewport and locale
|
| 181 |
context = browser.new_context(
|
| 182 |
viewport={'width': 1920, 'height': 1080},
|
| 183 |
+
user_agent=self.ua.random, # Rotate UA
|
| 184 |
locale="en-US",
|
| 185 |
timezone_id="America/New_York",
|
| 186 |
+
java_script_enabled=True,
|
| 187 |
+
has_touch=True,
|
| 188 |
+
is_mobile=False
|
| 189 |
)
|
| 190 |
+
|
| 191 |
+
# --- KEY STEALTH INJECTION ---
|
| 192 |
+
# Override navigator.webdriver to undefined
|
| 193 |
+
context.add_init_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
| 194 |
+
# mask chrome runtime
|
| 195 |
+
context.add_init_script("window.chrome = { runtime: {} };")
|
| 196 |
+
# -----------------------------
|
| 197 |
|
| 198 |
page = context.new_page()
|
| 199 |
|
| 200 |
# BLOCK RESOURCES for speed: images, fonts, css
|
| 201 |
+
# CAREFUL: Some WAFs check if CSS loads. If blocking persists, disable this.
|
| 202 |
+
# For now, we block images/fonts but keep CSS/JS
|
| 203 |
+
page.route("**/*.{png,jpg,jpeg,gif,svg,woff,woff2,ttf,otf}", lambda route: route.abort())
|
| 204 |
|
| 205 |
try:
|
| 206 |
logger.info(f"Playwright navigating to {url}...")
|
| 207 |
+
page.goto(url, timeout=90000, wait_until="domcontentloaded")
|
| 208 |
|
| 209 |
+
# Wait for hydration
|
| 210 |
+
page.wait_for_timeout(2000)
|
| 211 |
|
| 212 |
+
# --- Cloudflare / WAF Detection & Handling ---
|
| 213 |
title = page.title()
|
| 214 |
+
content = page.content().lower()
|
| 215 |
+
|
| 216 |
+
retries = 0
|
| 217 |
+
while retries < 2 and ("just a moment..." in title.lower() or "challenge" in title.lower() or "verify you are human" in content):
|
| 218 |
+
logger.warning(f"Detected Cloudflare Challenge ({title}). Waiting for 15s...")
|
| 219 |
+
# Simulate mouse movement
|
| 220 |
+
try:
|
| 221 |
+
page.mouse.move(100, 100)
|
| 222 |
+
page.mouse.down()
|
| 223 |
+
page.mouse.up()
|
| 224 |
+
page.mouse.move(200, 200)
|
| 225 |
+
except:
|
| 226 |
+
pass
|
| 227 |
+
|
| 228 |
+
page.wait_for_timeout(15000)
|
| 229 |
+
title = page.title()
|
| 230 |
+
content = page.content().lower()
|
| 231 |
+
retries += 1
|
| 232 |
+
# ---------------------------------------------
|
| 233 |
+
|
| 234 |
logger.info(f"Page Title: {title}")
|
| 235 |
|
| 236 |
+
# Final content grab
|
| 237 |
+
final_content = page.content()
|
| 238 |
|
| 239 |
# Explicit cleanup
|
| 240 |
page.close()
|
| 241 |
context.close()
|
| 242 |
browser.close()
|
| 243 |
|
| 244 |
+
return final_content
|
| 245 |
|
| 246 |
except Exception as nav:
|
| 247 |
logger.error(f"Playwright navigation error: {nav}")
|
| 248 |
return None
|
| 249 |
finally:
|
|
|
|
| 250 |
try:
|
| 251 |
browser.close()
|
| 252 |
except:
|
| 253 |
pass
|
| 254 |
|
| 255 |
+
|
| 256 |
except Exception as e:
|
| 257 |
logger.error(f"Playwright critical error: {e}")
|
| 258 |
return None
|