왓챠피디아 데이터 추출 및 레터박스 옮기기 (최신)
2026.03.30
원본 코드:
https://github.com/erinyskim/watchapedia-export (kimdonhyun/erinyskim)
GitHub - erinyskim/watchapedia-export
Contribute to erinyskim/watchapedia-export development by creating an account on GitHub.
github.com
현재 코드가 변경되어 실행 불가합니다. 저는 야매로 수정해서 썼습니다.
변경 사항:
- 헤더: x-watcha-* → x-frograms-*
- 엔드포인트 경로: /api/users/{id}/ratings/movie (기존: /contents/movies/ratings)
- (26.04.26. 패치) 재시도 로직 수정: 평가 개수가 많을수록 시간이 오래 걸릴 수 있습니다. 콘솔에 "429 발생, 10초 후 재시도..." 메시지가 떠도 정상 진행 중이니 창을 닫지 말고 기다려 주세요.
수정 코드:
!async function(){
const userId = document.querySelector('a[href*="/users/"]').href.replace(/^.*\/users\/(.*)$/, "$1"),
deviceId = document.cookie.split('; ').find(r => r.startsWith('_c_pdi='))?.split('=')[1] || '',
sleep = ms => new Promise(res => setTimeout(res, ms)),
request = async (url) => {
while (true) {
await sleep(500);
const res = await fetch(`https://pedia.watcha.com${url}`, {
credentials: "same-origin",
headers: {
"accept": "application/vnd.frograms+json;version=2.1.0",
"x-frograms-app-code": "Galaxy",
"x-frograms-client": "Galaxy-Web-App",
"x-frograms-client-version": "2.1.0",
"x-frograms-version": "2.1.0",
"x-frograms-device-identifier": deviceId,
"x-frograms-galaxy-language": "ko",
"x-frograms-galaxy-region": "KR"
}
});
if (res.status === 429) {
console.log(`429 발생, 10초 후 재시도...`);
await sleep(10000);
continue;
}
const json = await res.json();
if (!json.result) {
console.log(`result 없음, 5초 후 재시도...`, json);
await sleep(5000);
continue;
}
return json;
}
},
fetchAll = async (url, onProgress) => {
const items = [];
let next = url;
while (next) {
const json = await request(next);
items.push(...json.result.result);
onProgress && onProgress(json.result.result.length);
next = json.result.next_uri;
}
return items;
},
merge = (ratings, comments, type) => ratings.map(e => {
const c = comments.find(c => c.content_code === e.content.code),
code = e.content.code,
watchedAt = (() => {
const d = new Date(c?.watched_at || e.user_content_action.watched_at);
return isNaN(d) ? '' : `${d.getFullYear()}-${`0${d.getMonth()+1}`.slice(-2)}-${`0${d.getDate()}`.slice(-2)}`;
})(),
directors = (e.content.director_names || c?.content?.director_names || []).join(),
review = c?.text?.trim().replace(/"/g, '""') || '';
return [
code,
`https://pedia.watcha.com/ko-KR/contents/${code}`,
e.content.title.replace(/"/g, ""),
type,
e.content.year,
directors,
watchedAt,
+e.user_content_action.rating / 2,
review,
c?.spoiler ?? ''
];
});
await (async () => {
const bar = Object.assign(document.createElement("div"), { style: "position:fixed;left:0;top:0;right:0;height:59px;z-index:60;display:flex;align-items:center;background-color:#fff" }),
inner = Object.assign(document.createElement("div"), { style: "max-width:1320px;width:100%;margin:0 auto;padding:0 32px;" }),
msg = Object.assign(document.createElement("p"), { style: "color:#7e7e7e;font-size:15px;letter-spacing:-0.3px;" }),
pct = Object.assign(document.createElement("span"), { style: "font-weight:bold" });
msg.innerText = "리뷰 다운로드 중입니다. ";
msg.appendChild(pct);
inner.appendChild(msg);
bar.appendChild(inner);
document.body.appendChild(bar);
try {
const totalCount = (await request(`/api/users/${userId}`)).result?.ratings_count || 0,
counted = { n: 0 },
progress = (n) => { counted.n += n; pct.innerText = totalCount ? `${(counted.n / totalCount * 100).toFixed(0)}%` : ''; },
[mRatings, mComments, tRatings, tComments] = await Promise.all([
fetchAll(`/api/users/${userId}/contents/movies/ratings`, progress),
fetchAll(`/api/users/${userId}/contents/movies/comments`, progress),
fetchAll(`/api/users/${userId}/contents/tv_seasons/ratings`, progress),
fetchAll(`/api/users/${userId}/contents/tv_seasons/comments`, progress),
]),
rows = [
...merge(mRatings, mComments, "MOVIE"),
...merge(tRatings, tComments, "TV")
],
csv = `ID,URL,Title,Type,Year,Directors,WatchedAt,Rating,Review,Spoiler,\n` + rows.map(r => `"${r.join('","')}"`).join("\n"),
blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8;" }),
link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${userId}-watcha.csv`;
link.click();
} catch(err) { console.error(err) }
bar.remove();
})()
}();
사용 방법:
- 크롬에서 왓챠피디아에 로그인 (주소 최하단 ko-KR 환경)
- F12 혹은 ctrl+shift+i 로 개발자 도구 열기
- console 열고 위 코드 복붙 후 엔터 (*콘솔에서 최초 복붙 시 복붙 허용 커맨더 입력할 것)
- letterboxd에서 csv 파일 import
- https://letterboxd.com/import/
- hide successful match 토글을 눌러 등록되지 않은 데이터를 확인할 수 있음.
- 감독판 등 부가 정보가 포함되거나 한국어 제목이 등록되지 않은 경우: 원제를 검색해서 match해 주세요.
- TV의 경우, letterboxd에서는 리미티드 시리즈만 취급하므로 없을 가능성이 높음.
- 최하단에서 import films까지 해야 등록됩니다.