기존 paper-pdf-downloader 가 Unpaywall/Crossref API 체인만 다루는 반면, 이 스킬은 undetected-chromedriver + 저널별 규칙 DB 로 paywall·Cloudflare 저널까지 처리한다. 성공한 규칙이 DB 에 누적되어 운영할수록 더 똑똑해진다.
언제 사용하나
- Unpaywall/Crossref API 로는 못 받는 paywall 저널 (Wiley, Elsevier, AJR, Radiology, AAN …)
- Cloudflare 방어가 있는 OA 저널 (MDPI, Frontiers 등 일부)
- 기관 EZ-Proxy 세션을 활용해야 하는 학회지
5가지 URL 변형 패턴 (v2)
| Pattern | 적용 | 예시 |
|---|---|---|
transforms (독립 시도) | 단순 {doi} 치환 | ["https://onlinelibrary.wiley.com/doi/pdfdirect/{doi}"] |
sequence (방문 순서) | 첫 URL 방문 → 두 번째 URL nav (referer/세션) | ["/doi/epdf/{doi}", "/doi/pdf/{doi}?download=true"] |
sequences (multi) | 여러 sequence 동시 시도 | [[seq1...], [seq2...]] |
{current_url_html_to_pdf} | 페이지 URL 의 /html → /pdf | AME 일부 |
{current_url_last_to_pdf} | 마지막 segment → /pdf | AME (/article/view/14623/12290 → /article/view/14623/pdf) |
6가지 method (priority 순)
| method | 적용 | 파라미터 |
|---|---|---|
unpaywall | OA 저널 자동 | (없음) |
url_transform | publisher 별 URL 패턴 (transforms / sequence / sequences) | 위 5가지 패턴 |
citation_pdf_url | Google Scholar 표준 meta 태그 (범용) | (없음) |
selector | CSS 셀렉터 + <button data-config> URL 추출 | {"selectors": ["button.js-ejp-login-btn"]} |
ez_proxy | 기관 세션 경유 | {"proxyUrl": "..."} |
browser_capture | 마지막 수단 (렌더 PDF) | — |
v2 새 기능
1) Cookie 자동 동의
OneTrust, Quantcast, 일반 "Accept all cookies" 버튼 자동 클릭. iframe 포함, JS click fallback.
2) Cloudflare 자동 통과 (human-like)
- "Just a moment..." challenge 페이지 자동 감지
- ActionChains 마우스 천천히 다단계 이동 + 스크롤 + 자연 대기
- 실패 시 logs/challenge-screenshots/ 에 스크린샷
- 35초 내 자동통과 실패 시 →
PdfHumanClickQueue자동 적재 후 즉시 다음 논문으로 (Conv_home 통합).
사용자가 한가할 때 scripts/human-click-session.py 로 일괄 처리 (사람 클릭 1번 → publisher 별 cf_clearance 쿠키 영구 발급 → 같은 publisher 후속 논문 자동 통과). 상세는 Conv_home docs/pdf-human-click-queue.md.
3) Button data-config 추출 (LWW)
<button data-config='{"eventDetail":{"url":"..."}}'> 형태에서 PDF URL 자동 추출.
4) PDF 내용 검증 (가짜양성 방지)
다운로드 후 제목 키워드 매칭. 매치 < 40% 이면 자동 롤백 + 파일 삭제.
- 단 'Letter to the editor', 'Correspondence', 'Corrigendum' 등은 통과
5) 좀비 Chrome 자동 정리
실행 시 pkill undetected_chromedriver + Singleton 파일 삭제.
6) 사람 수정 저자정보 보호
extract-authors 스크립트가 다음 중 하나라도 해당하면 무조건 skip:
- ExtractedAuthorInfo.source = 'manual'
- Activity 'paper.authorEdited' 로그 존재
검증된 Publisher 규칙 (2026-04-15 시드 기준)
# URL 변형이 통하는 저널들
wiley:
doiPrefix: 10.1002
method: url_transform
params: {"transforms": ["https://onlinelibrary.wiley.com/doi/pdfdirect/{doi}"]}
ajr_arrs:
doiPrefix: 10.2214
method: url_transform
params: {"transforms": ["https://www.ajronline.org/doi/pdf/{doi}"]}
mdpi:
doiPrefix: 10.3390
method: url_transform
params: {"transforms": ["{current_url}/pdf"]} # article_url → article_url/pdf
liebert_sage:
doiPrefix: 10.1089
method: url_transform
# 2023년 Liebert → SAGE 인수로 URL 변경됨
params: {"transforms": ["https://journals.sagepub.com/doi/pdf/{doi}"]}
# citation_pdf_url 이 통하는 OA 저널
plos: {doiPrefix: 10.1371, method: citation_pdf_url}
frontiers: {doiPrefix: 10.3389, method: citation_pdf_url}사용법 (프로젝트 통합)
전제: 전용 Chrome 프로파일 셋업 (1회)
python3 scripts/chrome-driver-pdf.py --setup
# Chrome 창이 뜨면 기관 EZ-Proxy 로그인 → 창 닫지 말고 터미널 엔터단일 DOI 다운로드 (DB 규칙 사용)
python3 scripts/chrome-driver-pdf.py \
--doi 10.1148/radiol.2018181197 \
--output /tmp/test.pdf \
--db-path prisma/prod.dbPublisher discovery (대표 1건 테스트)
# 미해결 publisher 목록
python3 scripts/discover-publisher.py --list
# 특정 prefix 테스트 (headful Chrome 창 뜸)
python3 scripts/discover-publisher.py --prefix 10.2214Publisher 일괄 재다운로드 (페이싱 + DOI dedupe 자동)
python3 scripts/batch-redownload-publisher.py --prefix 10.2214
# --dedupe-by-doi 기본 ON: 같은 DOI 는 1번만 다운, N 레코드에 공유
# 페이싱: 건 사이 15~35s 랜덤, 5건마다 60~120s 휴식저자 재추출 (DOI 캐시)
DATABASE_URL="file:./prisma/prod.db" \
npx tsx scripts/extract-authors-for-updated-papers.ts --skip-existingPublisher 규칙 추가하는 법
자동 (discovery 가 성공한 경우)
python3 scripts/discover-publisher.py --prefix 10.XXXX
# 성공 시 chrome-driver-pdf.py 가 successCount++ 해서 자동 학습수동 (새 URL 패턴 발견 시)
- 브라우저에서
https://doi.org/{sample DOI}열어 PDF 링크 확인 /admin/pdf-rules에서 "규칙 추가":publisherKey: 식별 키 (예:new_journal)doiPrefix: 해당 저널 DOI prefixdomainPattern: URL substring (선택)method:url_transformparams:{"transforms": ["발견한 패턴"]}priority: 1 (최우선)confirmedByHuman: ✅ (자동 재조정에서 제외)- 다시
discover-publisher.py --prefix로 재검증 - 성공하면
batch-redownload-publisher.py --prefix로 일괄 처리
페이싱 (차단 방지 — 필수)
| 구분 | 기본값 |
|---|---|
| 건 사이 | 15~35초 랜덤 |
| 5건 연속 후 | 60~120초 휴식 |
| Publisher 전환 | 60초+ |
| 차단 지표(429/captcha) 감지 | 즉시 중단 |
절대 --no-pace 쓰지 말 것. publisher 에 차단되면 해당 IP/세션이 최소 수시간 블록됨.
DOI 단위 dedupe (중요)
DB 에 같은 DOI 가 여러 Content 레코드(교수 n명 등록)로 존재하는 경우가 있다. --dedupe-by-doi (기본 ON) 이 작동하면:
- 다운로드: 1번만 수행 →
pdfFile경로를 모든 sibling 레코드에 공유 - 저자 추출: WOS/PubMed 쿼리도 1번만 →
ExtractedAuthorInfo만 각자 저장 - 결과: publisher 트래픽 ↓, 저장 공간 ↓, 차단 위험 ↓
실패 패턴과 대응
| 실패 원인 | 해결 |
|---|---|
| headless 에서 Access Denied | headful 로 실행 (기본값) |
| 403 / Cloudflare | 기관 EZ-Proxy 세션 필요, --setup 다시 |
| 가짜 양성 (엉뚱한 PDF) | selector 규칙 비활성, url_transform 으로 교체 |
| Publisher URL 변경 | 브라우저 직접 확인 → 어드민 UI 수정 |
| 신규 publisher | discovery CLI 실행 → 성공/수동 rule |
성공률 자동 관리
- 성공 시
successCount++, 실패 시failureCount++ - 주기적으로
/admin/pdf-rules의 "성공률 집계 + priority 재정렬" 클릭 confirmedByHuman=true규칙은 건드리지 않음 (수동 확정 보호)
제약 & 주의
- macOS: chrome-driver headful 은 GUI 세션 필요. PM2 서비스(프로덕션 서버) 에서 직접 실행 불가.
- 자동화 한계: cron 으로 돌리려면 사용자가 로그인된 GUI 세션 안에서 실행해야 함.
- 검증 권장: 다운로드 성공 후
pdftotext -l 1 file.pdf로 첫 페이지 텍스트 확인. - 차단 지표: 429/blocked/captcha 감지 시 즉시
break.
관련 스킬·문서
paper-pdf-downloader(API-only 구 스킬, 폴백 1단계)paper-author-extractor-wos/paper-author-extractor-pubmed(저자 추출)- 프로젝트 내:
docs/pdf-download-pipeline.md,docs/pdf-download-learning-workflow.md
설계 철학
- 저널 = 방법 1:1 — 같은 저널 내 여러 논문마다 발견할 필요 없음
- 누적 학습 — 한 번 성공한 규칙이 DB 에 쌓여 다음에 자동 재사용
- DOI 단위 dedupe — publisher 에 부담 최소, 저장·시간 절약
- 페이싱 필수 — 속도보다 안정성. 차단되면 복구 어려움
- 수동·자동 혼합 — 인간 확정(
confirmedByHuman) 과 자동 재조정이 공존