Web of Science (webofscience.com) Export 다이얼로그를 Selenium 으로 조작할 때 selector 가 fail 하는 패턴을 진단하고 수정합니다.
언제 사용
- WoS 자동화 (Excel export, Full record 선택) 가 fail 또는 timeout
- "Record Content 드롭다운 버튼을 찾지 못함" /
[RADIO] 모든 N회 시도 실패로그 - nightly Selenium 잡이 매일 동일 패턴으로 실패 (4일 이상 연속)
- WoS UI 가 업데이트되어 기존 selector 가 안 통함
대상 코드: scripts/wos-collector/helper.py (Conv_home 프로젝트) 또는 동일 패턴을 쓰는 다른 WoS Selenium 자동화.
핵심 함정
1. Angular Material 동적 ID
WoS 가 export 다이얼로그를 mat-mdc-radio-button, mat-mdc-select 컴포넌트로 렌더링. Angular Material 은 컴포넌트마다 페이지 전역 카운터로 ID 를 할당 (#radio0, #radio1, ...). 다이얼로그를 닫고 다시 열면 카운터가 증가 → #radio3 이 두 번째 다이얼로그에서 #radio7 등으로 바뀜.
증상: 첫 batch 는 통과, batch 2 (다이얼로그 재오픈) 에서 fail.
fix: 텍스트 기반 selector 사용.
# bad
(By.CSS_SELECTOR, "#radio3")
# good
(By.XPATH, "//mat-radio-button[contains(., 'Records from')]")2. Smart vs Advanced Search 다이얼로그 wrapper 차이
같은 export 다이얼로그처럼 보이지만 wrapper element 가 페이지마다 다름:
- Advanced Search 결과 페이지:
mat-mdc-dialog-container또는role='dialog'wrapper - Smart Search 결과 페이지: 표준 Material wrapper 없음 (cdk-overlay-pane 만, 표준 dialog 클래스 없음)
dialog scope (//*[@role='dialog']//) 로 selector 좁히면 Smart Search 에서 fail.
fix: dialog scope 좁히지 말고 텍스트 노드 직후의 첫 트리거 element 매치.
# good (텍스트-트리거 거리가 짧으니 외부 element 끼어들기 어려움)
(By.XPATH, "//*[contains(normalize-space(.), 'Record Content')]/following::button[@aria-haspopup='listbox' or @aria-haspopup='menu' or @role='combobox'][1]")3. selector timeout 누적
EC.element_to_be_clickable 의 default 10초 × selector 7개 = 70초 wait. 모두 fail 시 wrapper 의 5분 timeout 도달 → SIGKILL → 다이얼로그 dirty 상태로 retry.
fix: per-selector timeout 3초 상한.
def _find_dropdown_button(driver, timeout=10):
per_timeout = min(timeout, 3) # 각 selector 최대 3초
for i, (sel_type, sel) in enumerate(_DROPDOWN_BUTTON_SELECTORS):
try:
btn = WebDriverWait(driver, per_timeout).until(EC.element_to_be_clickable((sel_type, sel)))
if btn: return btn
except Exception:
continue
return None진단 패턴: visible 후보 dump
selector 가 모두 fail 했을 때, 페이지의 visible listbox/select 후보를 JS 로 dump 해서 정확한 selector 작성에 활용:
candidates = driver.execute_script(
"""
const seen = [];
document.querySelectorAll('button[aria-haspopup], mat-select, [role=\\'combobox\\']').forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return; // invisible 제외
seen.push({
tag: el.tagName.toLowerCase(),
id: el.id || '',
cls: (el.className || '').toString().slice(0, 80),
text: (el.innerText || '').slice(0, 40),
aria: el.getAttribute('aria-haspopup') || '',
x: Math.round(rect.x), y: Math.round(rect.y),
});
});
return seen.slice(0, 8);
"""
) or []
for c in candidates:
print(f" <{c['tag']}> id={c['id']!r} aria={c['aria']!r} pos=({c['x']},{c['y']}) text={c['text']!r}")후보의 좌표(x, y) 로 다이얼로그 내부 element 식별 가능 (다이얼로그는 보통 중앙, 외부 element 는 사이드바).
라디오 버튼 클릭 5가지 방법
Angular Material radio 가 click 안 먹는 경우를 위한 fallback chain:
def click_radio_robust(driver, radio_element, max_retries=5):
"""텍스트 기반으로 찾은 mat-radio-button element 에 클릭 시도."""
for attempt in range(max_retries):
# 방법1: 내부 <label> 클릭 (표준 클릭 영역)
label = radio_element.find_element(By.XPATH, ".//label")
ActionChains(driver).move_to_element(label).pause(0.2).click().perform()
if is_selected(radio_element): return True
# 방법2: .mdc-radio 클릭
# 방법3: <input> 에 focus 후 Space 키
# 방법4: JS native input.click() — input style 임시로 visible 처리 후 click()
# 방법5: JS 강제 상태 변경 + Angular change event dispatch
# ... (각 방법 후 is_selected 확인, fail 시 다음 방법)핵심: mat-radio-button element 를 텍스트 기반으로 매번 새로 찾기 (DOM 재렌더링 대응).
Dry-run 검증 패턴
WoS 큐 처리 같은 시스템은 DB 갱신 없는 dry-run 모드를 제공해야 함:
# DB 갱신 없이 selector 작동만 검증
process-wos-lookup-queue.ts --dry-run --limit 7DB update 단계만 if (!dryRun) await prisma.xxx.update(...) 로 감싸면 됨.
chrome 좀비 cleanup
WoS 자동화는 .chrome-profile-wos-meta 같은 user_data_dir 을 공유. SIGKILL 또는 비정상 종료 시 chrome 프로세스 + Singleton 잠금 잔존 → 다음 실행 fail.
# 표준 cleanup 패턴
pkill -f "chrome-profile-wos-meta" 2>/dev/null && sleep 2
rm -f scripts/wos-collector/.chrome-profile-wos-meta/Singleton{Lock,Socket,Cookie}참조
- 실제 사고: 2026-05-13 Conv_home
#radio3하드코딩 4일 연속 fail. daily-uus 800건 + 큐 7건 모두 영향. 텍스트 기반 selector 로 fix (commitce1a23f+1cb2aaa). - 운영 문서:
docs/wos-lookup-queue.md(트러블슈팅 섹션) - 관련 스킬:
author-extractor-wos(저자 추출 시 WOS 데이터 활용)