API ও web scraping
এই পাঠে যা শিখবেন
- REST API-এর মূল ধারণা — endpoint, method, JSON response
requestslibrary দিয়ে API call ও pagination handle- BeautifulSoup দিয়ে HTML parse করে data extract
- Web scraping-এর ethical ও legal boundary — robots.txt, ToS, PII
১ · Data source-এর তিন স্তর
ডেটা সংগ্রহের তিনটি প্রধান source:
১) 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
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]}...")
৪ · Query parameters ও pagination
বেশিরভাগ API একসাথে সব data দেয় না — pagination করতে হয়।
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
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।
.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।
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।
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))
১) 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 — সমস্যাজনক।
৮ · 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
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")
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ "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:
-
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।
-
Personal Data Protection Bill (২০২৪ draft):
- EU GDPR-এর আদলে।
- Personal data scraping → consent দরকার।
- Cross-border transfer regulated।
-
Copyright Act 2000:
- News article republish → infringement।
- Database compilation copyright।
- Fair use exceptions narrow।
-
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:
- robots.txt check।
- ToS read — explicit "no scraping" → reconsider।
- Identifiable User-Agent + contact email।
- Rate limit: ১-৫ সে delay; off-peak hours।
- Public data only — no login/paywall bypass।
- No PII; aggregated metrics OK।
- Cite source if publish।
- Stop on cease-and-desist।
- 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:
- Read API docs first।
- Implement exponential backoff with jitter।
- Cap max retries (5-10)।
- Distinguish retryable (5xx, 429) vs not (4xx)।
- Log every retry — debug-এ critical।
- Circuit breaker — prolonged failure auto-stop।
- 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):
- User-Agent block — rotate UA।
- IP rate limit — proxy rotation।
- JavaScript challenge — Playwright।
- CAPTCHA — solving service (ethical concern)।
- Honeypot link — hidden, bot trap।
- Session/cookie validation — maintain session।
- 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।
অনুশীলন
-
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। -
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 অপরিহার্য।
-
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।
আরও পড়ুন
- পাঠ ০৮ · Descriptive statistics পরবর্তী পাঠ ডেটা সংগ্রহ শেষ — এবার পরিসংখ্যানের module শুরু।
- পাঠ ০৬ · Window function আগের পাঠ SQL-এর গভীর skill।
- পাঠ ০১ · ডেটা সায়েন্স কী এই পাঠের সাথে সম্পর্কিত ডেটা সংগ্রহ মডিউলের সারসংক্ষেপ।
- সব AI Courses দেখুন ABCL TECH সব AI কোর্স।