Повний гайд із crawling списків

Витягувати дані з сайтів, де інформація подана у вигляді списків (каталоги товарів, вакансії, результати пошуку), непросто. Сайти розбивають елементи на кілька сторінок, ховають їх за кнопками «Завантажити ще» або використовують нескінченне прокручування. Без правильного підходу ваш набір даних буде неповним.

Цей посібник охоплює основи краулінгу списків: як надійно витягувати структуровані дані за допомогою Python та інструментів автоматизації.

Що таке обхід списків?

Краулінг списків витягує структуровані дані з вебсторінок, де інформація подається у повторюваних форматах. На відміну від загального вебскрапінгу, який орієнтований на окремі сторінки, краулінг списків зосереджується на збиранні кількох схожих елементів (карток товарів, списків вакансій, записів компаній) з однієї або кількох сторінок.

Чому краулінг списків важливий

Сучасні вебсайти організовують дані у передбачувані шаблони. Кожна картка товару, вакансія або запис у каталозі має однакову HTML-структуру. Це робить їх ідеальними для автоматизованого вилучення. Виклик полягає в тому, щоб отримати доступ до всіх елементів, коли вони розподілені між пагінацією, нескінченним прокручуванням або динамічним завантаженням.

Ключові компоненти краулінгу списків

Identification: Знайдіть HTML-контейнер, у якому міститься кожен повторюваний елемент (картка товару, вакансія). Це визначає, які дані ви можете зібрати.

Extraction: Зіставляйте конкретні поля в кожному контейнері (назва, ціна, місцезнаходження) зі своєю схемою даних. Послідовне зіставлення полів забезпечує чистий результат.

Traversal: Переходьте між усіма елементами за допомогою пагінації, нескінченного прокручування або кнопок «Завантажити ще». Це забезпечує повне збирання даних.

Real-World Applications

E-Commerce Price Monitoring: Відстежуйте зміни цін, наявність товарів на складі та пропозиції конкурентів, витягуючи назви товарів, ціни, рейтинги та статус наявності.

Job Market Analysis: Збирайте назви вакансій, діапазони зарплат, потрібні навички, назви компаній і локації from job sites. Використовуйте ці дані для сповіщень про вакансії, аналізу трендів і бенчмаркінгу зарплат.

Business Intelligence: Витягуйте контактну інформацію, години роботи, оцінки клієнтів і категорії послуг із каталогів для генерації лідів та конкурентного аналізу.

Content Aggregation: Обходьте новинні сайти, блоги та контентні платформи, щоб створювати бази даних, відстежувати трендові теми та моніторити конкретні теми.

Identifying List Types

Перш ніж писати код, розберіться, як цільовий сайт структурує свої списки. Спосіб завантаження визначає вашу стратегію краулінгу.

Standard Pagination: Фіксована кількість елементів на сторінку з пронумерованими посиланнями або кнопками «Наступна». Номери сторінок відображаються в URL як ?page=2 or /page/2/. Найлегше обходити.

Load More Buttons: Та сама URL-адреса, нові елементи додаються після кліку. Автоматизуйте натискання кнопки або безпосередньо перехоплюйте фоновий API-запит.

Infinite Scroll: Нові елементи завантажуються автоматично під час прокручування вниз. Типово для соціальних мереж і сучасної електронної комерції. Потрібно імітувати дії прокручування або перехоплювати базові виклики API.

Search Result Pages: Результати залежать від параметрів запиту, фільтрів і сортування. Ваш краулер має керувати цими параметрами, одночасно обробляючи базову пагінацію. Перегляньте мою статтю про найкращі SERP API, що дає змогу пропустити логіку вебскрапінгу й отримувати результати напряму через API.

Вибір методу краулінгу

Метод Найкраще для Переваги Недоліки HTTP Requests Обробка статичного HTML із пагінацією на основі URL Швидкий, легкий, простий Не працює з контентом, згенерованим JavaScript Headless Browser Динамічний контент, що потребує JavaScript Обробляє складні взаємодії Ресурсоємний, складніше масштабувати Enterprise API Виробничі системи, яким потрібна надійність Керована інфраструктура, вбудована антибот-обробка Додаткова вартість

Спосіб 1: HTTP-запити з BeautifulSoup

Коли елементи списку присутні в початковій HTML-відповіді, витягуйте їх за допомогою HTTP-запитів і HTML-розбору.

Setup

pip install requests beautifulsoup4

Basic List Crawler

import csv
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
BASE_URL = "https://books.toscrape.com/"

def extract_items_from_page(soup, base_url):
    """Extract all items from a single page."""
    items = []
    
    for card in soup.select("article.product_pod"):
        link_el = card.select_one("h3 a")
        price_el = card.select_one("p.price_color")
        rating_el = card.select_one("p.star-rating")
        
        if not link_el or not price_el:
            continue
        
        item = {
            "title": link_el.get("title", "").strip(),
            "detail_url": urljoin(base_url, link_el.get("href", "")),
            "price": price_el.get_text(strip=True),
            "rating": extract_rating(rating_el)
        }
        items.append(item)
    
    return items

def extract_rating(rating_element):
    """Extract rating from CSS classes."""
    if not rating_element:
        return ""
    classes = rating_element.get("class", [])
    return " ".join(c for c in classes if c != "star-rating")

def find_next_page(soup, current_url):
    """Find and return the next page URL."""
    next_link = soup.select_one("li.next a")
    if next_link:
        return urljoin(current_url, next_link.get("href", ""))
    return None

def crawl_book_list(max_pages=5):
    """Main crawling function."""
    url = BASE_URL
    all_items = []
    page_count = 1
    
    while url and page_count <= max_pages:
        print(f"Краулінг сторінки {page_count}: {url}")
        
        response = requests.get(url, timeout=15)
        response.raise_for_status()
        
        soup = BeautifulSoup(response.text, "html.parser")
        items = extract_items_from_page(soup, url)
        print(f"Found {len(items)} елементи на сторінці {page_count}")
        
        if not items:
            print("Елементів не знайдено, зупиняємо обхід")
            break
        
        all_items.extend(items)
        url = find_next_page(soup, url)
        page_count  = 1
        
        time.sleep(1.5)
    
    print(f"Усього зібрано елементів: {len(all_items)}")
    return all_items

def save_results(items, filename="books_catalog.csv"):
    """Save extracted items to CSV."""
    if not items:
        print("Немає елементів для збереження")
        return
    
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=items[0].keys())
        writer.writeheader()
        writer.writerows(items)
    
    print(f"Saved {len(items)} елементи для {filename}")

if __name__ == "__main__":
    books = crawl_book_list(max_pages=5)
    save_results(books)

(response.

`Error:

Коли JavaScript динамічно генерує вміст списку, використовуйте справжній браузер. Playwright дає вам хороший контроль над автоматизацією браузера.

Setup

pip install playwright
playwright install chromium

Handling Infinite Scroll

import csv
import time
from playwright.sync_api import sync_playwright
TARGET_URL = "https://www.scrapingcourse.com/infinite-scrolling/"

def scroll_and_wait(page, scroll_pause=2.0):
    """Scroll to bottom and wait for content to load."""
    page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
    time.sleep(scroll_pause)

def get_item_count(сторінка, селектор):
    """Count currently visible items."""
    return page.locator(selector).count()

def extract_items(page):
    """Extract all visible items from the page."""
    items = []
    
    for card in page.locator(".product-item").all():
        item = {
            "name": card.locator(".product-name"Цей скрипт витягує товари лише з першої сторінки. Тепер змінимо його, щоб скрапити кілька сторінок.
            "price": card.locator(".product-price"Цей скрипт витягує товари лише з першої сторінки. Тепер змінимо його, щоб скрапити кілька сторінок.
            "url": card.locator("a"Крок 2: Вебскрапінг кількох сторінок"href") or ""
        Потрібно пройтися циклом по кожній сторінці та витягнути дані з усіх сторінок. Припускаємо, що сайт має 12 сторінок.
    
    return items

def crawl_infinite_scroll(baseURL, totalPages15):
    """Crawl an infinite scrolling list."""
    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=True)
        page = browser.new_page()
        
        page.goto(TARGET_URL, wait_until="networkidle")
        page.wait_for_selector(".product-item", timeout=10000)
        
        previous_count = 0
        `Scraping: 0
        max_stagnant = 3
        
        for scroll_num in range(max_scrolls):
            current_count = get_item_count(page, ".product-item")
            print(f"Scroll {scroll_num   1}: {current_count} видимі елементи")
            
            if current_count == previous_count:
                stagnant_scrolls  = 1
                if stagnant_scrolls >= max_stagnant:
                    print("Після кількох прокруток нових елементів немає, зупиняємося")
                    break
            else:
                stagnant_scrolls = 0
            
            previous_count = current_count
            scroll_and_wait(page)
        
        items = extract_items(page)
        browser.close()
        
        print(f"Extracted {len(items)} усього елементів\"")
        return items

def export_to_csv(items, filename="products.csv"):
    """Export items to CSV file."""
    if not items:
        print("Немає елементів для експорту")
        return
    
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=items[0].keys())
        writer.writeheader()
        writer.writerows(items)
    
    print(f"Exported {len(items)} елементи для {filename}")

if __name__ == "__main__":
    products = crawl_infinite_scroll(max_scrolls=15)
    export_to_csv(products)

Краулер зупиняється, коли кількість елементів залишається незмінною протягом трьох спроб прокрутки. Це означає, що ви дійшли до кінця списку.

Метод 3: Корпоративне рішення з Bright Data

Для production-систем, яким потрібні масштабованість і надійність, Bright Data надає керовану інфраструктуру без операційної рутини.

Option A: Web Scraper API

Bright Data Web Scraper API пропонує готові скрапери для популярних сайтів. Він автоматично обробляє рендеринг JavaScript, антиботові перевірки та керування проксі.

Синхронний вебскрапінг (для до 20 URL-адрес із результатами в реальному часі):

import requests
def scrape_urls_sync(urls, dataset_id, api_key):
    """Scrape URLs using Bright Data Web Scraper API."""
    endpoint = "https://api.brightdata.com/datasets/v3/scrape"
    
    params = {
        "dataset_id": dataset_id,
        "format": "json"
    }
    
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    payload = [{"url": url} for url in urls]
    
    response = requests.post(
        endpoint,
        params=params,
        headers=headers,
        json=payload,
        timeout=60
    )
    response.raise_for_status()
    
    return response.json()

if __name__ == "__main__":
    API_KEY = "YOUR_API_KEY"
    DATASET_ID = "gd_YOUR_DATASET_ID"  # Get from Bright Data dashboard
    
    urls = [
        "https://example.com/product/1",
        "https://example.com/product/2"
    ]
    
    results = scrape_urls_sync(urls, DATASET_ID, API_KEY)
    print(f"Scraped {len(results)} items")

Асинхронний вебскрапінг (для великих пакетів):

import requests
import time
def trigger_async_scrape(urls, dataset_id, api_key):
    """Start async scrape job."""
    endpoint = "https://api.brightdata.com/datasets/v3/trigger"
    
    params = {
        "dataset_id": dataset_id,
        "format": "json"
    }
    
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    payload = [{"url": url} for url in urls]
    
    response = requests.post(
        endpoint,
        params=params,
        headers=headers,
        json=payload
    )
    response.raise_for_status()
    
    return response.json()["snapshot_id"]

def check_snapshot_status(snapshot_id, api_key):
    """Check if snapshot is ready."""
    endpoint = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
    
    headers = {"Authorization": f"Bearer {api_key}"}
    
    response = requests.get(endpoint, headers=headers)
    response.raise_for_status()
    
    return response.json()["status"]

def download_snapshot(snapshot_id, api_key):
    """Download completed snapshot data."""
    endpoint = f"https://api.brightdata.com/datasets/v3/snapshot/{snapshot_id}"
    
    params = {"format": "json"}
    headers = {"Authorization": f"Bearer {api_key}"}
    
    response = requests.get(endpoint, params=params, headers=headers)
    response.raise_for_status()
    
    return response.json()

def scrape_urls_async(urls, dataset_id, api_key, poll_interval=10):
    """Scrape URLs with polling."""
    snapshot_id = trigger_async_scrape(urls, dataset_id, api_key)
    print(У цьому посібнику я покажу, як використовувати Axios для вебскрапінгу сайтів із пагінацією та збирати дані з кількох сторінок у Node.js. {snapshot_id}")
    
    while True:
        status = check_snapshot_status(snapshot_id, api_key)
        print(f"Status: {status}")
        
        if status == "ready":
            return download_snapshot(snapshot_id, api_key)
        elif status == "failed":
            raise Exception("Завдання на вебскрапінг не вдалося")
        
        time.sleep(poll_interval)

if __name__ == "__main__":
    API_KEY = "YOUR_API_KEY"
    DATASET_ID = "gd_YOUR_DATASET_ID"
    
    urls = ["https://example.com/product/"   str(i) for i in range(100)]
    
    results = scrape_urls_async(urls, DATASET_ID, API_KEY)
    print(f"Scraped {len(results)} items")

Варіант B: API браузера

Для кастомного вебскрапінгу з повним контролем браузера, Bright Data’s Browser API надає віддалені браузери з вбудованим обходом блокувань. Підключайтеся через Playwright або Puppeteer:

import asyncio
from playwright.async_api import async_playwright
AUTH = "brd-customer-CUSTOMER_ID-zone-ZONE_NAME:ZONE_PASSWORD"
async def scrape_with_browser_api(url):
    """Scrape using Bright Data Browser API with Playwright."""
    async with async_playwright() as playwright:
        endpoint_url = f"wss://{@brd.superproxy.io">AUTH}@brd.superproxy.io:9222"
        
        browser = await playwright.chromium.connect_over_cdp(endpoint_url)
        
        try:
            page = await browser.new_page()
            await page.goto(url, timeout=120000)
            
            # Your custom extraction logic
            items = []
            for card in await page.locator(".product-item").all():
                item = {
                    "name": await card.locator(".name").inner_text(),
                    "price": await card.locator(".price").inner_text()
                }
                items.append(item)
            
            return items
        finally:
            await browser.close()

if __name__ == "__main__":
    results = asyncio.run(scrape_with_browser_api("https://example.com/products"))
    print(f"Extracted {len(results)} items")

Варіант C: Unlocker API

Для простіших випадків, коли вам потрібен лише HTML без блокувань, використовуйте Unlocker API. Він повертає чистий HTML/JSON, автоматично обробляючи проксі, CAPTCHA та фінгерпринтинг:

import requests
from bs4 import BeautifulSoup
def fetch_with_unlocker(url, api_key, zone_name):
    """Fetch URL content using Bright Data Unlocker API."""
    endpoint = "https://api.brightdata.com/request"
    
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "zone": zone_name,
        "url": url,
        "format": "raw"
    }
    
    response = requests.post(endpoint, headers=headers, json=payload)
    response.raise_for_status()
    
    return response.text

def crawl_with_unlocker(base_url, api_key, zone_name, max_pages=5):
    """Crawl paginated list using Unlocker API."""
    all_items = []
    
    for page_num in range(1, max_pages   1):
        url = f"{base_url}?page={page_num}"
        print(f"Fetching page {page_num}")
        
        html = fetch_with_unlocker(url, api_key, zone_name)
        soup = BeautifulSoup(html, "html.parser")
        
        items = []
        for card in soup.select(".product-card"):
            items.append({
                "name": card.select_one(".name").get_text(strip=True),
                "price": card.select_one(".price").get_text(strip=True)
            })
        
        if not items:
            break
        
        all_items.extend(items)
    
    return all_items

Best Practices

Respect Rate Limits: Додавайте затримки між запитами. 1–2 секунди підходять для більшості сайтів. Для масштабніших краулів розподіляйте запити в часі.

Handle Errors: Виникатимуть проблеми з мережею та неочікувані структури сторінок. Використовуйте блоки try-except і перевіряйте дані перед збереженням.

Check Data Quality: Регулярно переглядайте витягнуті дані. Відсутні поля або обрізані списки означають, що у ваших селекторах або логіці обходу є проблеми.

Адаптуйтеся до змін: Сайти часто оновлюють свою HTML-структуру. Створюйте краулери з селекторами, які легко оновлювати, і регулярно тестуйте їх.

Summary

Обхід списків перетворює розрізнені вебдані на структуровані набори даних. Вибір методу залежить від технології цільового сайту та ваших потреб у масштабі.

Починайте з HTTP-запитів для статичних списків. Використовуйте headless-браузери, коли потрібен рендеринг JavaScript. Розгляньте корпоративні API на кшталт Bright Data, коли вам потрібні надійність, масштабованість і менші витрати на супровід.

Базові принципи залишаються незмінними: виявляйте повторювані шаблони, послідовно витягуйте дані та обходьте все повністю

Схожі записи