fix(edu): read diary event times directly from DOM, drop 25 AJAX clicks
ci / lint-prettier (push) Successful in 9s
ci / lint-ruff (push) Successful in 6s
ci / lint-yaml (push) Successful in 10s
ci / lint-dockerfiles (push) Successful in 7s
ci / validate (push) Successful in 6s
ci / build (push) Has been skipped
ci / deploy-userbot-panel (push) Has been skipped
ci / lint-prettier (push) Successful in 9s
ci / lint-ruff (push) Successful in 6s
ci / lint-yaml (push) Successful in 10s
ci / lint-dockerfiles (push) Successful in 7s
ci / validate (push) Successful in 6s
ci / build (push) Has been skipped
ci / deploy-userbot-panel (push) Has been skipped
_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.
This commit is contained in:
@@ -555,69 +555,40 @@ def _parse_calendar_html(table_html: str) -> tuple:
|
|||||||
|
|
||||||
|
|
||||||
async def _collect_event_times(page) -> dict:
|
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
|
The diary page embeds `div.event-full-info[data-event-full-info-id]`
|
||||||
(`div.event-full-info span.data`). Returns {event_id: 'HH:MM'} for events
|
containing `span.data` (e.g. "2026-09-02 16:30:00") for every event, so no
|
||||||
that have a date; events without one are simply skipped. A failed click on a
|
AJAX popup clicks are needed. Returns {event_id: 'HH:MM'} for events that
|
||||||
single event never breaks the whole diary parse.
|
have a date; events without one are simply skipped.
|
||||||
"""
|
"""
|
||||||
times_by_id: dict[str, str] = {}
|
times_by_id: dict[str, str] = {}
|
||||||
try:
|
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:
|
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
|
return times_by_id
|
||||||
|
|
||||||
for link in links:
|
for event_id, time_text in (raw_times or {}).items():
|
||||||
event_id = None
|
if not time_text:
|
||||||
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
|
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)
|
m = re.search(r'(\d{1,2}:\d{2})', time_text)
|
||||||
if m:
|
if m:
|
||||||
times_by_id[event_id] = m.group(1)
|
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)
|
|
||||||
|
|
||||||
logger.info(f'Diary event times collected for {len(times_by_id)} events')
|
logger.info(f'Diary event times collected for {len(times_by_id)} events')
|
||||||
return times_by_id
|
return times_by_id
|
||||||
|
|||||||
Reference in New Issue
Block a user