Skip to content

Documentation

Antibot page: emit a verification result (PHP)

Read and check the ScrapingCourse control page, then save a clear boolean result. The pattern can serve as a separate content-checking step.

Choose PHP, paste the complete source from this article and set the run parameters below. The source for this collection is ScrapingCourse. This walkthrough applies to the specified demonstration page.

Example settings

{
  "path": "/antibot-challenge",
  "mode": "challenge",
  "expected": 1
}

path selects the page, mode selects an algorithm branch, and expected sets the control count for this example. It is not a plan limit. When the source dataset changes, check the expected count against it.

How the algorithm works

  1. Set /antibot-challenge and the challenge mode. The shared source stays the same; the selected page parameters change.
  2. Open the page with the browser SDK and the auto_captcha setting.
  3. Read the body text and normalize whitespace so HTML line breaks do not affect the comparison.
  4. Require the demonstration site control phrase. An HTTP status alone does not replace this content check.
  5. On a match, emit one record: name contains the path, passed is true, and the path is also the record key.
  6. Close the session in finally. This branch does not invoke the product collector or compare card counts.

Details that matter

A control condition should reflect the page being checked. This example expression matches ScrapingCourse content; it is not a universal indication of access to any website.

PHP details

PHP calls network and browser methods sequentially with ->. courseCollect receives $seen and returns its updated array, so assign each result back. Close the page in finally; $ctx->fail() stops the run with a readable message.

Expected output

This branch emits one record with name and passed. name is /antibot-challenge; passed is true only after matching the control text. The final seen count check is skipped for challenge mode.

Complete scenario source

The source is shared by this collection. This example runs its challenge branch using the parameters above. Copy the complete block into the PHP editor; no fixed sign-in credentials or service keys need to be added.

<?php

function run(\Sdk\Context $ctx): void
{
    $origin = 'https://www.scrapingcourse.com';
    $path = (string) $ctx->param('path', '/pagination');
    $mode = (string) $ctx->param('mode', 'http');
    $auto = $ctx->param('auto_captcha', true) !== false;
    $seen = [];
    if ($mode === 'http') {
        $url = $origin.$path;
        $visited = [];
        while ($url !== null && count($visited) < 50) {
            if (isset($visited[$url])) {
                $ctx->fail('Pagination returned an already processed page');
            }
            $visited[$url] = true;
            $response = $ctx->http()->withoutCache()->get($url, $auto);
            if (! $response->ok()) {
                $ctx->fail('The source could not be opened: '.$response->status);
            }
            $html = $response->body;
            $seen = courseCollect($ctx, $html, $seen);
            $next = $ctx->dom($html)->attr('a[rel="next"], a.next.page-numbers', 'href');
            $url = $next ? $ctx->url()->absolutize($url, $next) : null;
        }
        if ($url !== null) {
            $ctx->fail('The pagination safety limit was reached');
        }
    } else {
        $page = $ctx->browser()->open($origin.$path, ['*/ajax/products*'], [], $auto);
        try {
            if ($mode === 'login') {
                $dom = $ctx->dom($page->content());
                $hint = '';
                foreach ($dom->qa('p') as $p) {
                    if (str_contains($p->text(), 'Demo Credentials:')) {
                        $hint = $p->text();
                    }
                }
                preg_match('/Password:\s*([^\s|]+)/i', $hint, $password);
                preg_match('/Email:\s*([^\s|]+@[^\s|]+)/i', $hint, $emailMatch);
                $email = $emailMatch[1] ?? '';
                if ($email === '' || ! isset($password[1])) {
                    $ctx->fail('The page does not contain demo credentials');
                }
                $page->type('#email', $email)->type('#password', $password[1])->click('button[type="submit"]');
            }
            if ($mode === 'challenge') {
                $text = preg_replace('/\s+/', ' ', $ctx->dom($page->content())->text('body') ?? '');
                if (! preg_match('/You (?:successfully )?bypassed|successfully bypassed|Congratulations/i', $text)) {
                    $ctx->fail('The expected page content was not received');
                }
                $ctx->emit(['name' => $path, 'passed' => true], $path);
            } else {
                $page->waitFor('.product-name:not(:empty)', 15000);
                if ($mode === 'button') {
                    $complete = false;
                    for ($i = 0; $i < 30; $i++) {
                        $html = $page->content();
                        $before = $ctx->dom($html);
                        $seen = courseCollect($ctx, $html, $seen);
                        $button = $before->q('#load-more-btn');
                        if ($button === null || preg_match('/display:\s*none/', $button->attr('style') ?? '')) {
                            $complete = true;
                            break;
                        }
                        $offset = $button->attr('data-offset');
                        $count = count($before->qa('.product-item'));
                        $page->click('#load-more-btn');
                        $response = $page->waitForResponse('*offset='.$offset, 15000);
                        if ($response === null || ($response['status'] < 200 || $response['status'] >= 300) || $response['truncated'] || $response['error']) {
                            $ctx->fail('The next batch could not be loaded');
                        }
                        if (trim($response['body'] ?? '') === '') {
                            $complete = true;
                            break;
                        }
                        $page->waitFor('.product-item:nth-child('.($count + 1).')');
                    }
                    if (! $complete) {
                        $ctx->fail('The batch loading safety limit was reached');
                    }
                }
                if ($mode === 'scroll') {
                    $stable = 0;
                    $previous = 0;
                    for ($i = 0; $i < 40 && $stable < 3; $i++) {
                        $page->scroll(3, 400);
                        $html = $page->content();
                        $seen = courseCollect($ctx, $html, $seen);
                        $count = count($ctx->dom($html)->qa('.product-item'));
                        $stable = $count === $previous ? $stable + 1 : 0;
                        $previous = $count;
                    }
                    if ($stable < 3) {
                        $ctx->fail('The feed is still loading: increase the scroll limit');
                    }
                }
                $seen = courseCollect($ctx, $page->content(), $seen);
            }
        } finally {
            $page->close();
        }
    }
    $expected = (int) $ctx->param('expected', 1);
    if ($mode !== 'challenge' && count($seen) !== $expected) {
        $ctx->fail('Incomplete result: '.count($seen).' instead of '.$expected);
    }
}

function courseCollect(\Sdk\Context $ctx, string $html, array $seen): array
{
    $dom = $ctx->dom($html);
    foreach ($dom->qa('.product-item, li.product') as $card) {
        $name = trim($card->q('.product-name, .woocommerce-loop-product__title')?->text() ?? '');
        $key = $card->q('a')?->attr('href') ?? $name;
        if ($name === '' || isset($seen[$key])) {
            continue;
        }
        $seen[$key] = true;
        $ctx->emit(['name' => $name, 'price' => $ctx->text()->money($card->q('.product-price, .price')?->text() ?? ''),
            'image' => $card->q('img')?->attr('src') ?? ''], $key);
    }

    return $seen;
}

Related reading

Shared algorithm and parameters · HTTP requests · Browser actions · Find HTML elements