srilanka / src /scraper.py
jbbove's picture
Added flood tab with live alerts. Updated landlsides tab to show divisions and unosat layer of landslides.
5285dcf
Raw
History Blame Contribute Delete
12.1 kB
"""
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
# Disable SSL warnings for DMC website (has certificate issues)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# DMC Website URLs
BASE_URL = "https://www.dmc.gov.lk"
# Correct URL for sitreps (from README)
SITREP_PAGE_URL = (
"https://www.dmc.gov.lk/index.php?"
"option=com_dmcreports&view=reports&Itemid=273&report_type_id=1&lang=en"
)
# URL for landslide early warning reports
LANDSLIDE_PAGE_URL = (
"https://www.dmc.gov.lk/index.php?"
"option=com_dmcreports&view=reports&Itemid=276&report_type_id=5&lang=en"
)
# URL for flood/water level reports
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()
# Find all PDF links related to situation reports
for link in soup.find_all("a", href=True):
href = link.get("href", "")
text = link.get_text(strip=True).lower()
# Check if it's a PDF link related to situation reports
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:
# Make URL absolute if relative
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}"
# Skip duplicates
if pdf_url in seen_urls:
continue
seen_urls.add(pdf_url)
# Extract metadata
report_info = _extract_report_metadata(link, pdf_url)
reports.append(report_info)
# Sort by date (newest first) - reports are usually listed newest first on the page
# but we'll keep the order as-is since the page already orders them
return reports[:limit]
def _extract_report_metadata(link, pdf_url: str) -> dict:
"""Extract metadata from a report link."""
# Try to extract metadata from table row
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 first cell is a number (row index), shift
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:
# Extract info from link text or filename
link_text = link.get_text(strip=True)
filename = pdf_url.split("/")[-1]
# Try to extract date from filename
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()
# Find all PDF links related to landslide reports
for link in soup.find_all("a", href=True):
href = link.get("href", "")
text = link.get_text(strip=True).lower()
# Check if it's a PDF link related to landslide warnings
if ".pdf" in href.lower():
is_landslide = (
"landslide" in href.lower() or
"landslide" in text or
"ew_report" in href.lower() or # Early Warning Report
"early warning" in text
)
if is_landslide:
# Make URL absolute if relative
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}"
# Skip duplicates
if pdf_url in seen_urls:
continue
seen_urls.add(pdf_url)
# Extract metadata
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()
# Find all PDF links related to water level/rainfall reports
for link in soup.find_all("a", href=True):
href = link.get("href", "")
text = link.get_text(strip=True).lower()
# Check if it's a PDF link related to water level/flood reports
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:
# Make URL absolute if relative
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}"
# Skip duplicates
if pdf_url in seen_urls:
continue
seen_urls.add(pdf_url)
# Extract metadata
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
# CLI usage for testing
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")