From 68630eb77350fa25938a9257d7de57c8a39192ca Mon Sep 17 00:00:00 2001 From: mr-forust Date: Thu, 10 Sep 2026 00:31:04 +0200 Subject: [PATCH] fix(edu): read diary event times directly from DOM, drop 25 AJAX clicks _collect_event_times re-clicked every a.event-link and waited ~3s per event for a visible span.data, but the calendar embeds all times in div.event-full-info[data-event-full-info-id] span.data already. The old loop took ~109s for 25 events and collected 0 (original divs stay sf-hidden), effectively hanging /diary. Now a single evaluate reads all times (~3.7s), parsing HH:MM from p.date span.data. --- edu_master/webinar-checker/checker.py | 83 +++++++++------------------ 1 file changed, 27 insertions(+), 56 deletions(-) diff --git a/edu_master/webinar-checker/checker.py b/edu_master/webinar-checker/checker.py index 80fbddc..dd2db88 100644 --- a/edu_master/webinar-checker/checker.py +++ b/edu_master/webinar-checker/checker.py @@ -555,69 +555,40 @@ def _parse_calendar_html(table_html: str) -> tuple: async def _collect_event_times(page) -> dict: - """Click every calendar `a.event-link` and read the time from the AJAX popup. + """Read event times straight from the rendered calendar DOM. - The site shows event details only after a click fills the fancybox container - (`div.event-full-info span.data`). Returns {event_id: 'HH:MM'} for events - that have a date; events without one are simply skipped. A failed click on a - single event never breaks the whole diary parse. + The diary page embeds `div.event-full-info[data-event-full-info-id]` + containing `span.data` (e.g. "2026-09-02 16:30:00") for every event, so no + AJAX popup clicks are needed. Returns {event_id: 'HH:MM'} for events that + have a date; events without one are simply skipped. """ times_by_id: dict[str, str] = {} try: - links = await page.query_selector_all('a.event-link') + raw_times = await page.evaluate( + """() => { + const out = {}; + for (const div of document.querySelectorAll( + 'div.event-full-info[data-event-full-info-id]' + )) { + const id = div.getAttribute('data-event-full-info-id'); + const date_span = div.querySelector('p.date span.data'); + if (id && date_span) { + out[id] = date_span.textContent.trim(); + } + } + return out; + }""" + ) except Exception as e: - logger.warning(f'Failed to list diary event links: {e}') + logger.warning(f'Failed to read diary event times from DOM: {e}') return times_by_id - for link in links: - event_id = None - event_deadline = time.monotonic() + 10 - try: - event_id = await link.get_attribute('data-event-id') - if not event_id or event_id in times_by_id: - continue - await link.evaluate('(el) => el.click()') - await page.wait_for_selector( - f'div.event-full-info[data-event-full-info-id="{event_id}"] span.data', - timeout=3000, - state='visible', - ) - if time.monotonic() > event_deadline: - break - time_text = await page.evaluate( - """(id) => { - const nodes = document.querySelectorAll( - 'div.event-full-info[data-event-full-info-id="' + id + '"] span.data' - ); - for (const el of nodes) { - if (el.getClientRects().length > 0 || el.offsetParent !== null) { - return el.textContent.trim(); - } - } - return null; - }""", - event_id, - ) - if time_text: - m = re.search(r'(\d{1,2}:\d{2})', time_text) - if m: - times_by_id[event_id] = m.group(1) - except Exception as e: - logger.debug(f'Failed to read time for diary event {event_id}: {e}') - if time.monotonic() > event_deadline: - break - finally: - # Close the fancybox popup (click the close button, else Escape). - closed = False - with contextlib.suppress(Exception): - close_btn = await page.query_selector('#fancybox-close') - if close_btn is not None: - await close_btn.click(timeout=2000) - closed = True - if not closed: - with contextlib.suppress(Exception): - await page.keyboard.press('Escape') - await page.wait_for_timeout(150) + for event_id, time_text in (raw_times or {}).items(): + if not time_text: + continue + m = re.search(r'(\d{1,2}:\d{2})', time_text) + if m: + times_by_id[event_id] = m.group(1) logger.info(f'Diary event times collected for {len(times_by_id)} events') return times_by_id