Programování

Web scraping v Pythonu s httpx a BeautifulSoup – eticky

Jak stahovat data z webu pomocí httpx a BeautifulSoup, respektovat robots.txt, nezahltit server a vyhnout se právním problémům. Praktický kód a checklist.

12. 10. 2026 · 4 min čtení

Scraping je automatické získávání dat z webových stránek. Technicky je to pár řádků Pythonu, ale snadno se z něj stane nežádoucí zátěž pro cizí server. Tento návod ukazuje, jak scrapovat funkčně i slušně.

Než začnete: má to smysl?

Projděte si tento seznam:

  1. Existuje API nebo export dat? Pokud ano, použijte je. Je stabilnější a provozovatel s ním počítá.
  2. Co říkají podmínky užití webu? Některé weby scraping výslovně zakazují.
  3. Jde o osobní údaje? Jména, e-maily, telefony spadají pod GDPR i tehdy, když jsou veřejně dostupné. Jejich hromadný sběr potřebuje právní titul.
  4. Autorská práva a databázová práva. Převzetí podstatné části cizí databáze může být problém i bez porušení technických opatření.

Tento článek není právní rada – u komerčního využití se poraďte s právníkem.

Instalace

pip install httpx beautifulsoup4 lxml

lxml je rychlý parser; BeautifulSoup umí i vestavěný html.parser, pokud nechcete další závislost.

Respektujte robots.txt

Standardní knihovna obsahuje parser robots.txt:

from urllib.robotparser import RobotFileParser

USER_AGENT = "MujScraper/1.0 (+mailto:[email protected])"

rp = RobotFileParser("https://example.com/robots.txt")
rp.read()

if not rp.can_fetch(USER_AGENT, "https://example.com/produkty"):
    raise SystemExit("robots.txt přístup nepovoluje")

delay = rp.crawl_delay(USER_AGENT) or 2

Poznámka: RobotFileParser.read() používá urllib, ne httpx. Pro jednoduché skripty to nevadí.

User-Agent s kontaktem je slušnost – provozovatel vás může kontaktovat místo toho, aby vás rovnou zablokoval.

Stažení a parsování stránky

import time
import httpx
from bs4 import BeautifulSoup

client = httpx.Client(
    headers={"User-Agent": USER_AGENT},
    timeout=15,
    follow_redirects=True,
)

def stahni(url: str) -> BeautifulSoup:
    r = client.get(url)
    r.raise_for_status()
    return BeautifulSoup(r.text, "lxml")

soup = stahni("https://example.com/produkty")

for karta in soup.select("div.product"):
    nazev = karta.select_one("h2").get_text(strip=True)
    cena = karta.select_one(".price").get_text(strip=True)
    odkaz = karta.select_one("a")["href"]
    print(nazev, cena, odkaz)

Pozor: httpx na rozdíl od requests ve výchozím stavu nesleduje přesměrování – proto follow_redirects=True.

CSS selektory vs. find

soup.select("table.ceny tr")          # všechny shody, CSS selektor
soup.select_one("h1")                 # první shoda nebo None
soup.find_all("a", class_="detail")   # alternativa bez CSS

select_one vrací None, pokud nic nenajde. Volání .get_text() na None skončí AttributeError – u nespolehlivých stránek kontrolujte.

Relativní odkazy a stránkování

from urllib.parse import urljoin

base = "https://example.com/produkty"
url = base
while url:
    soup = stahni(url)
    # ... zpracování ...
    dalsi = soup.select_one("a.next")
    url = urljoin(url, dalsi["href"]) if dalsi else None
    time.sleep(delay)

urljoin správně složí /produkty?page=2 i ?page=2 s aktuální adresou.

Šetrnost k serveru

  • Prodleva mezi požadavky – aspoň 1–2 sekundy, nebo hodnota z Crawl-delay.
  • Žádný paralelní útok. Pokud už používáte async, omezte souběh na 1–2 požadavky na doménu.
  • Cache. Stránky během vývoje ukládejte na disk a parsujte z nich. Ladit selektory proti živému webu stovkami požadavků je zbytečné.
  • Reagujte na 429 a 503. Zpomalte, respektujte hlavičku Retry-After, případně skončete.
  • Scrapujte mimo špičku a jen to, co skutečně potřebujete.

Jednoduchá cache:

from pathlib import Path
import hashlib

CACHE = Path(".cache"); CACHE.mkdir(exist_ok=True)

def stahni_html(url: str) -> str:
    soubor = CACHE / hashlib.sha256(url.encode()).hexdigest()
    if soubor.exists():
        return soubor.read_text(encoding="utf-8")
    r = client.get(url); r.raise_for_status()
    soubor.write_text(r.text, encoding="utf-8")
    time.sleep(delay)
    return r.text

Uložení dat

import csv

with open("produkty.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=["nazev", "cena", "url"])
    w.writeheader()
    w.writerows(zaznamy)

Pro Excel s českými znaky použijte encoding="utf-8-sig".

Když stránka obsah generuje JavaScriptem

BeautifulSoup vidí jen HTML, které vrátil server. Pokud se data načítají JavaScriptem, otevřete v prohlížeči DevTools → Network a hledejte XHR/fetch požadavky – často najdete JSON endpoint, který je jednodušší i šetrnější než renderovat stránku. Headless prohlížeč (Playwright) je až poslední možnost.

Co nedělat

  • Neobcházejte přihlášení, CAPTCHA ani jiná technická opatření.
  • Nerotujte IP adresy a User-Agenty, abyste se vyhnuli blokaci – pokud vás web blokuje, dává tím jasně najevo svůj postoj.
  • Nepublikujte převzatý obsah jako vlastní.

Dodržíte-li tato pravidla, je scraping užitečný nástroj pro sledování cen, výzkum nebo archivaci vlastních dat.