多浏览器轮询打码服务(单例模式) 支持配置浏览器数量,每个浏览器只开 1 个标签页,请求轮询分配
| 2538 | |
| 2539 | |
| 2540 | class BrowserCaptchaService: |
| 2541 | """多浏览器轮询打码服务(单例模式) |
| 2542 | |
| 2543 | 支持配置浏览器数量,每个浏览器只开 1 个标签页,请求轮询分配 |
| 2544 | """ |
| 2545 | |
| 2546 | _instance: Optional['BrowserCaptchaService'] = None |
| 2547 | _lock = asyncio.Lock() |
| 2548 | |
| 2549 | def __init__(self, db=None): |
| 2550 | self.db = db |
| 2551 | self.website_key = "6LdsFiUsAAAAAIjVDZcuLhaHiDn5nnHVXVRQGeMV" |
| 2552 | self.base_user_data_dir = os.path.join(os.getcwd(), "browser_data_rt") |
| 2553 | self._browsers: Dict[int, TokenBrowser] = {} |
| 2554 | self._browsers_lock = asyncio.Lock() |
| 2555 | self._slot_allocation_lock = asyncio.Lock() |
| 2556 | self._slot_reservations: Dict[int, int] = {} |
| 2557 | |
| 2558 | # ??????? |
| 2559 | self._browser_count = 1 # ?? 1 ????????? |
| 2560 | self._round_robin_index = 0 # ???? |
| 2561 | # ???? |
| 2562 | self._stats = { |
| 2563 | "req_total": 0, |
| 2564 | "gen_ok": 0, |
| 2565 | "gen_fail": 0, |
| 2566 | "api_403": 0 |
| 2567 | } |
| 2568 | |
| 2569 | # ?????? _load_browser_count ??????? |
| 2570 | self._token_semaphore = None |
| 2571 | self._idle_reaper_task: Optional[asyncio.Task] = None |
| 2572 | |
| 2573 | async def _ensure_idle_reaper(self): |
| 2574 | if self._idle_reaper_task is None or self._idle_reaper_task.done(): |
| 2575 | self._idle_reaper_task = asyncio.create_task(self._idle_reaper_loop()) |
| 2576 | |
| 2577 | async def _idle_reaper_loop(self): |
| 2578 | while True: |
| 2579 | try: |
| 2580 | await asyncio.sleep(15) |
| 2581 | idle_ttl = int(getattr(config, "browser_idle_ttl_seconds", 600) or 600) |
| 2582 | browsers = [] |
| 2583 | async with self._browsers_lock: |
| 2584 | browsers = list(self._browsers.values()) |
| 2585 | for browser in browsers: |
| 2586 | try: |
| 2587 | if browser.is_busy(): |
| 2588 | continue |
| 2589 | if not browser.has_shared_browser(): |
| 2590 | continue |
| 2591 | if browser.idle_seconds() < idle_ttl: |
| 2592 | continue |
| 2593 | await browser.recycle_browser(reason=f"idle_ttl_{idle_ttl}s", rotate_profile=False) |
| 2594 | except Exception as e: |
| 2595 | debug_logger.log_warning(f"[BrowserCaptcha] idle reaper failed: {e}") |
| 2596 | except asyncio.CancelledError: |
| 2597 | return |
nothing calls this directly
no outgoing calls
no test coverage detected