작성
·
37
·
수정됨
1
private fun scrapeBookData(browser: Browser, bookLinks: List<String>): List<BookDTO?> {
val bestsellers = mutableListOf<BookDTO?>()
runBlocking {
bookLinks.mapIndexed { i, link ->
printWithThread("${i} 시작")
val page = browser.newPage()
page.navigate(link)
printWithThread("${link}에 접속 완료")
launch {
delay(3000)
page.waitForLoadState(LoadState.DOMCONTENTLOADED)
val data = page.evaluate(
""" () => JSON.stringify({
title: document.querySelector('.prod_title')?.innerText?.trim() || '',
author: document.querySelector('.author')?.innerText?.trim() || '',
isbn: document.querySelector('#scrollSpyProdInfo .product_detail_area.basic_info table tbody tr:nth-child(1) td')?.innerText?.trim() || '',
description: document.querySelector('.intro_bottom')?.innerText?.trim() || '',
image: document.querySelector('.portrait_img_box img')?.getAttribute('src') || ''}) """
).toString()
val type = object : TypeToken<Map<String, String>>() {}.type
val json: Map<String, String> = Gson().fromJson(data, type)
page.close()
printWithThread("${link}의 데이터 파싱 완료")
var bestseller: BookDTO? = null
if (!json.values.all { it.isBlank() }) {
bestseller = BookDTO(
id = 0L,
title = json["title"] ?: "",
author = json["author"] ?: "",
description = json["description"] ?: "",
image = json["image"] ?: "",
isbn = json["isbn"] ?: "",
ranking = i + 1,
favoriteCount = 0
)
}
bestsellers.add(bestseller)
}
}
}
return bestsellers
}
[http-nio-8080-exec-1 @coroutine#1] 0 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215819502에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 1 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215150862에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 2 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215150863에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 3 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215150882에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 4 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215150895에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 5 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000215150892에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 6 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000000610612에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 7 시작
[http-nio-8080-exec-1 @coroutine#1] https://product.kyobobook.co.kr/detail/S000001632467에 접속 완료
[http-nio-8080-exec-1 @coroutine#1] 8 시작
DOM객체가 로드되는데까지 시간이 오래 걸려서 페이지들을 한번에 호출하고 DOM객체가 로드되는 시간을 공유하고 객체가 로드되는대로 데이터를 가져오려고 했습니다
접속하는건 메인 코루틴이 하게하고 반복문만큼 launch로 코루틴을 만들어서 각 DOM객체가 만들어지면 데이터를 파싱하려고 했으나 delay(3000)때문에 3초마다 메인코루틴이 접속하는 것 같습니다.
하지만 delay(3000)을 없애도 같은 결과가 나옵니다 메인 코루틴이 거의 3초느낌으로 웹페이지에 접속을 합니다 launch를 하나 더 만들어서 페이지를 호출하는것도 코루틴 처리를 하면 launch 바깥부분은 page객체를 받지못해 컴파일 오류가 뜹니다
이 상황을 어떻게 돌파해야하는지 감이 잘 안오는데 힌트 주실 수 있을까요? ㅠㅠ
답변 1
0
안녕하세요 재익님! 🙂 질문 주셔서 감사합니다.
결론부터 말씀드리면, 현재 blocking I/O가 single thread를 점유하고 있기 때문에 그런 것으로 보입니다.
bookLinks.mapIndexed { i, link ->
businessLogic()
}
에서 businessLogic()
은 크게
외부 Page를 불러오는 과정
불러온 Page에 대한 데이터를 가공하는 과정
으로 나눠지는 것 같은데요!
이 중 1번은 blocking I/O로 코루틴 안에서 실행한다고 하더라도, 코루틴도 결국 특정한 스레드에 매핑되어 실행 (1강 내용입니다!) 되기 때문에 스레드가 하나인 경우 로딩 -> 로직 실행 -> 로딩 -> 로직 실행 처럼 순차적인 결과로 보일 수 밖에 없습니다.
가장 간단한 해결책은 해당 로직을 여러 스레드에 매핑될 수 있게끔 해주는건데요!
예를 들어 스레드가 5개라면, 동시에 5명이
로딩 -> 로직 실행
로딩 -> 로직 실행
로딩 -> 로직 실행
로딩 -> 로직 실행
로딩 -> 로직 실행
을 하기 때문에 로딩이 blocking 하게 3초 정도 걸린다 하더라도 3초에 5번씩 businessLogic을 돌릴 수 있게 됩니다.
runBlocking {
val jobs = bookLinks.mapIndexed { i, link ->
async(Dispatchers.IO) {
businessLogic()
}
}
jobs.awaitAll()
}
처럼 runBlocking
만 사용해 '하나의 스레드'에 코루틴을 배정하지 않고 Dispatchers.IO
를 통해 여러개의 스레드에 코루틴을 배정하면 원하시는 결과를 얻으실 수 있을거에요! 🙂
또 다른 해결책으로는 blocking I/O를 non-blocking I/O로 교체하는건데, 이는 저희가 100% 제어할 수는 없고 non-blocking I/O가 지원되는 라이브러리가 있는지 확인이 필요하기에 애매할 수 있습니다.
학습하신 내용을 바로 적용해 보려 하시다니 대단하시네요~! 👍
답변이 도움이 되었으면 좋겠습니다. 감사합니다. 🙇
감사합니다! 알림이안떠서 이제봤네요 ㅠ
알고보니 제가 사용한 playwright 라는 라이브러리가 싱글스레드기반에서만 작동했던거여서 스레드를 늘려서 각각 playwright인스턴스를 넣고 Dispatchers.io로 해결했습니다! 감사합니다!!!
강의 복습 한번 더 해야겠네용ㅎㅎ