From 9645dbca8d78d379b3e45d5acdbd14f3e8f40e70 Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Fri, 10 Jul 2026 23:19:22 +0200 Subject: [PATCH] Add PostFinance to job scout --- job_scout/scout.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/job_scout/scout.py b/job_scout/scout.py index 7d075ff..4f453a3 100644 --- a/job_scout/scout.py +++ b/job_scout/scout.py @@ -313,6 +313,21 @@ COMPANIES = [ # (IT/data + energy-trading, incl. the flagged Energiehandel roles). German/generic # titles, so _score_floor keeps the pre-filtered set visible. ("bkw", "BKW (Bern)", "bkw", {"_score_floor": 2}), + # PostFinance (Bern). The careers site renders a small, client-side paginated board; + # scrape all pages through its stable next-page control. No title filter: the board is + # low-volume, and the scorer keeps unrelated banking/customer-service roles out of the + # default report while retaining locally relevant engineering variants. + ("postfinance", "PostFinance (Bern)", "playwright", { + "url": "https://jobs.postfinance.ch/PostFinance/search?locale=de_DE", + "wait_for": "a[href*='/PostFinance/job/']", + "card": "a[href*='/PostFinance/job/']", + "title_attr": "text", + "link_attr": "href", + "default_location": "Switzerland", + "use_inner_text_as_blob": True, + "next_button": "#pfch-pagination-next", + "max_pages": 10, + }), ] # Companies where adapter probing did not yield a reliable scrape. Reasons noted. @@ -922,7 +937,27 @@ def fetch_playwright(args): if p > 0 and added == 0: break else: - scrape_current() + # Optional in-page pagination for client-side job boards. This is distinct from + # query-param pagination above: the URL does not change when moving between pages. + next_button = args.get("next_button") + if next_button: + for _ in range(args.get("max_pages", 8)): + added = scrape_current() + button = page.locator(next_button) + if button.count() != 1: + break + try: + if not button.is_visible() or button.is_disabled(): + break + button.click() + page.wait_for_timeout(args.get("next_wait_ms", 800)) + except Exception: + break + # A page with no unseen cards signals a loop or exhausted pagination. + if added == 0: + break + else: + scrape_current() finally: ctx.close()