Skip to content

Commit

Permalink
Update v1.3
Browse files Browse the repository at this point in the history
- Fixed Timeout Retrying
- Faster Detector Model Loading
- Improved ReadMe
- Updated Github Workflow to PreInstall Huggingface Models
- Updated Requirements
  • Loading branch information
Vinyzu committed Feb 12, 2024
1 parent 7949c5f commit b33490f
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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. <br> Usable with an easy-to-use API, also available for Async and Sync Playwright. <br> 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.

Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion recognizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

from .components.detector import Detector

VERSION = 1.2
VERSION = 1.3

__all__ = ["Detector", "VERSION"]
29 changes: 17 additions & 12 deletions recognizer/agents/playwright/async_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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():
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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]:
Expand Down
29 changes: 17 additions & 12 deletions recognizer/agents/playwright/sync_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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():
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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]:
Expand Down
37 changes: 26 additions & 11 deletions recognizer/components/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
numpy~=1.26.4
playwright~=1.41.2
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit b33490f

Please sign in to comment.