目標:Android 端實現深層爬蟲 —— 首頁列表獲取新聞標題+鏈接 → 進入詳情頁解析圖片/正文。核心難點:網絡請求串行依賴(詳情頁 URL 依賴列表頁解析),且必須在子線程執行。
拋棄嵌套 AsyncTask(「回調地獄」),採用 Kotlin 協程 + Retrofit + Jsoup,代碼同步寫法、異步執行。
// build.gradle (Module)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-scalars:2.9.0" // 返回原始 HTML
implementation "org.jsoup:jsoup:1.16.1"
// 1. 定義網絡接口
interface ApiService {
@GET
suspend fun getHtml(@Url url: String): String
}
// 2. 數據實體
data class News(val title: String, val link: String, var content: String = "", var images: List = emptyList())
// 3. 爬蟲邏輯(在 ViewModel 或 Repository 中)
class NewsRepository(private val api: ApiService) {
// 獲取列表頁並解析標題+鏈接
suspend fun fetchList(page: Int): List {
val html = api.getHtml("https://example.com/news?page=$page")
return parseList(html)
}
// 並發獲取所有詳情頁
suspend fun fetchDetails(newsList: List): List {
return coroutineScope {
newsList.map { news ->
async(Dispatchers.IO) {
val detailHtml = api.getHtml(news.link)
news.content = parseContent(detailHtml)
news.images = parseImages(detailHtml)
news
}
}.awaitAll()
}
}
}
// 4. UI 層調用(MainScope)
viewModelScope.launch {
val list = repo.fetchList(1)
val detailed = repo.fetchDetails(list)
_newsList.postValue(detailed) // 更新 LiveData/StateFlow
}
coroutineScope 確保所有詳情請求完成才返回,異常自動傳播取消。Dispatchers.IO 專用於網絡/磁盤,不阻塞主線程。若必須用 Java,使用 RxJava 3 + Retrofit:
Observable.fromIterable(newsList)
.flatMap(news -> api.getHtml(news.link)
.subscribeOn(Schedulers.io())
.map(html -> { parseDetail(news, html); return news; }))
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::onSuccess, this::onError);
HtmlParser 工具類,統一 Jsoup 選擇器邏輯,便於網站改版維護。robots.txt、設置合理 User-Agent、請求間隔隨機化,防反爲蟲封禁。