Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion data/feeds.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
"name": "University of Macedonia",
"type": "scraper",
"scraper": "uom_news"
},
{
"name": "University of Western Macedonia",
"type": "scraper",
"scraper": "uowm_news"
}
]
]
144 changes: 144 additions & 0 deletions data/university_lists/uowm_sources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
{
"university": "University of Western Macedonia",
"default_max_pages": 3,
"sources": [
{
"label": "UoWM Main News",
"base_url": "https://www.uowm.gr",
"first_page": "https://www.uowm.gr/en/news/",
"item_selector": ".post-list-item",
"title_selector": "h4.post-title a, .post-title a",
"date_selector": ".post-date span, .post-date",
"summary_selector": ".post-item-excerpt"
},
{
"label": "UoWM Electrical and Computer Engineering",
"base_url": "https://ece.uowm.gr",
"first_page": "https://ece.uowm.gr/%ce%b3%ce%b5%ce%bd%ce%b9%ce%ba%ce%b5%cf%83-%ce%b1%ce%bd%ce%b1%ce%ba%ce%bf%ce%b9%ce%bd%cf%89%cf%83%ce%b5%ce%b9%cf%83/",
"item_selector": ".rt-list-item",
"title_selector": "h3.entry-title a.tpg-post-link, .entry-title a",
"date_selector": ".post-meta-tags .date a, .date",
"summary_selector": ".tpg-excerpt-inner"
},
{
"label": "UoWM Chemical Engineering",
"base_url": "https://chemeng.uowm.gr",
"first_page": "https://chemeng.uowm.gr/enimerosi/",
"item_selector": "article.type-post, article.post",
"title_selector": "h3 a, h2 a, .entry-title a",
"date_selector": ".date, .entry-date, time, .posted-on",
"summary_selector": ".entry-summary, .entry-content, p"
},
{
"label": "UoWM Mineral Resources Engineering",
"base_url": "https://mre.uowm.gr",
"first_page": "https://mre.uowm.gr/en/category/announcements/",
"item_selector": ".post.type-post, .type-post",
"title_selector": "h2 a, .entry-title a",
"date_selector": ".entry-meta, .entry-date, time",
"summary_selector": ".entry-summary, .entry-content"
},
{
"label": "UoWM Product and Systems Design Engineering - First Years",
"base_url": "https://ide.uowm.gr",
"first_page": "https://ide.uowm.gr/category/protoeteis_25/",
"item_selector": "article.type-post, article.post",
"title_selector": "h2 a, .entry-title a",
"date_selector": "time, .entry-date, .posted-on",
"summary_selector": ".entry-summary, .entry-content"
},
{
"label": "UoWM Product and Systems Design Engineering - News",
"base_url": "https://ide.uowm.gr",
"first_page": "https://ide.uowm.gr/en/category/news/",
"item_selector": "article.type-post, article.post",
"title_selector": "h2 a, .entry-title a",
"date_selector": "time, .entry-date, .posted-on",
"summary_selector": ".entry-summary, .entry-content"
},
{
"label": "UoWM Early Childhood Education",
"base_url": "https://nured.uowm.gr",
"first_page": "https://nured.uowm.gr/news/",
"item_selector": "article.type-post, article.post",
"title_selector": "h2 a, .entry-title a",
"date_selector": "time, .entry-date, .posted-on",
"summary_selector": ".entry-summary, .entry-content"
},
{
"label": "UoWM Computer Science",
"base_url": "https://cs.uowm.gr",
"first_page": "https://cs.uowm.gr/en/home-page/",
"item_selector": ".latest-news-item",
"title_selector": ".latest-news-title a, h3 a",
"date_selector": ".latest-news-date, time, .posted-on",
"summary_selector": ".latest-news-text-content p, .latest-news-excerpt",
"max_pages": 1
},
{
"label": "UoWM Agriculture",
"base_url": "https://agro.uowm.gr",
"first_page": "https://agro.uowm.gr/home/",
"item_selector": ".uael-post-wrapper",
"title_selector": ".uael-post__title a, h5 a",
"date_selector": ".uael-post__date, .uael-post__meta-data, time",
"summary_selector": ".uael-post__excerpt, .uael-post__content-wrap",
"max_pages": 1
},
{
"label": "UoWM Regional and Cross-Border Development",
"base_url": "https://rdcbs.uowm.gr",
"first_page": "https://rdcbs.uowm.gr/nea/",
"item_selector": "article.post-list-item, .post-list-item",
"title_selector": "h3 a, .post-title a",
"date_selector": ".post-date, time",
"summary_selector": ".post-excerpt, .entry-summary, p"
},
{
"label": "UoWM International and European Economic Studies",
"base_url": "https://iees.uowm.gr",
"first_page": "https://iees.uowm.gr/category/anakoinoseis/",
"item_selector": ".post.type-post, .type-post",
"title_selector": "h2 a, .entry-title a",
"date_selector": ".entry-meta, .entry-date, time",
"summary_selector": ".entry-summary, .entry-content"
},
{
"label": "UoWM Business Administration",
"base_url": "https://ba.uowm.gr",
"first_page": "https://ba.uowm.gr/category/anakoinoseis/",
"item_selector": "article.type-post, article.post",
"title_selector": "h3 a, h2 a, .entry-title a",
"date_selector": ".post-date, .entry-date, time",
"summary_selector": ".entry-summary, .entry-content, p"
},
{
"label": "UoWM Applied and Fine Arts",
"base_url": "https://eetf.uowm.gr",
"first_page": "https://eetf.uowm.gr/en/category/news/",
"item_selector": ".news-mini-wrap.type-post, .news-mini-wrap",
"title_selector": "h1 a, h2 a, .entry-title a",
"date_selector": "time, .news-meta",
"summary_selector": ".news-summary, .entry-summary, .entry-content"
},
{
"label": "UoWM Occupational Therapy",
"base_url": "https://ot.uowm.gr",
"first_page": "https://ot.uowm.gr/",
"item_selector": "#left-sidebar .widget_text h3",
"title_selector": "a",
"date_selector": "",
"summary_selector": "",
"max_pages": 1
},
{
"label": "UoWM Midwifery",
"base_url": "https://mw.uowm.gr",
"first_page": "https://mw.uowm.gr/category/genikes-anakoinoseis/",
"item_selector": "article.type-post, article.post",
"title_selector": "h2 a, .entry-title a",
"date_selector": "time, .entry-date, .posted-on",
"summary_selector": ".entry-summary, .entry-content"
}
]
}
47 changes: 47 additions & 0 deletions feed_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import json

from settings import FEEDS_FILE


def load_builtin_feeds() -> list[dict]:
if not FEEDS_FILE.exists():
return []

with open(FEEDS_FILE, "r", encoding="utf-8") as file:
return json.load(file)


def get_all_feeds(app_settings: dict) -> list[dict]:
builtin_feeds = load_builtin_feeds()
custom_feeds = app_settings.get("custom_rss_feeds", [])

feeds = []
seen = set()

for feed in builtin_feeds + custom_feeds:
source_type = feed.get("type", "rss")

if source_type == "rss":
name = feed.get("name", "").strip()
url = feed.get("url", "").strip()

if not name or not url:
continue

key = ("rss", url.lower())
else:
name = feed.get("name", "").strip()
scraper = feed.get("scraper", "").strip()

if not name or not scraper:
continue

key = ("scraper", scraper.lower())

if key in seen:
continue

seen.add(key)
feeds.append(feed)

return feeds
5 changes: 3 additions & 2 deletions scrapers/scraper_service.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from scrapers.uom_scraper import UomNewsScraper

from scrapers.uowm_scraper import UowmNewsScraper

SCRAPER_REGISTRY = {
"uom_news": UomNewsScraper,
"uowm_news": UowmNewsScraper,
}


Expand All @@ -13,4 +14,4 @@ def run_scraper(scraper_id: str) -> list[dict]:
raise ValueError(f"Unknown scraper: {scraper_id}")

scraper = scraper_class()
return scraper.scrape()
return scraper.scrape()
44 changes: 44 additions & 0 deletions scrapers/university_source_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import json
from pathlib import Path

from settings import DATA_DIR

UNIVERSITY_LISTS_DIR = DATA_DIR / "university_lists"


def load_university_source_config(file_name: str) -> dict:
config_path = UNIVERSITY_LISTS_DIR / file_name

if not config_path.exists():
raise FileNotFoundError(f"Missing university source list: {config_path}")

with open(config_path, "r", encoding="utf-8") as file:
config = json.load(file)

validate_university_source_config(config, config_path)

return config


def validate_university_source_config(config: dict, config_path: Path) -> None:
if "university" not in config:
raise ValueError(f"{config_path} is missing 'university'")

if "sources" not in config:
raise ValueError(f"{config_path} is missing 'sources'")

if not isinstance(config["sources"], list):
raise ValueError(f"{config_path}: 'sources' must be a list")

required_fields = [
"label",
"base_url",
"first_page",
"item_selector",
"title_selector",
]

for index, source in enumerate(config["sources"], start=1):
for field in required_fields:
if not source.get(field):
raise ValueError(f"{config_path}: source #{index} is missing '{field}'")
Loading
Loading