Files
pomos/crawler_bestattung_wels_enhanced.py
T
2025-02-22 18:00:16 +01:00

187 lines
7.6 KiB
Python

import requests
from bs4 import BeautifulSoup
import re
import yaml
from sqlalchemy import create_engine, Column, Integer, String, Date, LargeBinary, UniqueConstraint
from sqlalchemy.orm import declarative_base, sessionmaker
from datetime import datetime
import sys
import time
import random
BASE_URL = "https://www.bestattung-wels.at/"
URL_TEMPLATE = f"{BASE_URL}/current-deaths?page={{}}&deathCompany={{}}"
URL_TEMPLATE_NO_COMPANY = f"{BASE_URL}/current-deaths?page={{}}"
# Load database credentials from config.yaml
with open("config.yaml", "r") as file:
config = yaml.safe_load(file)
db_config = config['database']
DATABASE_URL = f"postgresql://{db_config['username']}:{db_config['password']}@{db_config['host']}:{db_config['port']}/{db_config['dbname']}"
# Define the SQLAlchemy base and model
Base = declarative_base()
class DeathNotice(Base):
__tablename__ = 'death_notices'
__table_args__ = (
UniqueConstraint('dn_death', 'dn_name', 'dn_location', name='unique_death_notice'),
{'schema': 'pomos'}
)
id = Column(Integer, primary_key=True, autoincrement=True)
dn_death = Column(Date)
dn_name = Column(String)
dn_location = Column(String)
dn_picture = Column(LargeBinary)
dn_age = Column(Integer) # New column for age
# Create the SQLAlchemy engine and session
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)
# Create the schema and tables
Base.metadata.create_all(engine)
# List of User-Agent strings to rotate
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:54.0) Gecko/20100101 Firefox/54.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/16.16299",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
]
class WelsCrawler:
def __init__(self, death_company):
self.death_company = death_company
def get_page_content(self, url):
headers = {
"User-Agent": random.choice(USER_AGENTS)
}
response = requests.get(url, headers=headers)
return BeautifulSoup(response.content, "html.parser")
def download_picture(self, url):
headers = {
"User-Agent": random.choice(USER_AGENTS)
}
response = requests.get(url, headers=headers)
return response.content if response.status_code == 200 else None
def extract_data(self, soup, session):
death_table = soup.find("table", attrs={"class": "death-table"})
if not death_table:
print("No death table found.")
return False
death_table_data = death_table.find_all("tr")
for row in death_table_data:
rows = row.find_all('td')
cols = [ele.text.strip() for ele in rows]
if cols:
date_death_str = cols[1].replace('†', '').strip() if cols[1] else None
try:
date_death = datetime.strptime(date_death_str, "%d.%m.%Y").date() if date_death_str else None
except ValueError:
print(f"Invalid date format: {date_death_str}")
date_death = None
continue
# full-name is available as well
name = row.find('span', class_='full-name').text.strip()
# name = cols[0]
location = self.death_company.capitalize()
# Extract the picture URL
picture_tag = row.find('img', class_='profile-picture')
picture_url = picture_tag['src'] if picture_tag else None
picture_data = self.download_picture(picture_url) if picture_url else None
# Extract the age
age_str = row.find('span', class_='deceased-age').text.strip() if row.find('span', class_='deceased-age') else None
age_match = re.search(r'(\d+)', age_str) if age_str else None
age = int(age_match.group(1)) if age_match else None
# Check for duplicates before inserting
#existing_notice = session.query(DeathNotice).filter_by(dn_death=date_death, dn_name=name, dn_location=location).first()
existing_notice = session.query(DeathNotice).filter_by(dn_death=date_death, dn_name=name).first()
if existing_notice:
# update picture and age
existing_notice.dn_picture = picture_data
existing_notice.dn_age = age
existing_notice.dn_name = name
print(f"Duplicate entry found - Date: {date_death}, Name: {name}, Location: {location}")
else:
# Insert data into the database using SQLAlchemy
death_notice = DeathNotice(dn_death=date_death, dn_name=name, dn_location=location, dn_picture=picture_data, dn_age=age)
session.add(death_notice)
print(f"Inserted data - Date: {date_death}, Name: {name}, Location: {location}, Age: {age}, Picture URL: {picture_url}")
return True
def find_next_page(self, soup, current_url):
next_button_disabled = soup.find("a", class_="pagination__next-btn pagination__next-btn--disabled")
if next_button_disabled:
return None
# next_button = soup.find("a", class_="pagination__next-btn")
# if next_button:
match = re.search(r'\?page=(\d+)', current_url) #next_button['href'])
if match:
next_page_num = int(match.group(1)) + 1
if self.death_company == "unbekannt":
next_page_url = URL_TEMPLATE_NO_COMPANY.format(next_page_num)
else:
next_page_url = URL_TEMPLATE.format(next_page_num, self.death_company)
return next_page_url
return None
class Worker:
def __init__(self, crawler, start_page=1):
self.crawler = crawler
self.start_page = start_page
def run(self):
# Create a new session
session = SessionLocal()
try:
if self.crawler.death_company == "unbekannt":
current_url = URL_TEMPLATE_NO_COMPANY.format(self.start_page)
else:
current_url = URL_TEMPLATE.format(self.start_page, self.crawler.death_company)
while current_url:
soup = self.crawler.get_page_content(current_url)
if not self.crawler.extract_data(soup, session):
break
current_url = self.crawler.find_next_page(soup, current_url)
if not current_url:
break
# Random delay between requests
time.sleep(random.uniform(1, 5))
# Commit the transaction after processing all rows on the page
session.commit()
except Exception as e:
print(f"An error occurred: {e}")
session.rollback()
finally:
# Close the session
session.close()
if __name__ == "__main__":
death_company = sys.argv[1] if len(sys.argv) > 1 else "unbekannt"
start_page = int(sys.argv[2]) if len(sys.argv) > 2 else 1
if death_company not in ["wels", "marchtrenk", "unbekannt"]:
print("Invalid death company. Use 'wels' or 'marchtrenk'.")
sys.exit(1)
crawler = WelsCrawler(death_company)
worker = Worker(crawler, start_page)
worker.run()