Developer Snippet Diary

Chrome Automation with Cloudflare Turnstile Protection

"""
Chrome Automation with Cloudflare Turnstile Protection

SETUP:
pip install patchright==1.56.0 psutil
"""

import asyncio
import os
import sys
from pathlib import Path
from patchright.async_api import async_playwright


class ChromeAutomation:
    def __init__(self):
        print("Chrome Automation started. Please wait...")
        self.playwright = None  # Patchright engine
        self.browser = None     # Browser session/profile
        self.page = None        # Active tab

    async def open_profile(self, profile_no=1, max_retries=3, use_cwd=False):
        """
        Open Chrome with specified profile.
        
        Args:
            profile_no: Profile number (1 = "Profile 1", 0 = "Default")
            max_retries: Retry attempts (not used yet)
            use_cwd: If True, use current directory; if False, use Chrome original profiles
        """
        if self.playwright is None:
            self.playwright = await async_playwright().start()
        
        # Determine profile directory name
        if profile_no == 0:
            profile_dir_name = "Default"
        else:
            profile_dir_name = f"Profile {profile_no}"
        
        # Determine user data directory
        if use_cwd:
            # Use current working directory
            user_data_dir = str(Path.cwd() / "ChromeProfile")
            Path(user_data_dir).mkdir(exist_ok=True)
            print(f"???? Using CWD profile: {user_data_dir}")
        else:
            # Use Chrome original profiles
            username = os.getenv('USERNAME') or os.getenv('USER') or 'User'
            
            if sys.platform == 'win32':
                user_data_dir = f"C:\\Users\\{username}\\AppData\\Local\\Google\\Chrome\\User Data"
            elif sys.platform == 'darwin':
                user_data_dir = str(Path.home() / "Library" / "Application Support" / "Google" / "Chrome")
            else:  # Linux
                user_data_dir = str(Path.home() / ".config" / "google-chrome")
            
            if not Path(user_data_dir).exists():
                print(f"?????? Original Chrome profile not found at {user_data_dir}")
                print("???? Try using use_cwd=True to create profile in current directory")
                raise FileNotFoundError(f"Chrome profile not found: {user_data_dir}")
            
            print(f"???? Using Chrome original profile: {user_data_dir}")
        
        self.browser = await self.playwright.chromium.launch_persistent_context(
            user_data_dir=user_data_dir,
            channel="chrome",
            headless=False,
            no_viewport=True,
            args=[f"--profile-directory={profile_dir_name}"]
        )
        
        # Use existing page if available
        if len(self.browser.pages) > 0:
            self.page = self.browser.pages[0]
        else:
            self.page = await self.browser.new_page()
        
        print(f"??? Chrome session ready with {profile_dir_name}")

    async def solve_turnstile_challenge(self):
        """
        Detect and solve Cloudflare Turnstile challenge.
        
        Returns:
            bool: True if solved or not present, False if failed
        """
        try:
            print("Checking for human verification challenge...")
            
            # Step 1: Quickly check if a Cloudflare/Turnstile iframe is injected into the DOM
            # We wait up to 3.5 seconds for the iframe itself. If it's not and won't be there, we skip.
            challenge_iframe = self.page.locator(
                "iframe[src*='cloudflare'], iframe[src*='turnstile'], "
                "iframe[title*='challenge'], iframe[title*='Cloudflare']"
            ).first
            
            is_challenge_present = False
            try:
                await challenge_iframe.wait_for(state="attached", timeout=3500)
                is_challenge_present = True
            except Exception:
                pass
                
            if is_challenge_present:
                print("Turnstile challenge detected! Waiting for checkbox to become visible...")
                # The iframe is present, now wait up to 15 seconds for the checkbox inside to be visible
                checkbox = self.page.frame_locator("iframe").locator(
                    ".cb-c input[type='checkbox'], label.cb-lb, .cb-i"
                ).first
                await checkbox.wait_for(state="visible", timeout=15000)
                
                print("Checkbox found! Moving mouse carefully...")
                await checkbox.hover()
                await asyncio.sleep(0.8)  # Human-like pause
                await checkbox.click(delay=150)  # Human-like click delay
                print("Checkbox clicked successfully!")
                
                # Wait a little bit for the challenge to resolve
                await asyncio.sleep(5)
                return True
            else:
                print("No Turnstile challenge detected on page. Proceeding immediately.")
                return True
                
        except Exception as e:
            print(f"Skipping verification check or failed: {e}")
            return False

    async def close(self):
        """Close browser."""
        if self.browser:
            await self.browser.close()
        if self.playwright:
            await self.playwright.stop()


async def main():
    automation = ChromeAutomation()
    
    # Example 1: Use CWD with Profile 1
    await automation.open_profile(profile_no=1, use_cwd=True)
    
    # Example 2: Use Chrome original profile (uncomment to use)
    # await automation.open_profile(profile_no=1, use_cwd=False)
    
    await automation.page.goto('https://www.ip2location.com/')
    
    await automation.solve_turnstile_challenge()
    
    await automation.page.screenshot(path=f'example-{automation.playwright.chromium.name}.png')
    await automation.close()


if __name__ == "__main__":
    asyncio.run(main())
Posted by: R GONDAL
Email: rizikmw@gmail.com