runBlocking {
bookLinks.mapIndexed { ranking, bookLink ->
val job = async { scrapeBookData(browser, bookLink, ranking) }
val result = job.await()
if (result != null) {
bestsellers.add(result)
}
}
}
private suspend fun scrapeBookData(browser: Browser, bookUrl: String, ranking: Int): BookDTO? {
val page = browser.newPage()
page.navigate(bookUrl, Page.NavigateOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED))
printWithThread("${bookUrl}에 접근 완료")
delay(3000)
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("${bookUrl}의 데이터 파싱 완료")
if (json.values.all { it.isBlank() }) {
return null
}
return BookDTO(
id = 0L,
title = json["title"] ?: "",
author = json["author"] ?: "",
description = json["description"] ?: "",
image = json["image"] ?: "",
isbn = json["isbn"] ?: "",
ranking = ranking + 1,
favoriteCount = 0
)
}
我预期如果我将 scrapeBookData(一个挂起函数)延迟 3 秒,协程会在延迟期间切换并再次执行 scrapeBookData。我预期在重复执行 scrapeBookData 3 秒后,第一个协程将解析网络响应已完成的页面。然而,协程是同步运行的。
[http-nio-8080-exec-2 @coroutine#2] https:S000215819502에 접근 완료
[http-nio-8080-exec-2 @coroutine#2] https:S000215819502의 데이터 파싱 완료
[http-nio-8080-exec-2 @coroutine#3] https:S000215150862에 접근 완료
[http-nio-8080-exec-2 @coroutine#3] https:S000215150862의 데이터 파싱 완료
[http-nio-8080-exec-2 @coroutine#4] https:S000215787651에 접근 완료
问题不太清楚,但我猜您希望
scrapeBookData
对中的每个条目并行执行bookLinks
。但您的代码并非如此,因为在使用 启动新协程后
async
,您会立即通过调用 暂停代码await
,等待该协程完成 - 无论您在该协程中延迟多长时间。async
立即调用await
几乎总是一个错误,因为它会使协程变得多余,它基本上与仅调用 相同你想要的是在循环之后等待启动的协程,在所有协程启动之后 - 而不是在每个协程之后:
现在,循环为每个 bookLink 启动一个协程,并立即继续处理下一个链接,而无需等待协程完成。由于 async 返回一个
Deferred
(而不是一个Job
,正如原始代码的变量名所暗示的那样),因此在启动所有协程后,mapIndexed 将返回一个 Deferred 列表。现在您想等待所有协程,直到它们完成。幸运的是,Kotlin 为此提供了一个方便的函数,awaitAll
。awaitAll
现在返回一个简单的List<BookDTO?>
,您可以进一步处理。从查看代码来看,您想要过滤掉所有空值,因此您应该应用.filterNotNull()
下一个。现在您可以对结果进行任何您想做的事情List<BookDTO>
。如果您想将整个列表添加到另一个列表bestsellers
,您可以附加.also { bestsellers.addAll(it) }
。但只需这样做就足够了:您应该删除
delay
scrapeBookData 中的,您希望您的协程尽快完成。如果您只想在代码的该部分添加一个暂停点,您可以yield
改为调用。但我不明白为什么这里需要这样做,所以您应该完全删除它。