From b33490f291041f5378421ab71686fb6e2fd89574 Mon Sep 17 00:00:00 2001
From: Vinyzu <50874994+Vinyzu@users.noreply.github.com>
Date: Mon, 12 Feb 2024 20:23:05 +0100
Subject: [PATCH] Update v1.3
- Fixed Timeout Retrying
- Faster Detector Model Loading
- Improved ReadMe
- Updated Github Workflow to PreInstall Huggingface Models
- Updated Requirements
---
.github/workflows/ci.yml | 5 +++
README.md | 17 +++++++--
recognizer/__init__.py | 2 +-
recognizer/agents/playwright/async_control.py | 29 +++++++++------
recognizer/agents/playwright/sync_control.py | 29 +++++++++------
recognizer/components/detector.py | 37 +++++++++++++------
requirements-test.txt | 2 +-
requirements.txt | 6 +--
setup.cfg | 2 +-
9 files changed, 85 insertions(+), 44 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c036251..df1416f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,5 +49,10 @@ jobs:
uses: browser-actions/setup-chrome@v1
- name: Install Chromium Driver
run: python -m playwright install chromium
+ - name: Install HuggingFace Models
+ run: |
+ pip install -U "huggingface_hub[cli]"
+ huggingface-cli download flavour/CLIP-ViT-B-16-DataComp.XL-s13B-b90K config.json
+ huggingface-cli download CIDAS/clipseg-rd64-refined config.json
- name: Test with PyTest
run: pytest
\ No newline at end of file
diff --git a/README.md b/README.md
index ad41905..922ed7b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
-# reCognizer v1.2
-![Tests & Linting](https://github.com/Vinyzu/recognizer/actions/workflows/tests.yml/badge.svg)
+# reCognizer v1.3
+![Tests & Linting](https://github.com/Vinyzu/recognizer/actions/workflows/ci.yml/badge.svg)
+
#### reCognizer is a free-to-use AI based [reCaptcha](https://developers.google.com/recaptcha) Solver.
Usable with an easy-to-use API, also available for Async and Sync Playwright.
You can pass almost any format into the Challenger, from full-page screenshots, only-captcha images and no-border images to single images in a list.
@@ -13,6 +14,16 @@ pip install recognizer
---
+## Examples
+
+### Possible Image Inputs
+![Accepted Formats](https://i.ibb.co/nztTD9Z/formats.png)
+
+### Example Solve Video (Good IP & Botright)
+https://github.com/Vinyzu/recognizer/assets/50874994/95a713e3-bb46-474b-994f-cb3dacae9279
+
+---
+
## Basic Usage
```py
@@ -107,7 +118,7 @@ asyncio.run(main())
---
-![Version](https://img.shields.io/badge/reCognizer-v1.2-blue)
+![Version](https://img.shields.io/badge/reCognizer-v1.3-blue)
![License](https://img.shields.io/badge/License-GNU%20GPL-green)
![Python](https://img.shields.io/badge/Python-v3.x-lightgrey)
diff --git a/recognizer/__init__.py b/recognizer/__init__.py
index 3680bdb..0a2b1cd 100644
--- a/recognizer/__init__.py
+++ b/recognizer/__init__.py
@@ -2,6 +2,6 @@
from .components.detector import Detector
-VERSION = 1.2
+VERSION = 1.3
__all__ = ["Detector", "VERSION"]
diff --git a/recognizer/agents/playwright/async_control.py b/recognizer/agents/playwright/async_control.py
index 13d67fe..17e70f6 100644
--- a/recognizer/agents/playwright/async_control.py
+++ b/recognizer/agents/playwright/async_control.py
@@ -31,11 +31,6 @@ def __init__(self, page: Page, click_timeout: Optional[int] = None, retry_times:
self.dynamic: bool = False
self.captcha_token: Optional[str] = None
- def check_retry(self):
- self.retried += 1
- if self.retried >= self.retry_times:
- raise RecursionError(f"Exceeded maximum retry times of {self.retry_times}")
-
async def route_handler(self, route: Route, request: Request) -> None:
response = await route.fetch()
await route.fulfill(response=response) # Instant Fulfillment to save Time
@@ -69,10 +64,11 @@ async def check_captcha_visible(self):
label_obj = captcha_frame.locator("//strong")
try:
await label_obj.wait_for(state="visible", timeout=10000)
- return True
except TimeoutError:
return False
+ return await label_obj.is_visible()
+
async def click_checkbox(self) -> bool:
# Clicking Captcha Checkbox
try:
@@ -98,7 +94,9 @@ async def detect_tiles(self, prompt: str, area_captcha: bool) -> bool:
async def load_captcha(self, captcha_frame: Optional[FrameLocator] = None, reset: Optional[bool] = False) -> Union[str, bool]:
# Retrying
- self.check_retry()
+ self.retried += 1
+ if self.retried >= self.retry_times:
+ raise RecursionError(f"Exceeded maximum retry times of {self.retry_times}")
if not await self.check_captcha_visible():
if captcha_token := await self.check_result():
@@ -111,8 +109,11 @@ async def load_captcha(self, captcha_frame: Optional[FrameLocator] = None, reset
# Clicking Reload Button
if reset:
assert isinstance(captcha_frame, FrameLocator)
- reload_button = captcha_frame.locator("#recaptcha-verify-button")
- await reload_button.click()
+ try:
+ reload_button = captcha_frame.locator("#recaptcha-reload-button")
+ await reload_button.click()
+ except TimeoutError:
+ return await self.load_captcha()
# Resetting Values
self.dynamic = False
@@ -147,7 +148,7 @@ async def handle_recaptcha(self) -> Union[str, bool]:
area_captcha = len(recaptcha_tiles) == 16
result_clicked = await self.detect_tiles(prompt, area_captcha)
- if self.dynamic and not len(recaptcha_tiles) == 16:
+ if self.dynamic and not area_captcha:
while result_clicked:
await self.page.wait_for_timeout(5000)
result_clicked = await self.detect_tiles(prompt, area_captcha)
@@ -170,9 +171,13 @@ async def handle_recaptcha(self) -> Union[str, bool]:
await self.page.wait_for_timeout(1000)
+ # Check if error occurred whilst solving
+ incorrect = self.page.locator("[class='rc-imageselect-incorrect-response']")
+ errors = self.page.locator("[class *= 'rc-imageselect-error']")
+ if await incorrect.is_visible() or any([await error.is_visible() for error in await errors.all()]):
+ await self.load_captcha(captcha_frame, reset=True)
+
# Retrying
- self.check_retry()
- await self.load_captcha(captcha_frame, reset=True)
return await self.handle_recaptcha()
async def solve_recaptcha(self) -> Union[str, bool]:
diff --git a/recognizer/agents/playwright/sync_control.py b/recognizer/agents/playwright/sync_control.py
index bfd64c1..e8d1d82 100644
--- a/recognizer/agents/playwright/sync_control.py
+++ b/recognizer/agents/playwright/sync_control.py
@@ -31,11 +31,6 @@ def __init__(self, page: Page, click_timeout: Optional[int] = None, retry_times:
self.dynamic: bool = False
self.captcha_token: Optional[str] = None
- def check_retry(self):
- self.retried += 1
- if self.retried >= self.retry_times:
- raise RecursionError(f"Exceeded maximum retry times of {self.retry_times}")
-
def route_handler(self, route: Route, request: Request) -> None:
response = route.fetch()
route.fulfill(response=response) # Instant Fulfillment to save Time
@@ -69,10 +64,11 @@ def check_captcha_visible(self):
label_obj = captcha_frame.locator("//strong")
try:
label_obj.wait_for(state="visible", timeout=10000)
- return True
except TimeoutError:
return False
+ return label_obj.is_visible()
+
def click_checkbox(self) -> bool:
# Clicking Captcha Checkbox
try:
@@ -98,7 +94,9 @@ def detect_tiles(self, prompt: str, area_captcha: bool) -> bool:
def load_captcha(self, captcha_frame: Optional[FrameLocator] = None, reset: Optional[bool] = False) -> Union[str, bool]:
# Retrying
- self.check_retry()
+ self.retried += 1
+ if self.retried >= self.retry_times:
+ raise RecursionError(f"Exceeded maximum retry times of {self.retry_times}")
if not self.check_captcha_visible():
if captcha_token := self.check_result():
@@ -111,8 +109,11 @@ def load_captcha(self, captcha_frame: Optional[FrameLocator] = None, reset: Opti
# Clicking Reload Button
if reset:
assert isinstance(captcha_frame, FrameLocator)
- reload_button = captcha_frame.locator("#recaptcha-verify-button")
- reload_button.click()
+ try:
+ reload_button = captcha_frame.locator("#recaptcha-reload-button")
+ reload_button.click()
+ except TimeoutError:
+ return self.load_captcha()
# Resetting Values
self.dynamic = False
@@ -147,7 +148,7 @@ def handle_recaptcha(self) -> Union[str, bool]:
area_captcha = len(recaptcha_tiles) == 16
result_clicked = self.detect_tiles(prompt, area_captcha)
- if self.dynamic and not len(recaptcha_tiles) == 16:
+ if self.dynamic and not area_captcha:
while result_clicked:
self.page.wait_for_timeout(5000)
result_clicked = self.detect_tiles(prompt, area_captcha)
@@ -170,9 +171,13 @@ def handle_recaptcha(self) -> Union[str, bool]:
self.page.wait_for_timeout(1000)
+ # Check if error occurred whilst solving
+ incorrect = self.page.locator("[class='rc-imageselect-incorrect-response']")
+ errors = self.page.locator("[class *= 'rc-imageselect-error']")
+ if incorrect.is_visible() or any([error.is_visible() for error in errors.all()]):
+ self.load_captcha(captcha_frame, reset=True)
+
# Retrying
- self.check_retry()
- self.load_captcha(captcha_frame, reset=True)
return self.handle_recaptcha()
def solve_recaptcha(self) -> Union[str, bool]:
diff --git a/recognizer/components/detector.py b/recognizer/components/detector.py
index b772617..d21e9b9 100644
--- a/recognizer/components/detector.py
+++ b/recognizer/components/detector.py
@@ -3,10 +3,10 @@
import math
import random
import warnings
-from concurrent.futures import ThreadPoolExecutor
+from concurrent.futures import Future, ThreadPoolExecutor
from os import PathLike
from pathlib import Path
-from typing import List, Optional, Sequence, Tuple, Union
+from typing import Callable, List, Optional, Sequence, Tuple, Union
import cv2
from numpy import generic, uint8
@@ -23,12 +23,16 @@
class DetectionModels:
def __init__(self) -> None:
# Preloading: Loading Models takes ~9 seconds
- set_num_threads(2)
- self.executor = ThreadPoolExecutor(max_workers=2)
+ set_num_threads(5)
+ self.executor = ThreadPoolExecutor(max_workers=5)
+ self.loading_futures: List[Future[Callable[..., None]]] = []
try:
- self.yolo_loading_feature = self.executor.submit(self._load_yolo_detector)
- self.clip_loading_feature = self.executor.submit(self._load_clip_detector)
+ self.loading_futures.append(self.executor.submit(self._load_yolo_detector))
+ self.loading_futures.append(self.executor.submit(self._load_vit_model))
+ self.loading_futures.append(self.executor.submit(self._load_vit_processor))
+ self.loading_futures.append(self.executor.submit(self._load_seg_model))
+ self.loading_futures.append(self.executor.submit(self._load_seg_processor))
except Exception as e:
self.executor.shutdown(wait=True, cancel_futures=True)
raise e
@@ -38,20 +42,31 @@ def _load_yolo_detector(self):
self.yolo_model = YOLO("yolov8m-seg.pt")
- def _load_clip_detector(self):
- from transformers import CLIPModel, CLIPProcessor, CLIPSegForImageSegmentation, CLIPSegProcessor
+ def _load_vit_model(self):
+ from transformers import CLIPModel
self.vit_model = CLIPModel.from_pretrained("flavour/CLIP-ViT-B-16-DataComp.XL-s13B-b90K")
+
+ def _load_vit_processor(self):
+ from transformers import CLIPProcessor
+
self.vit_processor = CLIPProcessor.from_pretrained("flavour/CLIP-ViT-B-16-DataComp.XL-s13B-b90K")
+ def _load_seg_model(self):
+ from transformers import CLIPSegForImageSegmentation
+
self.seg_model = CLIPSegForImageSegmentation.from_pretrained("CIDAS/clipseg-rd64-refined")
+
+ def _load_seg_processor(self):
+ from transformers import CLIPSegProcessor
+
self.seg_processor = CLIPSegProcessor.from_pretrained("CIDAS/clipseg-rd64-refined")
def check_loaded(self):
try:
- if not self.yolo_loading_feature.done() or not self.clip_loading_feature.done():
- self.yolo_loading_feature.result()
- self.clip_loading_feature.result()
+ if not all([future.done() for future in self.loading_futures]):
+ for future in self.loading_futures:
+ future.result()
assert self.yolo_model
assert self.seg_model
diff --git a/requirements-test.txt b/requirements-test.txt
index a35876f..084b456 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -2,7 +2,7 @@
flake8==7.0.0
tox==4.12.1
pytest==7.4.4
-pytest_asyncio==0.23.4
+pytest_asyncio==0.23.5
mypy==1.8.0
types-setuptools==69.0.0.20240125
botright==0.5
diff --git a/requirements.txt b/requirements.txt
index 865b5e9..a686a8e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
-setuptools~=69.0.3
+setuptools~=69.1.0
opencv-python~=4.9.0.80
imageio~=2.33.1
ultralytics~=8.1.9
transformers~=4.37.2
-numpy~=1.26.3
-playwright~=1.41.0
\ No newline at end of file
+numpy~=1.26.4
+playwright~=1.41.2
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 32240db..64a7cc4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
[metadata]
name = recognizer
version = attr: recognizer.VERSION
-description = 🦉 Gracefully face reCAPTCHA challenge with ultralytics YOLOv8-seg and CLIPs VIT-B/32. Implemented in playwright or an easy-to-use API.
+description = 🦉Gracefully face reCAPTCHA challenge with ultralytics YOLOv8-seg, CLIPs VIT-B/16 and CLIP-Seg/RD64. Implemented in playwright or an easy-to-use API.
long_description = file: README.md
long_description_content_type = text/markdown
author = Vinyzu