| """ |
| Scraper for Sri Lanka DMC Situation Reports. |
| |
| Fetches situation report PDFs from the Disaster Management Centre website. |
| """ |
|
|
| import requests |
| from bs4 import BeautifulSoup |
| import urllib3 |
| from typing import Optional |
| import re |
|
|
| |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
|
|
| |
| BASE_URL = "https://www.dmc.gov.lk" |
|
|
| |
| SITREP_PAGE_URL = ( |
| "https://www.dmc.gov.lk/index.php?" |
| "option=com_dmcreports&view=reports&Itemid=273&report_type_id=1&lang=en" |
| ) |
|
|
| |
| LANDSLIDE_PAGE_URL = ( |
| "https://www.dmc.gov.lk/index.php?" |
| "option=com_dmcreports&view=reports&Itemid=276&report_type_id=5&lang=en" |
| ) |
|
|
| |
| FLOOD_PAGE_URL = ( |
| "https://www.dmc.gov.lk/index.php?" |
| "option=com_dmcreports&view=reports&Itemid=277&report_type_id=6&lang=en" |
| ) |
|
|
| HEADERS = { |
| "User-Agent": ( |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " |
| "AppleWebKit/537.36 (KHTML, like Gecko) " |
| "Chrome/120.0.0.0 Safari/537.36" |
| ), |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", |
| } |
|
|
|
|
| def get_sitrep_list(limit: int = 5) -> list[dict]: |
| """ |
| Fetch a list of sitrep metadata from DMC website. |
| |
| Args: |
| limit: Maximum number of reports to return |
| |
| Returns: |
| List of dictionaries with title, date, time, and pdf_url, ordered newest first. |
| """ |
| try: |
| response = requests.get( |
| SITREP_PAGE_URL, |
| headers=HEADERS, |
| timeout=30, |
| verify=False |
| ) |
| response.raise_for_status() |
| except requests.RequestException as e: |
| print(f"Error fetching DMC page: {e}") |
| return [] |
| |
| soup = BeautifulSoup(response.text, "html.parser") |
| |
| reports = [] |
| seen_urls = set() |
| |
| |
| for link in soup.find_all("a", href=True): |
| href = link.get("href", "") |
| text = link.get_text(strip=True).lower() |
| |
| |
| if ".pdf" in href.lower(): |
| is_sitrep = ( |
| "situation" in href.lower() or |
| "situation" in text or |
| "sitrep" in href.lower() or |
| "sitrep" in text |
| ) |
| if is_sitrep: |
| |
| pdf_url = href |
| if not pdf_url.startswith("http"): |
| if pdf_url.startswith("/"): |
| pdf_url = f"{BASE_URL}{pdf_url}" |
| else: |
| pdf_url = f"{BASE_URL}/{pdf_url}" |
| |
| |
| if pdf_url in seen_urls: |
| continue |
| seen_urls.add(pdf_url) |
| |
| |
| report_info = _extract_report_metadata(link, pdf_url) |
| reports.append(report_info) |
| |
| |
| |
| |
| return reports[:limit] |
|
|
|
|
| def _extract_report_metadata(link, pdf_url: str) -> dict: |
| """Extract metadata from a report link.""" |
| |
| row = link.find_parent("tr") |
| if row: |
| cells = row.find_all("td") |
| title = cells[0].get_text(strip=True) if cells else "" |
| date = cells[1].get_text(strip=True) if len(cells) > 1 else "" |
| time = cells[2].get_text(strip=True) if len(cells) > 2 else "" |
| |
| |
| if cells and cells[0].get_text(strip=True).isdigit(): |
| title = cells[1].get_text(strip=True) if len(cells) > 1 else "" |
| date = cells[2].get_text(strip=True) if len(cells) > 2 else "" |
| time = cells[3].get_text(strip=True) if len(cells) > 3 else "" |
| |
| return { |
| "title": title or "Situation Report", |
| "date": date, |
| "time": time, |
| "pdf_url": pdf_url |
| } |
| else: |
| |
| link_text = link.get_text(strip=True) |
| filename = pdf_url.split("/")[-1] |
| |
| |
| date_match = re.search(r'on[_\s]?(\d{4})', filename) |
| time_match = re.search(r'at[_\s]?(\d{4})hrs', filename, re.IGNORECASE) |
| |
| return { |
| "title": link_text or filename.replace("_", " ").replace(".pdf", ""), |
| "date": date_match.group(1) if date_match else "", |
| "time": f"{time_match.group(1)} hrs" if time_match else "", |
| "pdf_url": pdf_url |
| } |
|
|
|
|
| def get_latest_sitrep() -> dict | None: |
| """ |
| Fetch the latest sitrep metadata from DMC website. |
| |
| Returns: |
| Dictionary with title, date, time, and pdf_url, or None if not found. |
| """ |
| reports = get_sitrep_list(limit=1) |
| return reports[0] if reports else None |
|
|
|
|
| def get_latest_two_sitreps() -> tuple[dict | None, dict | None]: |
| """ |
| Fetch the two most recent sitreps from DMC website. |
| |
| Returns: |
| Tuple of (latest, previous) report metadata, either can be None. |
| """ |
| reports = get_sitrep_list(limit=2) |
| latest = reports[0] if len(reports) > 0 else None |
| previous = reports[1] if len(reports) > 1 else None |
| return latest, previous |
|
|
|
|
| def download_pdf(url: str) -> bytes: |
| """ |
| Download PDF from URL and return bytes. |
| |
| Args: |
| url: URL of the PDF file |
| |
| Returns: |
| PDF file content as bytes |
| """ |
| response = requests.get(url, headers=HEADERS, timeout=60, verify=False) |
| response.raise_for_status() |
| return response.content |
|
|
|
|
| def get_landslide_report_list(limit: int = 5) -> list[dict]: |
| """ |
| Fetch a list of landslide warning report metadata from DMC website. |
| |
| Args: |
| limit: Maximum number of reports to return |
| |
| Returns: |
| List of dictionaries with title, date, time, and pdf_url, ordered newest first. |
| """ |
| try: |
| response = requests.get( |
| LANDSLIDE_PAGE_URL, |
| headers=HEADERS, |
| timeout=30, |
| verify=False |
| ) |
| response.raise_for_status() |
| except requests.RequestException as e: |
| print(f"Error fetching DMC landslide page: {e}") |
| return [] |
| |
| soup = BeautifulSoup(response.text, "html.parser") |
| |
| reports = [] |
| seen_urls = set() |
| |
| |
| for link in soup.find_all("a", href=True): |
| href = link.get("href", "") |
| text = link.get_text(strip=True).lower() |
| |
| |
| if ".pdf" in href.lower(): |
| is_landslide = ( |
| "landslide" in href.lower() or |
| "landslide" in text or |
| "ew_report" in href.lower() or |
| "early warning" in text |
| ) |
| if is_landslide: |
| |
| pdf_url = href |
| if not pdf_url.startswith("http"): |
| if pdf_url.startswith("/"): |
| pdf_url = f"{BASE_URL}{pdf_url}" |
| else: |
| pdf_url = f"{BASE_URL}/{pdf_url}" |
| |
| |
| if pdf_url in seen_urls: |
| continue |
| seen_urls.add(pdf_url) |
| |
| |
| report_info = _extract_report_metadata(link, pdf_url) |
| report_info["report_type"] = "landslide" |
| reports.append(report_info) |
| |
| return reports[:limit] |
|
|
|
|
| def get_latest_landslide_report() -> dict | None: |
| """ |
| Fetch the latest landslide warning report metadata from DMC website. |
| |
| Returns: |
| Dictionary with title, date, time, and pdf_url, or None if not found. |
| """ |
| reports = get_landslide_report_list(limit=1) |
| return reports[0] if reports else None |
|
|
|
|
| def get_flood_report_list(limit: int = 5) -> list[dict]: |
| """ |
| Fetch a list of flood/water level report metadata from DMC website. |
| |
| Args: |
| limit: Maximum number of reports to return |
| |
| Returns: |
| List of dictionaries with title, date, time, and pdf_url, ordered newest first. |
| """ |
| try: |
| response = requests.get( |
| FLOOD_PAGE_URL, |
| headers=HEADERS, |
| timeout=30, |
| verify=False |
| ) |
| response.raise_for_status() |
| except requests.RequestException as e: |
| print(f"Error fetching DMC flood page: {e}") |
| return [] |
| |
| soup = BeautifulSoup(response.text, "html.parser") |
| |
| reports = [] |
| seen_urls = set() |
| |
| |
| for link in soup.find_all("a", href=True): |
| href = link.get("href", "") |
| text = link.get_text(strip=True).lower() |
| |
| |
| if ".pdf" in href.lower(): |
| is_flood = ( |
| "water" in href.lower() or |
| "water" in text or |
| "rainfall" in href.lower() or |
| "rainfall" in text or |
| "flood" in href.lower() or |
| "flood" in text |
| ) |
| if is_flood: |
| |
| pdf_url = href |
| if not pdf_url.startswith("http"): |
| if pdf_url.startswith("/"): |
| pdf_url = f"{BASE_URL}{pdf_url}" |
| else: |
| pdf_url = f"{BASE_URL}/{pdf_url}" |
| |
| |
| if pdf_url in seen_urls: |
| continue |
| seen_urls.add(pdf_url) |
| |
| |
| report_info = _extract_report_metadata(link, pdf_url) |
| report_info["report_type"] = "flood" |
| reports.append(report_info) |
| |
| return reports[:limit] |
|
|
|
|
| def get_latest_flood_report() -> dict | None: |
| """ |
| Fetch the latest flood/water level report metadata from DMC website. |
| |
| Returns: |
| Dictionary with title, date, time, and pdf_url, or None if not found. |
| """ |
| reports = get_flood_report_list(limit=1) |
| return reports[0] if reports else None |
|
|
|
|
| |
| if __name__ == "__main__": |
| print("=" * 60) |
| print("Testing DMC Scraper") |
| print("=" * 60) |
| |
| print("\nFetching sitrep list...") |
| reports = get_sitrep_list(limit=5) |
| print(f"Found {len(reports)} situation reports:\n") |
| |
| for i, report in enumerate(reports): |
| print(f"{i+1}. {report['title'][:50]}...") |
| print(f" Date: {report['date']} | Time: {report['time']}") |
| print(f" URL: {report['pdf_url'][:70]}...") |
| print() |
| |
| print("=" * 60) |
| print("Testing get_latest_two_sitreps()") |
| print("=" * 60) |
| |
| latest, previous = get_latest_two_sitreps() |
| |
| if latest: |
| print(f"\nLatest report:") |
| print(f" Title: {latest['title']}") |
| print(f" Date: {latest['date']} {latest['time']}") |
| |
| print("\nDownloading latest PDF...") |
| pdf_bytes = download_pdf(latest['pdf_url']) |
| print(f" Downloaded {len(pdf_bytes):,} bytes") |
| |
| if previous: |
| print(f"\nPrevious report:") |
| print(f" Title: {previous['title']}") |
| print(f" Date: {previous['date']} {previous['time']}") |
| |
| print("\nDownloading previous PDF...") |
| pdf_bytes = download_pdf(previous['pdf_url']) |
| print(f" Downloaded {len(pdf_bytes):,} bytes") |
| else: |
| print("\nNo previous report found") |