পাঠ ০৭ · ৩০-এর মধ্যে · মডিউল ১

API ও web scraping

APIs & Web Scraping — beyond SQL data sources
৭ মিনিট পড়া মাঝারি · Intermediate Web data

এই পাঠে যা শিখবেন

  • REST API-এর মূল ধারণা — endpoint, method, JSON response
  • requests library দিয়ে API call ও pagination handle
  • BeautifulSoup দিয়ে HTML parse করে data extract
  • Web scraping-এর ethical ও legal boundary — robots.txt, ToS, PII

১ · Data source-এর তিন স্তর

ডেটা সংগ্রহের তিনটি প্রধান source:

Three layers of data acquisition

১) Internal database: SQL warehouse — পাঠ ৩-৬-এ।
২) External API: structured access — JSON, documented। weather, currency, social media।
৩) Web scraping: HTML থেকে extract — যখন API নেই। fragile, ethical concerns।

২ · API — কী ও কেন

APIAPIApplication Programming Interface। দু'টি software-এর মধ্যে structured communication-এর contract। REST, GraphQL, gRPC বিভিন্ন style। আজকের REST API সাধারণত HTTP + JSON-এ চলে। = "এই URL-এ request পাঠালে JSON response পাবেন।" সবচেয়ে common style — REST API:

  • HTTP method: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)।
  • Endpoint: URL path — /api/v1/customers/123।
  • Headers: auth token, content-type।
  • Response: সাধারণত JSON, status code (200 OK, 404 not found)।

৩ · Python-এ requests — প্রথম API call

Python · requests
import requests

# একটি free API — JSONPlaceholder
url = "https://jsonplaceholder.typicode.com/posts/1"
response = requests.get(url)

print(f"Status: {response.status_code}")
print(f"Type: {response.headers['content-type']}")

data = response.json()  # JSON → Python dict
print(f"Title: {data['title']}")
print(f"Body: {data['body'][:60]}...")

    
Status code: 200 (OK), 4xx (client error — your fault), 5xx (server error)। সবসময় check করে তারপর process।

৪ · Query parameters ও pagination

বেশিরভাগ API একসাথে সব data দেয় না — pagination করতে হয়।

Python · requests
import requests
import time

# OpenWeather API উদাহরণ (api key লাগবে free signup)
def fetch_weather(city, api_key):
    url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "q": city,
        "appid": api_key,
        "units": "metric",
    }
    r = requests.get(url, params=params, timeout=10)
    r.raise_for_status()  # 4xx/5xx → exception
    return r.json()

# Pagination উদাহরণ
def fetch_all_pages(base_url, api_key):
    all_data = []
    page = 1
    while True:
        r = requests.get(
            base_url,
            params={"page": page, "per_page": 100},
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=15,
        )
        r.raise_for_status()
        chunk = r.json()
        if not chunk.get("data"):
            break
        all_data.extend(chunk["data"])
        if not chunk.get("has_next"):
            break
        page += 1
        time.sleep(0.5)  # rate-limit respect
    return all_data

    
Best practice: timeout সবসময় set করুন (নয়তো hang)। raise_for_status() error early surface করে। time.sleep() rate limit-এ courtesy।

৫ · Authentication — ৩ ধরনের

  • API key (header/param): সবচেয়ে simple। OpenWeather, NewsAPI।
  • Bearer token (OAuth): Twitter, Github। sign-in flow → token।
  • HMAC signature: AWS, banking — request body sign।
API key নিরাপত্তা: কোনোভাবেই code-এ hardcode করবেন না; .env file বা secret manager-এ। GitHub-এ leaked key — মিনিটের মধ্যে scraper bot exploit করে।

৬ · Web scraping — যখন API নেই

Bangladesh-এর অনেক public data এখনো শুধু website-এ — bdnews24, prothom-alo archive, government portals। Scraping দিয়ে extract।

Python · BeautifulSoup
import requests
from bs4 import BeautifulSoup

# Quotes website (scraping practice-friendly)
url = "https://quotes.toscrape.com/"

headers = {
    "User-Agent": "Mozilla/5.0 (educational scraper - contact: x@y.com)"
}
r = requests.get(url, headers=headers, timeout=10)
r.raise_for_status()

soup = BeautifulSoup(r.text, "html.parser")

quotes = []
for q in soup.select("div.quote"):
    text   = q.select_one("span.text").get_text(strip=True)
    author = q.select_one("small.author").get_text(strip=True)
    tags   = [t.get_text(strip=True) for t in q.select("a.tag")]
    quotes.append({"text": text, "author": author, "tags": tags})

print(f"Extracted {len(quotes)} quotes")
print(quotes[0])

    

৭ · Robots.txt ও ethics

প্রতিটি website-এর /robots.txt বলে দেয় কোন path scrape করা allowed, কোনটি না। এটি honor করা হলো প্রথম ethical baseline।

Python · urllib.robotparser
from urllib.robotparser import RobotFileParser

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

# একটি path scrape করা allowed কিনা
ua = "MyResearchBot/1.0"
print(rp.can_fetch(ua, "https://example.com/products"))
print(rp.can_fetch(ua, "https://example.com/admin"))

# Crawl-delay (যদি specified)
print(rp.crawl_delay(ua))

    
Scraping ethics — ৬ rules

১) robots.txt — disallowed path স্পর্শ করবেন না।
২) Rate limit: request-এ delay (১-৫ সেকেন্ড)। Server overload করবেন না।
৩) Honest User-Agent: "Mozilla" pretend না করে আপনার identity + contact email।
৪) ToS পড়ুন: অনেক site-এ scraping explicitly forbidden।
৫) PII এড়ান: ফোন, email, ছবি — privacy-sensitive scrape করবেন না।
৬) Public benefit: research, journalism — নৈতিক ব্যবহার; commercial repackaging — সমস্যাজনক।

Data কোথা থেকে আনব? — decision tree Try fastest, cheapest, most ethical first নতুন data দরকার? কোম্পানির warehouse-এ আছে? হ্যাঁ না ✓ SQL query fast, free, legal Public API আছে? REST/GraphQL হ্যাঁ না ✓ requests + JSON structured, stable ⚠ Scrape (last resort) check robots.txt + ToS Top → bottom: speed কমে, fragility বাড়ে, legal risk বাড়ে।
Data acquisition decision tree — যা সম্ভব ভিতরে রাখুন; যা পাবলিক API আছে সেটা দ্বিতীয়; scrape last resort।

৮ · Bangladesh-এর কাজে আসা data sources

  • Bangladesh Bank API: currency rate, money supply (limited)।
  • BBS data portal: census, household survey — ফাইল-based।
  • data.gov.bd: open data — agricultural, education metrics।
  • World Bank API: Bangladesh economic indicators — GDP, inflation, employment।
  • OpenWeather: weather data — agriculture, logistics analytics-এ।
  • Google Trends: পণ্য demand signal।
  • Twitter/X API: sentiment analysis (paid tier)।
  • News scraping (with caution): Prothom Alo, Daily Star headlines for trend।

৯ · Production-grade pattern — robust pipeline

Python · requests + retry
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import logging

logging.basicConfig(level=logging.INFO)
log = logging.getLogger("api_pipeline")

def make_session():
    s = requests.Session()
    retry = Retry(
        total=5,
        backoff_factor=1.5,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["GET"],
    )
    s.mount("https://", HTTPAdapter(max_retries=retry))
    s.headers.update({
        "User-Agent": "ABCL-Tech-Crawler/1.0 (research; contact@abcltech.com)"
    })
    return s

def safe_get(session, url, **kwargs):
    try:
        r = session.get(url, timeout=15, **kwargs)
        r.raise_for_status()
        return r.json()
    except requests.exceptions.HTTPError as e:
        log.error(f"HTTP {e.response.status_code} on {url}")
    except requests.exceptions.Timeout:
        log.error(f"Timeout on {url}")
    except requests.exceptions.RequestException as e:
        log.error(f"Request failed: {e}")
    return None

# Usage
session = make_session()
data = safe_get(session, "https://api.example.com/data")
if data:
    log.info(f"Got {len(data)} records")

    
Production pipeline-এ — retry strategy, exponential backoff, structured logging, identifiable User-Agent, exception handling। "Quick script" থেকে "robust pipeline"-এর পার্থক্য এই detail-এ।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ "Web scraping legal না illegal?" — এই প্রশ্নের উত্তর nuanced। বাংলাদেশী context-এ কী কী আইনি ও নৈতিক বিবেচনা আপনাকে মাথায় রাখতে হবে?

Web scraping-এর legality jurisdiction, content type, ও use case-ভেদে ভিন্ন।

Global landmark cases:

  • HiQ Labs vs LinkedIn (২০১৭, US): public profile scraping permitted; LinkedIn-এর "no scraping" ToS not enforceable for public data।
  • Facebook vs Power Ventures: automated access without permission CFAA violation।
  • Ryanair vs PR Aviation: EU — database right invoke।
  • Van Buren vs US: CFAA "exceeds authorized access" narrow — scraping public data সাধারণত OK।

Bangladesh-specific concerns:

  1. Digital Security Act 2018:
    • Section 22 — unauthorized access to data → up to 7 years।
    • Section 25 — fake/false data publish → criminal।
    • Vague language — risk-averse approach।
  2. Personal Data Protection Bill (২০২৪ draft):
    • EU GDPR-এর আদলে।
    • Personal data scraping → consent দরকার।
    • Cross-border transfer regulated।
  3. Copyright Act 2000:
    • News article republish → infringement।
    • Database compilation copyright।
    • Fair use exceptions narrow।
  4. ICT Act 2006 (amended):
    • Section 56 — unauthorized access penalty।
    • Section 57 — defamation via digital — vague।

Ethical framework — beyond law:

  • Public interest: journalism, research → defensible।
  • Commercial repackaging: someone else's content monetize — gray area।
  • Personal data: avoid even if technically allowed।
  • Server burden: small site DDoS — irrespective of legality, irresponsible।
  • Reciprocity: "would I want my site scraped this way?"

Safe scraping checklist:

  1. robots.txt check।
  2. ToS read — explicit "no scraping" → reconsider।
  3. Identifiable User-Agent + contact email।
  4. Rate limit: ১-৫ সে delay; off-peak hours।
  5. Public data only — no login/paywall bypass।
  6. No PII; aggregated metrics OK।
  7. Cite source if publish।
  8. Stop on cease-and-desist।
  9. Lawyer consult-এর জন্য proceed-এ doubt।

API-first culture:

  • Email site owner — API request — অনেকে provide করেন।
  • Partnership অপশন — sometimes paid API access।
  • Open data movement support — government data unlock-এ advocate।

Bangladesh real cases:

  • News aggregator Bondhu — copyright concern news scrape।
  • Job portal data scraping — student researchers ToS-এ atki।
  • Real estate aggregator — bikroy + bproperty দু'টোর scraping issue।

মূল উপলব্ধি: Legality + ethics + practicality — তিনটি বিচার। Lawyer-এর কাছে ১ ঘণ্টার consultation = পরের ১ বছরের headache কম।

প্র ০২ API rate limit, retry, exponential backoff — production data pipeline-এ critical। কিন্তু এই concept-গুলো কেন এত গুরুত্বপূর্ণ এবং সঠিক implementation কেমন?

"Quick script" থেকে "production pipeline"-এর পার্থক্য rate limit handling-এ।

Rate limit-এর প্রকৃতি:

  • API provider compute & cost protect করে।
  • Common patterns: 60/min, 1000/hour, 10000/day।
  • Burst limits + sustained limits — ভিন্ন।
  • Headers: X-RateLimit-Remaining, X-RateLimit-Reset।
  • HTTP 429 (Too Many Requests) — explicit signal।

Exponential backoff math:

delay(n) = base * (factor ^ n) + jitter
# 1st retry: 1s
# 2nd retry: 2s
# 3rd retry: 4s
# 4th retry: 8s
# ...
# +random jitter to avoid thundering herd

Why exponential — not linear:

  • Linear: 1s, 2s, 3s — slow recovery।
  • Exponential: 1s, 2s, 4s, 8s — quick relief in early failures, give server time।
  • Distributed system theory — proven optimal।

Implementation patterns:

# 1. Library-based (preferred)
from urllib3.util.retry import Retry
retry = Retry(
    total=5,
    backoff_factor=1.5,
    status_forcelist=[429, 500, 502, 503, 504],
)

# 2. Manual with tenacity
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=2, max=60),
)
def fetch(url):
    r = requests.get(url, timeout=10)
    r.raise_for_status()
    return r.json()

# 3. Adaptive (read header)
def adaptive_fetch(url):
    while True:
        r = requests.get(url)
        if r.status_code == 429:
            reset = int(r.headers.get("X-RateLimit-Reset", 60))
            time.sleep(reset)
            continue
        return r

Concurrency-aware rate limiting:

  • Multiple workers concurrently — global rate limit share করা দরকার।
  • Token bucket algorithm — Redis-backed।
  • Library: aiolimiter, ratelimit।

Common mistakes:

  • No retry → single failure pipeline break।
  • Infinite retry → DoS your own provider।
  • No backoff → stampede।
  • No jitter → coordinated retry storm।
  • Retry POST blindly → duplicate operations।
  • No timeout → hang forever।

Idempotency consideration:

  • GET — safe to retry।
  • PUT/DELETE — usually idempotent।
  • POST — risk of duplicate। Use idempotency key।

Monitoring:

  • Retry count metric।
  • P99 latency।
  • 429 count alert।
  • Backoff time logged।

Real-world story:

  • Bangladeshi e-commerce — competitor price scraping; aggressive concurrency → IP banned + lawsuit।
  • News aggregator — exponential retry without max → infinite loop, $5000 cloud bill।
  • Social media analytics — proper backoff → sustained 99% success over 2 years।

Best practice template:

  1. Read API docs first।
  2. Implement exponential backoff with jitter।
  3. Cap max retries (5-10)।
  4. Distinguish retryable (5xx, 429) vs not (4xx)।
  5. Log every retry — debug-এ critical।
  6. Circuit breaker — prolonged failure auto-stop।
  7. Test with chaos — inject failures।

মূল উপলব্ধি: Rate limit handling distributed system reliability-এর core skill। ১ ঘণ্টার extra effort — পরের ১ বছরের 3am alert avoid।

প্র ০৩ "BeautifulSoup vs Scrapy vs Playwright" — তিনটি ভিন্ন scraping tool। কোনটি কখন use করব?

Scraping tool selection — task-এর nature-এর উপর নির্ভর।

(১) BeautifulSoup + requests:

  • Strength: simple, beginner-friendly, no setup।
  • Weakness: static HTML only; JavaScript-rendered content miss।
  • Use: small projects, well-structured static sites, learning।
  • Speed: single-threaded, ~১০ pages/min।

(২) Scrapy:

  • Strength: framework, async, built-in retry/throttle/pipeline।
  • Weakness: learning curve; overkill small task-এ।
  • Use: large crawl, structured pipeline, production।
  • Speed: async, ~১০০-১০০০ pages/min।
  • Features: spider, item, pipeline, middleware, autothrottle।

(৩) Playwright/Selenium:

  • Strength: real browser, JavaScript-rendered, anti-bot bypass।
  • Weakness: slow (full page render), resource-heavy।
  • Use: SPA, login flows, screenshot, dynamic content।
  • Speed: ~৫-২০ pages/min।

Modern hybrid — Scrapy + Playwright:

scrapy-playwright integration:
- Scrapy framework + Playwright for JS pages
- Selectively render — speed maximize

Decision tree:

  • Static site, <100 pages → BeautifulSoup।
  • Static site, >1000 pages → Scrapy।
  • JS-heavy, login-required → Playwright।
  • Both static + dynamic, large scale → Scrapy + Playwright।

Other tools to know:

  • lxml: faster than BeautifulSoup for big HTML।
  • requests-html: JS render via Pyppeteer।
  • parsel: Scrapy-এর selector independent।
  • httpx: requests-এর async successor।
  • cloudscraper: Cloudflare bypass।

Anti-scraping measures (commonly faced):

  1. User-Agent block — rotate UA।
  2. IP rate limit — proxy rotation।
  3. JavaScript challenge — Playwright।
  4. CAPTCHA — solving service (ethical concern)।
  5. Honeypot link — hidden, bot trap।
  6. Session/cookie validation — maintain session।
  7. Behavioral analysis — random delays, mouse movement।

Bangladesh examples:

  • News scraping — most static, BeautifulSoup যথেষ্ট।
  • E-commerce product list — JavaScript-heavy, Playwright।
  • Job portal — login required, Playwright।
  • Government PDF — pdfplumber + requests।

Maintenance reality:

  • Scraper average lifetime ৩-৬ মাস — sites HTML change করে।
  • Brittle — production-এ monitoring + alert essential।
  • API থাকলে scraper-এর choice নেই — ১০x kemo lifetime।

মূল উপলব্ধি: "One tool fits all" নয়। Task scope, JS dependency, scale — বিচার করে select। Senior data engineer-রা সব tool-এ basic familiar — situation-এ pick।

প্র ০৪ আপনাকে দিতে হবে — Bangladesh-এর সব district-এর daily weather + USD-BDT exchange rate + একটি e-commerce-এর top-100 product price — তিন ভিন্ন source থেকে data integrate। এই pipeline কীভাবে design করবেন?

একটি real-world data engineering scenario — multiple source orchestration।

Source-by-source plan:

(ক) Weather — OpenWeather API:

  • ৬৪ district-এর coordinate list-এ daily call।
  • Free tier: 60 calls/min — যথেষ্ট।
  • Schedule: প্রতিদিন সকাল ৬টায়।
  • Storage: weather_daily(district, date, temp_c, humidity, rainfall_mm)।

(খ) USD-BDT rate — Bangladesh Bank API / fallback:

  • Primary: Bangladesh Bank-এর daily reference rate page (scrape if no API)।
  • Fallback: ExchangeRate-API (free tier)।
  • Schedule: প্রতিদিন সকাল ১১টা (BB publish-এর পর)।
  • Storage: fx_daily(date, base_ccy, target_ccy, rate, source)।

(গ) E-commerce price — Scraping (with caution):

  • robots.txt check; ToS read।
  • Public product listings, no login।
  • Scrapy + Playwright (JS-rendered)।
  • Schedule: প্রতিদিন রাত ২টা (off-peak)।
  • Identifiable User-Agent, 5-second delay।
  • Storage: product_price(date, product_id, name, price_bdt, in_stock)।

Architecture:

┌─────────────┐    ┌────────────────┐    ┌─────────────┐
│ OpenWeather │ ──→│  Airflow DAG   │ ──→│  PostgreSQL │
│  (API)      │    │  daily 6am     │    │   weather   │
└─────────────┘    └────────────────┘    └─────────────┘

┌─────────────┐    ┌────────────────┐    ┌─────────────┐
│  BB Page    │ ──→│  Airflow DAG   │ ──→│  PostgreSQL │
│  (scrape)   │    │  daily 11am    │    │     fx      │
└─────────────┘    └────────────────┘    └─────────────┘

┌─────────────┐    ┌────────────────┐    ┌─────────────┐
│ E-commerce  │ ──→│  Scrapy spider │ ──→│  PostgreSQL │
│   (HTML)    │    │  daily 2am     │    │  product    │
└─────────────┘    └────────────────┘    └─────────────┘
                          ↓
                    ┌──────────────┐
                    │ dbt model    │
                    │ unified view │
                    └──────────────┘

Orchestration tool — Airflow DAG:

from airflow import DAG
from airflow.operators.python import PythonOperator

with DAG('data_acquisition', schedule='0 6 * * *') as dag:
    weather  = PythonOperator(task_id='weather',  python_callable=fetch_weather)
    fx_rate  = PythonOperator(task_id='fx',       python_callable=fetch_fx)
    products = PythonOperator(task_id='products', python_callable=run_scrapy)
    [weather, fx_rate, products]  # parallel

Error handling per source:

  • Weather API: retry 3x; if persistent fail, alert ও skip district।
  • FX: if BB unavailable, fallback API; if both fail, use yesterday + flag।
  • Scraping: per-product retry; partial failure OK; site structure change → schema alert।

Data quality checks:

  • Weather temp range (-5 to 50°C) — outlier flag।
  • FX rate change > 5% day-over-day — alert (BB peg history)।
  • Product count drop > 30% — site change suspect।
  • Schema validation — column count, type।

Storage strategy:

  • Raw layer: JSON/HTML as received — re-parse possible।
  • Staging: parsed, validated।
  • Mart: business-ready, joined।

Security:

  • API keys → secret manager (AWS SSM, Vault)।
  • Database creds → encrypted।
  • Logging — exclude sensitive headers।

Observability:

  • Airflow UI — DAG status।
  • Slack alert — task failure।
  • Grafana dashboard — row counts trend।
  • Sentry — exception tracking।

Future scaling:

  • ৬৪ → ৪০০ upazila — API call rate plan।
  • Hourly → real-time streaming (Kafka)।
  • Multiple e-commerce — distributed scraping।

মূল উপলব্ধি: Real data pipeline 80% engineering, 20% transformation। Source diversity manage করতে orchestration + monitoring + error handling — তিনটি equally important। SQL-pandas-API-scrape সব skill মিলে modern data engineer।

অনুশীলন

  1. Public API call: JSONPlaceholder API থেকে প্রথম ১০টি posts টানুন এবং এক pandas DataFrame-এ রূপান্তর করুন।
    import requests
    import pandas as pd
    
    url = "https://jsonplaceholder.typicode.com/posts"
    r = requests.get(url, timeout=10)
    r.raise_for_status()
    
    posts = r.json()[:10]  # প্রথম ১০
    df = pd.DataFrame(posts)
    print(df.head())
    print(f"\nShape: {df.shape}")
    print(f"Columns: {list(df.columns)}")

    Common cleanup: df.drop_duplicates(), type-cast (e.g. userId → int), text cleaning।

  2. HTML scraping: quotes.toscrape.com থেকে সব quotes + tags extract করে JSON ফাইলে save করুন।
    import requests
    from bs4 import BeautifulSoup
    import json
    import time
    
    base_url = "https://quotes.toscrape.com/page/{}/"
    all_quotes = []
    page = 1
    
    while True:
        r = requests.get(base_url.format(page), timeout=10)
        if r.status_code != 200:
            break
    
        soup = BeautifulSoup(r.text, "html.parser")
        quotes = soup.select("div.quote")
        if not quotes:
            break
    
        for q in quotes:
            all_quotes.append({
                "text":   q.select_one("span.text").get_text(strip=True),
                "author": q.select_one("small.author").get_text(strip=True),
                "tags":   [t.get_text(strip=True) for t in q.select("a.tag")],
            })
    
        page += 1
        time.sleep(1)  # polite
    
    with open("quotes.json", "w", encoding="utf-8") as f:
        json.dump(all_quotes, f, ensure_ascii=False, indent=2)
    
    print(f"Saved {len(all_quotes)} quotes to quotes.json")

    Note: এই site scraping-friendly হিসেবে designed; production-এ robots.txt + rate check অপরিহার্য।

  3. Robust API integration: একটি function লিখুন যা পাঁচটি retry সহ exponential backoff-এ একটি API call করে এবং error case-এ structured log দেয়।
    import requests
    import time
    import logging
    import random
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
    log = logging.getLogger(__name__)
    
    def fetch_with_retry(url, max_retries=5, base_delay=1.0, **kwargs):
        """
        Fetch URL with exponential backoff + jitter.
        Returns parsed JSON or None on permanent failure.
        """
        for attempt in range(max_retries):
            try:
                r = requests.get(url, timeout=15, **kwargs)
                if r.status_code == 200:
                    return r.json()
                if r.status_code in (429, 500, 502, 503, 504):
                    delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
                    log.warning(f"HTTP {r.status_code} on {url}; retry {attempt+1}/{max_retries} in {delay:.1f}s")
                    time.sleep(delay)
                    continue
                log.error(f"HTTP {r.status_code} on {url}; non-retryable")
                return None
            except requests.exceptions.Timeout:
                log.warning(f"Timeout on {url}; attempt {attempt+1}")
                time.sleep(base_delay * (2 ** attempt))
            except requests.exceptions.RequestException as e:
                log.error(f"Request error: {e}")
                return None
        log.error(f"All {max_retries} retries exhausted for {url}")
        return None
    
    # Test
    data = fetch_with_retry("https://jsonplaceholder.typicode.com/posts/1")
    if data:
        print(data["title"])

    Key features: exponential backoff (2^n seconds), jitter to avoid thundering herd, distinguish retryable (5xx, 429, timeout) vs not (4xx other), structured logging।

আরও পড়ুন

Free public APIs বিশাল list: github.com/public-apis — practice-এর জন্য source খুঁজতে।
পূর্ববর্তী পাঠ
পাঠ ০৬ · Window function ও CTE