A guide to getting started with scraping and API practice on this site.
tukas.dev is a companion platform with a built-in code editor and structured, exercise-based courses. The Python and JavaScript API tracks send real HTTP requests straight to this site's own endpoints — everything you practise there works against the exact API documented on this page.
🟢 Beginner
Call the public products endpoint and print each product's name and sell price. No authentication needed.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/")
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
Expected: 20 products on the first page.
Parse the classic server-rendered catalog with BeautifulSoup. Extract product ID, sell price, and category from data-* attributes on each card.
from bs4 import BeautifulSoup
import requests
BASE = "https://apilearn.tukas.dev"
soup = BeautifulSoup(
requests.get(f"{BASE}/catalog/all/").text,
"html.parser")
for card in soup.select("[data-product-id][data-category]"):
print(card["data-product-id"],
card["data-category"],
card["data-price"])
Walk every page of the API using the next field in each response and collect all product names. How many are there in total?
import requests
url = "https://apilearn.tukas.dev/api/products/"
names = []
while url:
page = requests.get(url).json()
names += [p["name"] for p in page["results"]]
url = page["next"]
print(len(names), "total products")
🟡 Intermediate
Find discounted items in the living-room category under €800 sorted cheapest first. Then search by keyword. Available params: category, min_price, max_price, on_sale, ordering, search.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/",
params={"category": "living-room",
"max_price": 800,
"on_sale": True,
"ordering": "price"})
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
# Full-text search
r2 = requests.get(f"{BASE}/api/products/",
params={"search": "oak"})
print(r2.json()["count"], "results for 'oak'")
Register a user, get a Token, add a product to the cart, and verify it is there. The registration endpoint returns a token immediately — no extra auth call needed.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.post(f"{BASE}/api/users/register/", json={
"username": "alice",
"email": "alice@example.com",
"password": "Str0ngPass!"})
token = r.json()["token"]
headers = {"Authorization": f"Token {token}"}
# Add a product (replace slug with a real one)
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": "corner-sofa",
"quantity": 2})
# Verify the cart
print(requests.get(f"{BASE}/api/cart/",
headers=headers).json())
The AJAX catalog modes fetch from dedicated sub-endpoints you can call without a browser. /partial/ returns an HTML fragment; /json/ returns JSON — same filters apply to both.
import requests
from bs4 import BeautifulSoup
BASE = "https://apilearn.tukas.dev"
# AJAX HTML — returns a rendered HTML fragment
html = requests.get(
f"{BASE}/catalog/all/partial/",
params={"page": 1}).text
cards = BeautifulSoup(html, "html.parser").select(
"[data-product-id][data-category]")
print(len(cards), "product cards in fragment")
# AJAX JSON — returns structured JSON
data = requests.get(
f"{BASE}/catalog/all/json/",
params={"page": 1}).json()
print(data["total_count"], "total products")
Add several products to the cart, place an order, then verify it appears in your order history. The cart is cleared automatically after a successful order.
import requests
BASE = "https://apilearn.tukas.dev"
# assumes token from exercise 5
headers = {"Authorization": "Token YOUR_TOKEN"}
for slug in ["corner-sofa", "classic-sofa"]:
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": slug, "quantity": 1})
order = requests.post(f"{BASE}/api/orders/",
headers=headers, json={
"phone_number": "+1-555-0100",
"requires_delivery": False,
"payment_on_get": True}).json()
print("Order #" + str(order["id"]), "| Status:", order["status"])
history = requests.get(f"{BASE}/api/orders/",
headers=headers).json()
print(history["count"], "orders in history")
🔴 Advanced
The Infinite Scroll mode loads more products as you scroll down. Automate the scroll loop until no new cards appear, then count all loaded products.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch()
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/catalog/all/infinite/")
prev = 0
while True:
page.evaluate(
"window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(800)
cards = page.query_selector_all(
"[data-product-id]")
if len(cards) == prev:
break
prev = len(cards)
print(prev, "products loaded total")
br.close()
Automate the full user journey in a real browser: open the site, log in, pick a product, add it to the cart, and place an order — without touching the API directly.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch(headless=False)
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/user/login/")
page.fill("[name=username]", "alice")
page.fill("[name=password]", "Str0ngPass!")
page.click("[type=submit]")
page.goto(
"https://apilearn.tukas.dev/catalog/all/")
# click "Add to cart" on the first product card
page.click(".add-to-cart")
# navigate to cart and proceed to checkout
# (explore the site navigation to find the route)
br.close()
Understanding what the server can see — and what good automation should do about it.
This site runs a bot detection layer on every HTML response. It is not there to block you or to teach you how to circumvent protections. Its purpose is the opposite: to show what signals naive automation sends, and to give you immediate, readable feedback so you can learn to write scripts that behave predictably and respectfully.
The goal is not to defeat anti-bot systems. It is to build automation that verifies its own assumptions, handles edge cases gracefully, and interacts with web services in a way that would be acceptable on any site that allow it — not just a practice target.
Every HTML response (excluding /api/, /static/, and /media/ paths) includes these headers:
| Header | Values / meaning |
|---|---|
X-Scraping-Confidence | clean · low · medium · high |
X-Scraping-Signals | comma-separated list of what triggered detection, or empty if nothing fired |
X-Scraping-Hint | human-readable explanation of the active trap, or none |
| Header | When present |
|---|---|
Retry-After | on 429 responses — seconds to wait before retrying |
X-Scraping-Block-Reason | on 429 responses — confidence level and signals that caused the block |
A 429 response does not mean permanent rejection. Wait the number of seconds in Retry-After and the counter resets. Honouring Retry-After is the correct behaviour — on this site and on any real one.
Check what signals your script sends and read the educational hint:
import requests
r = requests.get(
"https://apilearn.tukas.dev/catalog/all/")
print(r.headers["X-Scraping-Confidence"])
print(r.headers["X-Scraping-Signals"])
print(r.headers["X-Scraping-Hint"])
# Typical output for python-requests defaults:
# high
# ua:python-requests
# The catalog HTML you received was customised
# for you — prices may be swapped...
Handle rate limiting correctly — check for 429 and respect Retry-After:
import requests, time
def get_with_backoff(url, **kwargs):
while True:
r = requests.get(url, **kwargs)
if r.status_code == 429:
wait = int(r.headers.get(
"Retry-After", 60))
print(f"Rate limited. "
f"Waiting {wait}s...")
time.sleep(wait)
continue
r.raise_for_status()
return r
When bot signals are detected, a comment is injected just before </body>.
It is invisible in the rendered page but present in the source — and in your scraped HTML.
If this shows up in your data, your scraper is not filtering by rendered visibility.
<!-- 🤖 Hello there. We've been expecting you.
confidence : high
signals : ua:python-requests
This comment only appears when bot signals are detected.
If you're a human reading source code — respect. -->
The traps here — hidden elements, manipulated prices, honeypot fields, pagination loops — exist on real websites too. You will encounter them. This site makes them visible and explains them, so you can write code that does not silently collect wrong data.
When you apply what you learn here to real-world projects: respect robots.txt, honour rate limits, prefer the API when one exists, and take responsibility for how your tools interact with services you do not own. Always act solely within the policy of use of a particular web resource.
Гайд для тих, хто хоче практикувати скрейпінг і роботу з API на цьому сайті.
tukas.dev — платформа-компаньйон із вбудованим редактором коду та структурованими курсами на основі вправ. Курси з Python та JavaScript API надсилають реальні HTTP-запити прямо на ендпоінти цього сайту — все, що ви практикуєте там, працює з тим самим API, що описаний на цій сторінці.
🟢 Початковий
Зробіть запит до публічного ендпоінту товарів і виведіть назву та ціну кожного. Авторизація не потрібна.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/")
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
Очікується: 20 товарів на першій сторінці.
Парсіть серверно-рендерений каталог за допомогою BeautifulSoup. Витягніть ID товару, ціну та категорію з атрибутів data-*.
from bs4 import BeautifulSoup
import requests
BASE = "https://apilearn.tukas.dev"
soup = BeautifulSoup(
requests.get(f"{BASE}/catalog/all/").text,
"html.parser")
for card in soup.select("[data-product-id][data-category]"):
print(card["data-product-id"],
card["data-category"],
card["data-price"])
Обходьте кожну сторінку API за полем next і збирайте всі назви товарів у список. Скільки їх всього?
import requests
url = "https://apilearn.tukas.dev/api/products/"
names = []
while url:
page = requests.get(url).json()
names += [p["name"] for p in page["results"]]
url = page["next"]
print(len(names), "total products")
🟡 Середній
Знайдіть товари зі знижкою у категорії «living-room» до €800, відсортовані за зростанням ціни. Доступні параметри: category, min_price, max_price, on_sale, ordering, search.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/",
params={"category": "living-room",
"max_price": 800,
"on_sale": True,
"ordering": "price"})
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
# Повнотекстовий пошук
r2 = requests.get(f"{BASE}/api/products/",
params={"search": "oak"})
print(r2.json()["count"], "результатів для 'oak'")
Зареєструйте користувача, отримайте токен, додайте товар до кошика та перевірте що він є. Ендпоінт реєстрації одразу повертає токен — окремий виклик не потрібен.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.post(f"{BASE}/api/users/register/", json={
"username": "alice",
"email": "alice@example.com",
"password": "Str0ngPass!"})
token = r.json()["token"]
headers = {"Authorization": f"Token {token}"}
# Додати товар (замінити slug на реальний)
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": "corner-sofa",
"quantity": 2})
# Перевірити кошик
print(requests.get(f"{BASE}/api/cart/",
headers=headers).json())
AJAX-режими каталогу отримують дані з окремих ендпоінтів, які можна викликати без браузера. /partial/ повертає HTML-фрагмент, /json/ — JSON. Фільтри працюють в обох.
import requests
from bs4 import BeautifulSoup
BASE = "https://apilearn.tukas.dev"
# AJAX HTML — повертає HTML-фрагмент
html = requests.get(
f"{BASE}/catalog/all/partial/",
params={"page": 1}).text
cards = BeautifulSoup(html, "html.parser").select(
"[data-product-id][data-category]")
print(len(cards), "карток у фрагменті")
# AJAX JSON — повертає структурований JSON
data = requests.get(
f"{BASE}/catalog/all/json/",
params={"page": 1}).json()
print(data["total_count"], "товарів всього")
Додайте кілька товарів до кошика, оформіть замовлення, потім перевірте що воно з'явилось в історії. Кошик очищається автоматично після успішного замовлення.
import requests
BASE = "https://apilearn.tukas.dev"
# токен з вправи 5
headers = {"Authorization": "Token YOUR_TOKEN"}
for slug in ["corner-sofa", "classic-sofa"]:
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": slug, "quantity": 1})
order = requests.post(f"{BASE}/api/orders/",
headers=headers, json={
"phone_number": "+380-00-000-0000",
"requires_delivery": False,
"payment_on_get": True}).json()
print("Замовлення #" + str(order["id"]), "| Статус:", order["status"])
history = requests.get(f"{BASE}/api/orders/",
headers=headers).json()
print(history["count"], "замовлень в історії")
🔴 Просунутий
Режим Infinite Scroll підвантажує товари при прокрутці. Автоматизуйте цикл прокрутки поки нові картки не перестануть з'являтися, потім порахуйте всі завантажені товари.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch()
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/catalog/all/infinite/")
prev = 0
while True:
page.evaluate(
"window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(800)
cards = page.query_selector_all(
"[data-product-id]")
if len(cards) == prev:
break
prev = len(cards)
print(prev, "товарів завантажено")
br.close()
Автоматизуйте повний шлях користувача в реальному браузері: відкрийте сайт, увійдіть, виберіть товар, додайте до кошика та оформіть замовлення — без прямого використання API.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch(headless=False)
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/user/login/")
page.fill("[name=username]", "alice")
page.fill("[name=password]", "Str0ngPass!")
page.click("[type=submit]")
page.goto(
"https://apilearn.tukas.dev/catalog/all/")
# натиснути "Додати до кошика" у першій картці
page.click(".add-to-cart")
# перейти до кошика і оформити замовлення
# (дослідіть навігацію сайту самостійно)
br.close()
Розуміння того, що бачить сервер — і що правильна автоматизація має з цим робити.
На цьому сайті кожна HTML-відповідь проходить через шар визначення ботів. Він існує не для того, щоб блокувати вас або вчити обходити захист. Його мета протилежна: показати, які сигнали надсилає наївна автоматизація, і дати вам зрозумілий зворотній зв'язок, щоб ви навчилися писати скрипти, які поводяться передбачувано й коректно.
Ціль — не перемогти антибот-системи. Ціль — писати автоматизацію, яка перевіряє власні припущення, граційно обробляє крайні випадки та взаємодіє з веб-сервісами так, щоб це було прийнятно на будь-якому сайті який це дозволяє — а не лише на навчальному.
Кожна HTML-відповідь (окрім шляхів /api/, /static/ та /media/) містить ці заголовки:
| Заголовок | Значення / опис |
|---|---|
X-Scraping-Confidence | clean · low · medium · high |
X-Scraping-Signals | список через кому того, що спрацювало, або empty якщо нічого |
X-Scraping-Hint | пояснення активної пастки зрозумілою мовою, або none |
| Заголовок | Коли присутній |
|---|---|
Retry-After | у відповідях 429 — секунди очікування перед повторним запитом |
X-Scraping-Block-Reason | у відповідях 429 — рівень впевненості та сигнали, що спричинили блокування |
Відповідь 429 не означає остаточну відмову. Зачекайте кількість секунд із Retry-After і лічильник скинеться. Дотримання Retry-After — правильна поведінка — на цьому сайті та на будь-якому реальному.
Перевірте, які сигнали надсилає ваш скрипт, та прочитайте навчальну підказку:
import requests
r = requests.get(
"https://apilearn.tukas.dev/catalog/all/")
print(r.headers["X-Scraping-Confidence"])
print(r.headers["X-Scraping-Signals"])
print(r.headers["X-Scraping-Hint"])
# Типовий вивід для python-requests за замовчуванням:
# high
# ua:python-requests
# The catalog HTML you received was customised
# for you — prices may be swapped...
Правильно обробляйте обмеження частоти запитів — перевіряйте 429 та дотримуйтесь Retry-After:
import requests, time
def get_with_backoff(url, **kwargs):
while True:
r = requests.get(url, **kwargs)
if r.status_code == 429:
wait = int(r.headers.get(
"Retry-After", 60))
print(f"Rate limited. "
f"Waiting {wait}s...")
time.sleep(wait)
continue
r.raise_for_status()
return r
Коли виявляються ознаки бота, перед </body> вставляється коментар.
Він невидимий на відрендереній сторінці, але присутній у вихідному коді — і у вашому зібраному HTML.
Якщо він з'являється у ваших даних, ваш скрейпер не фільтрує за видимістю елементів.
<!-- 🤖 Hello there. We've been expecting you.
confidence : high
signals : ua:python-requests
This comment only appears when bot signals are detected.
If you're a human reading source code — respect. -->
Пастки тут — приховані елементи, маніпульовані ціни, поля-приманки, петлі пагінації — існують і на реальних сайтах. Ви з ними зіткнетесь. Цей сайт робить їх видимими та пояснює, щоб ви могли писати код, який не збирає мовчки неправильні дані.
Коли ви застосовуєте те, що дізналися тут, до реальних проєктів: поважайте robots.txt, дотримуйтесь обмежень частоти запитів, надавайте перевагу API коли він існує, та несіть відповідальність за те, як ваші інструменти взаємодіють з сервісами, якими ви не володієте. Завжди дійте виключно в рамках політики використання конкретного веб-ресурсу.
Гайд для тех, кто хочет практиковать скрейпинг и работу с API на этом сайте.
tukas.dev — платформа-компаньон со встроенным редактором кода и структурированными курсами на основе упражнений. Курсы по Python и JavaScript API отправляют настоящие HTTP-запросы прямо на эндпоинты этого сайта — всё, что вы практикуете там, работает с тем же самым API, что описан на этой странице.
🟢 Начальный
Вызовите публичный эндпоинт товаров и выведите название и цену каждого. Авторизация не нужна.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/")
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
Ожидается: 20 товаров на первой странице.
Парсите серверно-рендеренный каталог с BeautifulSoup. Извлеките ID товара, цену и категорию из атрибутов data-*.
from bs4 import BeautifulSoup
import requests
BASE = "https://apilearn.tukas.dev"
soup = BeautifulSoup(
requests.get(f"{BASE}/catalog/all/").text,
"html.parser")
for card in soup.select("[data-product-id][data-category]"):
print(card["data-product-id"],
card["data-category"],
card["data-price"])
Обходите каждую страницу API по полю next и собирайте все названия товаров в список. Сколько их всего?
import requests
url = "https://apilearn.tukas.dev/api/products/"
names = []
while url:
page = requests.get(url).json()
names += [p["name"] for p in page["results"]]
url = page["next"]
print(len(names), "total products")
🟡 Средний
Найдите товары со скидкой в категории «living-room» до €800, отсортированные по возрастанию цены. Доступные параметры: category, min_price, max_price, on_sale, ordering, search.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.get(f"{BASE}/api/products/",
params={"category": "living-room",
"max_price": 800,
"on_sale": True,
"ordering": "price"})
for p in r.json()["results"]:
print(p["name"], p["sell_price"])
# Полнотекстовый поиск
r2 = requests.get(f"{BASE}/api/products/",
params={"search": "oak"})
print(r2.json()["count"], "результатов для 'oak'")
Зарегистрируйте пользователя, получите токен, добавьте товар в корзину и убедитесь что он там есть. Эндпоинт регистрации сразу возвращает токен — отдельный вызов не нужен.
import requests
BASE = "https://apilearn.tukas.dev"
r = requests.post(f"{BASE}/api/users/register/", json={
"username": "alice",
"email": "alice@example.com",
"password": "Str0ngPass!"})
token = r.json()["token"]
headers = {"Authorization": f"Token {token}"}
# Добавить товар (заменить slug на реальный)
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": "corner-sofa",
"quantity": 2})
# Проверить корзину
print(requests.get(f"{BASE}/api/cart/",
headers=headers).json())
AJAX-режимы каталога получают данные из отдельных эндпоинтов, которые можно вызывать без браузера. /partial/ возвращает HTML-фрагмент, /json/ — JSON. Фильтры работают в обоих.
import requests
from bs4 import BeautifulSoup
BASE = "https://apilearn.tukas.dev"
# AJAX HTML — возвращает HTML-фрагмент
html = requests.get(
f"{BASE}/catalog/all/partial/",
params={"page": 1}).text
cards = BeautifulSoup(html, "html.parser").select(
"[data-product-id][data-category]")
print(len(cards), "карточек во фрагменте")
# AJAX JSON — возвращает структурированный JSON
data = requests.get(
f"{BASE}/catalog/all/json/",
params={"page": 1}).json()
print(data["total_count"], "товаров всего")
Добавьте несколько товаров в корзину, оформите заказ, затем проверьте что он появился в истории. Корзина очищается автоматически после успешного заказа.
import requests
BASE = "https://apilearn.tukas.dev"
# токен из упражнения 5
headers = {"Authorization": "Token YOUR_TOKEN"}
for slug in ["corner-sofa", "classic-sofa"]:
requests.post(f"{BASE}/api/cart/items/",
headers=headers,
json={"product_slug": slug, "quantity": 1})
order = requests.post(f"{BASE}/api/orders/",
headers=headers, json={
"phone_number": "+7-000-000-0000",
"requires_delivery": False,
"payment_on_get": True}).json()
print("Заказ #" + str(order["id"]), "| Статус:", order["status"])
history = requests.get(f"{BASE}/api/orders/",
headers=headers).json()
print(history["count"], "заказов в истории")
🔴 Продвинутый
Режим Infinite Scroll подгружает товары при прокрутке. Автоматизируйте цикл прокрутки пока новые карточки не перестанут появляться, затем посчитайте все загруженные товары.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch()
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/catalog/all/infinite/")
prev = 0
while True:
page.evaluate(
"window.scrollTo(0, document.body.scrollHeight)")
page.wait_for_timeout(800)
cards = page.query_selector_all(
"[data-product-id]")
if len(cards) == prev:
break
prev = len(cards)
print(prev, "товаров загружено")
br.close()
Автоматизируйте полный путь пользователя в реальном браузере: откройте сайт, войдите в систему, выберите товар, добавьте в корзину и оформите заказ — без прямого обращения к API.
from playwright.sync_api import sync_playwright
with sync_playwright() as pw:
br = pw.chromium.launch(headless=False)
page = br.new_page()
page.goto(
"https://apilearn.tukas.dev/user/login/")
page.fill("[name=username]", "alice")
page.fill("[name=password]", "Str0ngPass!")
page.click("[type=submit]")
page.goto(
"https://apilearn.tukas.dev/catalog/all/")
# нажать "Добавить в корзину" у первой карточки
page.click(".add-to-cart")
# перейти в корзину и оформить заказ
# (исследуйте навигацию сайта самостоятельно)
br.close()
Понимание того, что видит сервер — и что правильная автоматизация должна с этим делать.
На этом сайте каждый HTML-ответ проходит через слой определения ботов. Он существует не для того, чтобы блокировать вас или учить обходить защиту. Его цель противоположна: показать, какие сигналы отправляет наивная автоматизация, и дать вам понятную обратную связь, чтобы вы научились писать скрипты, которые ведут себя предсказуемо и корректно.
Цель — не победить антибот-системы. Цель — писать автоматизацию, которая проверяет собственные допущения, корректно обрабатывает граничные случаи и взаимодействует с веб-сервисами так, чтобы это было приемлемо на любом сайте который это разрешает — а не только на учебном.
Каждый HTML-ответ (кроме путей /api/, /static/ и /media/) содержит эти заголовки:
| Заголовок | Значения / описание |
|---|---|
X-Scraping-Confidence | clean · low · medium · high |
X-Scraping-Signals | список через запятую того, что сработало, или empty если ничего |
X-Scraping-Hint | объяснение активной ловушки понятным языком, или none |
| Заголовок | Когда присутствует |
|---|---|
Retry-After | в ответах 429 — секунды ожидания перед повторным запросом |
X-Scraping-Block-Reason | в ответах 429 — уровень уверенности и сигналы, вызвавшие блокировку |
Ответ 429 не означает окончательный отказ. Подождите количество секунд из Retry-After и счётчик сбросится. Соблюдение Retry-After — правильное поведение — на этом сайте и на любом реальном.
Проверьте, какие сигналы отправляет ваш скрипт, и прочитайте обучающую подсказку:
import requests
r = requests.get(
"https://apilearn.tukas.dev/catalog/all/")
print(r.headers["X-Scraping-Confidence"])
print(r.headers["X-Scraping-Signals"])
print(r.headers["X-Scraping-Hint"])
# Типичный вывод для python-requests по умолчанию:
# high
# ua:python-requests
# The catalog HTML you received was customised
# for you — prices may be swapped...
Правильно обрабатывайте ограничения частоты запросов — проверяйте 429 и соблюдайте Retry-After:
import requests, time
def get_with_backoff(url, **kwargs):
while True:
r = requests.get(url, **kwargs)
if r.status_code == 429:
wait = int(r.headers.get(
"Retry-After", 60))
print(f"Rate limited. "
f"Waiting {wait}s...")
time.sleep(wait)
continue
r.raise_for_status()
return r
Когда обнаруживаются признаки бота, перед </body> вставляется комментарий.
Он невидим на отрендеренной странице, но присутствует в исходном коде — и в вашем собранном HTML.
Если он появляется в ваших данных, ваш скрейпер не фильтрует по видимости элементов.
<!-- 🤖 Hello there. We've been expecting you.
confidence : high
signals : ua:python-requests
This comment only appears when bot signals are detected.
If you're a human reading source code — respect. -->
Ловушки здесь — скрытые элементы, манипулированные цены, поля-приманки, петли пагинации — существуют и на реальных сайтах. Вы с ними столкнётесь. Этот сайт делает их видимыми и объясняет, чтобы вы могли писать код, который не собирает молча неправильные данные.
Когда вы применяете то, что узнали здесь, к реальным проектам: уважайте robots.txt, соблюдайте ограничения частоты запросов, отдавайте предпочтение API когда он есть, и несите ответственность за то, как ваши инструменты взаимодействуют с сервисами, которыми вы не владеете. Всегда действуйте исключительно в рамках политики использования конкретного веб-ресурса.