# scraper.py import requests, re from bs4 import BeautifulSoup import pandas as pd from urllib.parse import urljoin HEADERS = { "User-Agent": "Mozilla/5.0 (compatible; TenderScraper/1.0; +https://huggingface.co/spaces)" } def parse_value(text: str) -> str: """Normalise value text like '15 Lakhs' → '15 Lakhs' (strip ₹ icon).""" return re.sub(r"[^\d.,A-Za-z ]+", "", text).strip() def scrape_tender_list(url: str) -> pd.DataFrame: """Return a DataFrame with one row per
.""" resp = requests.get(url, headers=HEADERS, timeout=20) resp.raise_for_status() soup = BeautifulSoup(resp.text, "html.parser") rows = [] for tr in soup.select("div.tender_row"): # Authority | City | State header = tr.select_one("h2.workDesc strong") if not header: continue parts = [t.strip() for t in header.stripped_strings if t.strip()] authority = parts[0] if parts else "" city = parts[1][2:] if len(parts) > 1 else "" # "- Raisen" → "Raisen" state = parts[2][2:] if len(parts) > 2 else "" # Tender ID + work description a = tr.select_one("a.m-brief") tender_id = a.select_one("span.m-tender-id").text.strip() if a else "" work = a.get_text(" ", strip=True).replace(tender_id, "").strip() if a else "" tender_url = urljoin(url, a["href"]) if a else "" # Due date date_span = tr.select_one("span.m-due-date") if date_span: due_text = " ".join( s.get_text(strip=True) for s in date_span.parent.select("span")[1:] ) else: due_text = "" # Tender value value_span = tr.select_one("span.m-value") value_text = parse_value(value_span.parent.get_text(" ", strip=True)) if value_span else "" rows.append({ "Authority": authority, "City": city, "State": state, "Tender ID": tender_id, "Work Description": work, "Due Date": due_text, "Estimated Value": value_text, "Notice URL": tender_url, }) return pd.DataFrame(rows)