(TypeScript 공부 중)
어제 못다한 에러 해결과 “asycn/await fetch()로 문서 요소를 동적으로 추가하는 것이 완료되기 전에 document.querySelector 등이 호출되어 버리는 문제”에 대한 조사를 마쳤다.
어제 만든 ‘포켓몬 간단 도감을 크롤링 하기’ 코드를 타입스크립트로 변환해보았다.
나중에 조사해 볼 키워드:
named exports와 default exports
에러 TimeoutError: Waiting failed: 50000ms exceeded ✔️
에러 TimeoutError: Waiting failed: 50000ms exceeded ✔️
(어제에 이어서)
상황:
await page.waitForFunction(() => {
return document.querySelector(".card:last-child .card--id").textContent ===
"#100"
},
{ timeout: 50000 }
)
원인: 위의 코드에서 페이지가 다 로드됐는데도 불구하고 저 .card:last-child를 못 찾아서 타임아웃 에러가 나고 있었다.
다음과 같이하면 headless 브라우저(크로미움)의 console.log를 node 터미널에 나타낼 수 있다: (참고: https://github.com/puppeteer/puppeteer/issues/1944)
// 07_pokemon_app.js
...
const browser = await puppeteer.launch({
dumpio: true // 이 설정이 없으면 page.on('console')을 등록해도 작동하지 않는다.
})
// page.evaluate안의 console.log를 여기 node.js로 가져와서 보이기 위한 이벤트 등록
page.on('console', msg => {
for (let i = 0; i < msg.args.length; ++i)
console.log(`${i}: ${msg.args[i]}`);
});
// => dumpio: true 설정만으로 가능하다. 이 이벤트는 먹히지 않는다.
이 후, page.evaluate()
에서와 page.waitForFunction()
안에서 console.log를 작성할 수 있다.
try {
waitFunc = await page.waitForFunction(() => {
console.log("5. inside page.waitForfunction(), document: ", document);
console.log("5. inside page.waitForfunction(), window: ", window);
const el1 = document.querySelector(".card:first-child .card--id")
console.log("5. inside page.waitForfunction(), el1: ", el1)
console.log("5. inside page.waitForfunction(), el1.textContent: ", el1.textContent);
return document.querySelector(".card:last-child .card--id").textContent === "#100"
}, { timeout: 50000 }
);
} catch (e) {
console.log(e)
}
위와 같이 page.waitForFunction
안에서 document, window, el1, el1.textContent 순서로 콘솔을 찍게 만들었다.
결과 창:


⇒ document, window, el1, el1.textContent 순서가 계속해서 반복되어 찍히는 것을 볼 수 있다. 1초가 넘어가는 부분을 캡쳐해왔는데, 여기를 보면 대략 0.02초당 waitForFunction
을 한 번씩 실행한다는 것을 알 수 있다. 그러니까 1초에 약 50번씩 waitForFunction
의 리턴 불리언이 참이 되는지를 검사한다는 것인데…
document
.querySelector(".card:last-child")
에 문제가 있다는 것을 알았다. 시간이 지나도 계속해서 :first-child만 반환하고 있었다. 그래서 모든 요소가 그려진 후에도 waifForFunction의 리턴문 조건에 도달하지 못해 timeoutError가 떴던 것이다.
document
.querySelector(".card:last-child")
가 먹히지 않았던 이유
.card:last-chlid 에 해당하려면, 자기자신이 .card 클래스를 가지면서 그 부모의 자식들 중 제일 마지막이어야 한다. 즉, .card를 클래스로 가지지 않더라도 동생이 하나라도 있으면 이 부분은 적용되지 않는 것이다..!
그냥 .card들 중에서만 막내를 고르려면 .card:last-of-type으로 골라야 한다.
아니면 document.querySelectorAll(”.card”)[마지막 인덱스]로 뽑아오는 수도 있다.
내 포켓몬 도감에서 안됐던 이유는 결국 첫 .card의 부모 .flip에게 .card라는 자식이 자기 자신 하나뿐이어서 계속 이 첫번째 카드만 반환됐던 것이다.
이 경우에도 만약 .card가 .flip의 외동이 아니었다면 반환조차 되지 못했을 것이다.
강의에서는 .card에게 부모가 따로 없이 #app의 바로 하위 자식들이어서(=.card들이 사촌지간이 아니라 친형제지간이어서) 저 코드가 가능했던 것이고..!
해결:
// 방법 1.
document.querySelector(".filp:last-child .card--id").textContent === "#100"
// 방법 2.
document.querySelector(".card:last-of-type .card--id").textContent === "#100"
=> 안된다. 자기 자신이 부모에게 항상 마지막 자식이 되므로. 즉, 100개의 맞는 요소가 있는데 querySelector는 항상 첫 번째 애를 뽑아가므로 이것도 역시나 안된다.
DOMContentLoaded
와 onload
이벤트보다도 늦는 fetch()?
DOMContentLoaded
와 onload
이벤트보다도 늦는 fetch()?
puppeteer가 동적 렌더링을 지원해줘서, ‘브라우저(페이지) 컨텍스트에서’ 쿼리셀렉터나 함수를 실행해준다… 는 부분까지 접근했다. 미심쩍은 부분들이 남아있었는데 오늘 이것 저것 실험해 보며 알게 된 점을 기록한다:
DOMContentLoaded
와onload
이벤트는 동적 렌더링을 기다려주지 않는다.
- fetch()를 통한 동적 렌더링이 훨씬 느리다.
DOMContentLoaded
와onload
이벤트 호출이 끝난 시점에 fetch()는 겨우 4번 호출(이미지 8장 가져옴)될 뿐이다. 100번을 호출하려니 얼마나 택도 없는 시도였겠나,DOMContentLoaded
와onload
여…
- 하지만 아직 의문인 점이, fetch() 호출도 script의 한 부분 아닌가? 그렇다면 아직 script 실행이 끝나지 않은 건데 onload는 왜 먼저 불려버리는 걸까.
page.evaluate()
은 동적 렌더링을 마칠때까지 기다리는 기능은 없지만 그 안에서 ‘document.querySelector’ 등의 문서 요소에 접근하도록 하는 기능을 쓸 수 있다.
page.waitForFunction()
은 동적 렌더링을 마칠때까지 기다릴 수 있다. 그리고 문서 요소에도 접근할 수 있다. 즉, ‘목표 요소가 렌더링될 때까지 기다림’ +page.evaluate()
기능인 셈.
Uploaded by N2T