GhostBrowser / browser_pool.py
M-hv1's picture
Update browser_pool.py
e957604 verified
Raw
History Blame Contribute Delete
6.07 kB
"""
╔══════════════════════════════════════════════════════════════╗
║ Browser Pool Manager for Titan Downloader ║
║ Enhanced: visits target URL, realistic headers ║
╚══════════════════════════════════════════════════════════════╝
"""
import asyncio
import logging
import os
import tempfile
import urllib.parse
from typing import List, Optional
import yt_dlp
from playwright.async_api import async_playwright, Browser, BrowserContext, Playwright
log = logging.getLogger("titan-dl.browser")
HEADLESS = os.environ.get("BROWSER_HEADLESS", "true").lower() == "true"
PROXY_SERVER = os.environ.get("PROXY_SERVER")
USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36"
)
class BrowserPool:
_playwright: Optional[Playwright] = None
_browsers: List[Browser] = []
_lock = asyncio.Lock()
_initialized = False
@classmethod
async def initialize(cls, pool_size: int = 4):
if cls._initialized:
return
cls._playwright = await async_playwright().start()
for _ in range(pool_size):
browser = await cls._launch_browser()
cls._browsers.append(browser)
cls._initialized = True
log.info(f"Browser pool initialized with {pool_size} browsers.")
@classmethod
async def _launch_browser(cls) -> Browser:
launch_args = [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-blink-features=AutomationControlled",
"--disable-web-security",
"--disable-features=IsolateOrigins,site-per-process",
"--disable-site-isolation-trials",
]
if PROXY_SERVER:
launch_args.append(f"--proxy-server={PROXY_SERVER}")
browser = await cls._playwright.chromium.launch(
headless=HEADLESS,
args=launch_args,
)
return browser
@classmethod
async def acquire_context(cls) -> BrowserContext:
async with cls._lock:
if not cls._browsers:
log.warning("Browser pool exhausted, creating temporary browser.")
browser = await cls._launch_browser()
else:
browser = cls._browsers.pop()
try:
context = await browser.new_context(
user_agent=USER_AGENT,
viewport={"width": 1920, "height": 1080},
locale="en-US",
timezone_id="America/New_York",
extra_http_headers={
"Accept-Language": "en-US,en;q=0.9",
"sec-ch-ua": '"Chromium";v="131", "Not_A Brand";v="24"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
},
)
context.on("close", lambda: asyncio.create_task(cls._return_browser(browser)))
return context
except Exception:
await cls._return_browser(browser)
raise
@classmethod
async def _return_browser(cls, browser: Browser):
async with cls._lock:
if len(cls._browsers) < 4:
cls._browsers.append(browser)
else:
await browser.close()
@classmethod
async def close(cls):
async with cls._lock:
for browser in cls._browsers:
await browser.close()
cls._browsers.clear()
if cls._playwright:
await cls._playwright.stop()
cls._initialized = False
async def extract_with_browser(url: str) -> dict:
"""
استخراج معلومات الفيديو باستخدام متصفح حقيقي.
يقوم بزيارة صفحة الفيديو نفسها لتوليد الكوكيز المناسبة.
"""
context = await BrowserPool.acquire_context()
try:
page = await context.new_page()
# زيارة صفحة الفيديو مباشرة (وليس فقط الصفحة الرئيسية)
log.info(f"🌐 Visiting video URL with browser: {url[:60]}...")
await page.goto(url, wait_until="domcontentloaded", timeout=30000)
# انتظر قليلاً لتحميل بعض العناصر وتنفيذ السكريبتات
await asyncio.sleep(3)
# استخراج الكوكيز
cookies = await context.cookies()
netscape_cookies = _cookies_to_netscape(cookies)
# كتابة الكوكيز إلى ملف مؤقت
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
f.write(netscape_cookies)
cookie_file = f.name
try:
from main import YDL_OPTS_BASE, _extract_info_ytdlp
opts = YDL_OPTS_BASE.copy()
opts["cookiefile"] = cookie_file
if PROXY_SERVER:
opts["proxy"] = PROXY_SERVER
log.info("🔧 Running yt-dlp with fresh browser cookies...")
data = _extract_info_ytdlp(url, ydl_opts=opts)
return data
finally:
os.unlink(cookie_file)
finally:
await context.close()
def _cookies_to_netscape(cookies: list) -> str:
lines = ["# Netscape HTTP Cookie File", ""]
for c in cookies:
domain = c.get("domain", "")
flag = "TRUE" if domain.startswith(".") else "FALSE"
secure = "TRUE" if c.get("secure", False) else "FALSE"
expires = str(int(c.get("expires", -1) or -1))
name = c.get("name", "")
value = c.get("value", "")
path = c.get("path", "/")
lines.append(f"{domain}\t{flag}\t{path}\t{secure}\t{expires}\t{name}\t{value}")
return "\n".join(lines)