Skip to content

Responsible Web Scraping in Python: A Solid Start for Price Monitoring

Checking robots.txt, choosing between requests and Selenium 4, parsing prices with Decimal and bulk-loading into SQL Server: the pieces of a scraper that is polite and tells you when it breaks.

Python updated 25.10.2026 5 min read Atılay Berber

Web scraping is a powerful way to keep an eye on competitor prices, promotions or any public catalog. But a good scraper isn’t just one that gets the data. It’s one that doesn’t hammer the site, plays by the rules, and tells you when it breaks. Here are the building blocks of a scraper in Python that actually holds up.

1. Read the rules first

Before you write a line of code, check the site’s terms of use and its robots.txt. If there’s an API or a data export, always use that instead. Checking robots.txt takes a few lines with the standard library:

from urllib import robotparser

rp = robotparser.RobotFileParser()
rp.set_url("https://www.example-store.com/robots.txt")
rp.read()

url = "https://www.example-store.com/category/dairy"
if not rp.can_fetch("PriceWatchBot", url):
    raise SystemExit("Crawling this page is not allowed.")

delay = rp.crawl_delay("PriceWatchBot") or 2  # seconds

If you hit a CAPTCHA, a login wall or bot protection, don’t treat it as an obstacle to get around. Treat it as the site saying “no”, and stop.

2. Static pages only need requests

If the prices are already in the HTML, there’s no need to spin up a browser. requests plus BeautifulSoup is much faster and lighter. Send a User-Agent that identifies you, and wait between requests:

import time
import requests
from bs4 import BeautifulSoup

session = requests.Session()
session.headers["User-Agent"] = "PriceWatchBot/1.0 (+https://www.atilayberber.com)"

def fetch_page(url):
    response = session.get(url, timeout=20)
    response.raise_for_status()
    time.sleep(delay)
    return BeautifulSoup(response.text, "html.parser")

3. JavaScript-rendered pages: Selenium 4

If products are loaded by JavaScript after the page opens, you need Selenium. The old find_element_by_* methods are gone in Selenium 4; everything goes through By now. Use an explicit wait instead of time.sleep. It’s both faster and more reliable:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)  # Selenium Manager finds the driver for you

try:
    driver.get(url)
    WebDriverWait(driver, 15).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".product-card"))
    )
    cards = driver.find_elements(By.CSS_SELECTOR, ".product-card")
    for card in cards:
        name = card.find_element(By.CSS_SELECTOR, ".product-name").text
        price = card.find_element(By.CSS_SELECTOR, ".product-price").text
finally:
    driver.quit()

4. Parse prices properly

Turkish sites write prices like “1.249,90 TL”, with a dot for thousands and a comma for decimals. Throwing that at float either fails or quietly loses precision. Use Decimal for money:

from decimal import Decimal, InvalidOperation
import re

def parse_price(text: str) -> Decimal | None:
    clean = re.sub(r"[^\d,\.]", "", text)          # "1.249,90"
    clean = clean.replace(".", "").replace(",", ".")  # "1249.90"
    try:
        return Decimal(clean)
    except InvalidOperation:
        return None

parse_price("1.249,90 TL")  # Decimal('1249.90')

5. Write in batches

Running a separate INSERT and commit for every product is slow. Collect the rows and write them in one go. If you’re on SQL Server, pyodbc‘s fast_executemany makes a huge difference. And always close your connections and cursors:

import pyodbc
from contextlib import closing

with closing(pyodbc.connect(CONNECTION_STRING)) as conn:
    with closing(conn.cursor()) as cursor:
        cursor.fast_executemany = True
        cursor.executemany(
            "INSERT INTO dbo.Price (Source, ProductName, Price, ScrapedAt) VALUES (?, ?, ?, SYSDATETIME())",
            rows,
        )
    conn.commit()

6. Know when it breaks

  • Log how many products each run picked up. If the number suddenly drops, the site’s HTML has almost certainly changed.
  • Deduplicate by product code or URL so the same product doesn’t come in twice across pages.
  • Keep your selectors (the CSS classes) in one config section instead of scattering them through the code. When the site changes, you fix it in one place.

Scraping is only the first step. The real value comes from writing that data to a database on a schedule and making the changes over time visible in a tool like Power BI.

Common mistakes

  • Ignoring 429 responses: A “Too Many Requests” (429) response means the site wants you to slow down. Respect the Retry-After header and back off further on each retry instead of hammering the server.
  • Brittle selectors: Long XPath chains and auto-generated class names break on the first redesign. Prefer stable IDs, data-* attributes or the structured data (JSON-LD) on the page.
  • Failing silently: Writing an unreadable price as 0 corrupts your report. Log the failures separately and track their count.
  • Collecting personal data: Do not collect reviews, user names or other personal data. Product and price information is all you need for price monitoring, and personal data brings privacy law into play.

Frequently asked questions

Is web scraping legal?

This is not legal advice, but the general picture is this: pulling public data while following the site’s terms and robots.txt, without overloading it and without collecting personal data, carries far less risk. When in doubt, ask the site owner or use an official API if one exists.

How do I run the scraper on a schedule?

Task Scheduler on Windows or cron on Linux is enough. Log the start time, end time and row count of every run to a table so you spot problems early. You can then calculate price changes in SQL Server with LAG and visualize them in Power BI.

STAY IN THE LOOP

New guides, sorted by topic in a single email.

Every Monday, the week's new posts arrive grouped by topic. No new posts, no email.

Nothing is sent until you click the link in the confirmation email. Unsubscribe anytime with one click.

Found this useful? Share it

Leave a comment

Your email address will not be published. Required fields are marked *