gfit_tobebetter 2025-01-14 22:09 采纳率: 0%
浏览 11

Android studio 用retrofit解析html创建转化器失败

用andoid studio编写一个简单的新闻列表和新闻详细页的app。但是现在数据加载错误。点击一直显示加载失败,报错显示没有成功创建转化器

img


数据是网络爬虫的,用了retrofit和JSoup。
不知道哪里出错为什么不能创建转化器。
代码
数据类
NewsItem

data class NewsItem (

     val title: String,
     val content: String,
     val source:String,
     val dateTime: String,
     val url: String,
     val imgSrc: String,
)

NewsList


package com.example.newsapp.model

class NewsList:ArrayList<NewsItem>()


NewsApi


package com.example.newsapp.api


import com.example.newsapp.model.NewsItem
import com.example.newsapp.model.NewsList
import com.example.newsapp.model.NewsPage
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path

//https://www.cuc.edu.cn/news/1901/list.htm中传要闻
//https://www.cuc.edu.cn/news/10127/list.htm科学研究
//https://www.cuc.edu.cn/news/10128/list.htm今日推荐

interface NewsApi {
    //爬取新闻列表页
    @GET("news/{title}/list.htm")
    suspend fun getHtml(@Path("title")title:String): Call<NewsList>
    //爬取新闻详细页
    @GET("{url}")
    suspend fun getNewsContent(@Path("url")url:String): Call<NewsPage>
}

MyRetrofit

package com.example.newsapp

import com.example.newsapp.api.NewsApi
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object MyRetrofit {
    private const val BASE_URL = "https://www.cuc.edu.cn/"
    private val retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(NewsConverterFactory())//设计数据解析器
            //.addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    val api: NewsApi by lazy {
        retrofit.create(NewsApi::class.java)
    }
}

NewsConverFactory



package com.example.newsapp

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.newsapp.model.NewsItem
import com.example.newsapp.model.NewsList
import com.example.newsapp.model.NewsPage
import com.google.gson.reflect.TypeToken
import okhttp3.ResponseBody

import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type

class NewsConverterFactory : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<out Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        if (TypeToken.get(type).equals(TypeToken.get(String::class.java)) ){
            return HtmlConverter()
        }
        if (TypeToken.get(type).equals(TypeToken.get(NewsList::class.java))) {
            return NewsConverter()
                }
        if (TypeToken.get(type).equals(TypeToken.get(NewsPage::class.java))) {
            return ContentConverter()
        }
        return null
    }



    class NewsConverter: Converter<ResponseBody, List<NewsItem>> {
    override fun convert(value: ResponseBody):List<NewsItem>? {
        val html =value.string()
        val newsItems = mutableListOf<NewsItem>()
        //  val newsList = mutableListOf<NewsItem>()
        val doc:Document=Jsoup.parse(html)

        val newsElements: Elements = doc.select("ul.news_list li.news")
        for (newsElement in newsElements) {
            val imageUrl = newsElement.select("div.news_imgs img").attr("src")
            val newsUrl = newsElement.select("a").attr("href").removePrefix("/")
            val title = newsElement.select("div.news_title").text()
            val text = newsElement.select("div.news_text").text()
            val publisher = newsElement.select("span.publisher").text()
            val date = newsElement.select("span.news_date").text()

            newsItems.add(
                NewsItem(
                    title = title,
                    content = text,
                    source = publisher,
                    dateTime = date,
                    url = newsUrl,
                    imgSrc = imageUrl
                )
            )
        }

     return newsItems
    }
}
class ContentConverter:Converter<ResponseBody, NewsPage>
{
    override fun convert(value: ResponseBody): NewsPage? {
        val html =value.string()
        val doc:Document=Jsoup.parse(html)
        //提取标题
        val titleElement:Element? = doc.select("h1.arti_title ").first()
        val title =titleElement?.text()?:""
        //提取来源
        val sourceElement: Element? = doc.select("span.arti_publisher").first()
        val source = sourceElement?.text() ?: ""
        //提取浏览次数
        val viewCountElement: Element? = doc.select("span.arti_viwe span.WP_VisitCount").first()
        val viewCount= viewCountElement?.text() ?: ""
        //提取发布时间
        val dateTimeElement: Element? = doc.select("span.arti_update").first()
        val dateTime = dateTimeElement?.text() ?: ""
        //提取正文
        val contentElements: Elements = doc.select("p")
        var content = ""
        for(contentElement in contentElements) {
            content += contentElement.text() + '\n';

        }


    return NewsPage(title,source,viewCount,dateTime,content)

    }
}

    class HtmlConverter : Converter<ResponseBody, String> {
        override fun convert(value: ResponseBody): String? = value.string()
    }
}
  • 写回答

3条回答 默认 最新

  • GISer Liu 2025-01-15 03:11
    关注

    该回答引用自GPT-deepseek, 由博主 GISer Liu 编写:

    问题分析

    用户在Android Studio中使用Retrofit和JSoup解析HTML数据时,遇到了“创建转化器失败”的问题。具体表现为数据加载失败,点击后一直显示加载失败,报错信息显示没有成功创建转化器。

    关键点分析

    1. Retrofit的Converter机制:Retrofit默认使用Gson或Jackson等库来解析JSON数据,但用户需要解析HTML数据,因此自定义了一个NewsConverterFactory来处理HTML数据。

    2. 自定义Converter的实现:用户在NewsConverterFactory中实现了三个自定义的Converter:NewsConverterContentConverterHtmlConverter,分别用于解析新闻列表、新闻详细页和纯HTML字符串。

    3. 报错原因:报错信息显示“没有成功创建转化器”,可能是由于自定义的Converter没有正确注册或实现有问题。

    解决方案

    1. 检查Converter的注册:确保在Retrofit的构建过程中正确注册了自定义的NewsConverterFactory

    2. 检查Converter的实现:确保每个Converter的实现逻辑正确,特别是类型匹配和HTML解析部分。

    3. 调试和日志:添加日志输出,检查每个步骤的执行情况,定位问题所在。

    具体代码修改

    1. 检查Converter的注册

    MyRetrofit中,确保NewsConverterFactory被正确注册:

    object MyRetrofit {
        private const val BASE_URL = "https://www.cuc.edu.cn/"
        private val retrofit by lazy {
            Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(NewsConverterFactory()) // 确保这里正确注册
                .build()
        }
    
        val api: NewsApi by lazy {
            retrofit.create(NewsApi::class.java)
        }
    }
    

    2. 检查Converter的实现

    确保NewsConverterFactory中的每个Converter都正确实现了Converter接口,并且类型匹配正确。

    class NewsConverterFactory : Converter.Factory() {
        override fun responseBodyConverter(
            type: Type,
            annotations: Array<out Annotation>,
            retrofit: Retrofit
        ): Converter<ResponseBody, *>? {
            if (TypeToken.get(type).rawType == String::class.java) {
                return HtmlConverter()
            }
            if (TypeToken.get(type).rawType == NewsList::class.java) {
                return NewsConverter()
            }
            if (TypeToken.get(type).rawType == NewsPage::class.java) {
                return ContentConverter()
            }
            return null
        }
    }
    

    3. 调试和日志

    NewsConverterContentConverter中添加日志输出,检查HTML解析是否正确:

    class NewsConverter : Converter<ResponseBody, List<NewsItem>> {
        override fun convert(value: ResponseBody): List<NewsItem>? {
            val html = value.string()
            println("HTML Content: $html") // 添加日志输出
            val newsItems = mutableListOf<NewsItem>()
            val doc: Document = Jsoup.parse(html)
    
            val newsElements: Elements = doc.select("ul.news_list li.news")
            for (newsElement in newsElements) {
                val imageUrl = newsElement.select("div.news_imgs img").attr("src")
                val newsUrl = newsElement.select("a").attr("href").removePrefix("/")
                val title = newsElement.select("div.news_title").text()
                val text = newsElement.select("div.news_text").text()
                val publisher = newsElement.select("span.publisher").text()
                val date = newsElement.select("span.news_date").text()
    
                newsItems.add(
                    NewsItem(
                        title = title,
                        content = text,
                        source = publisher,
                        dateTime = date,
                        url = newsUrl,
                        imgSrc = imageUrl
                    )
                )
            }
    
            return newsItems
        }
    }
    
    class ContentConverter : Converter<ResponseBody, NewsPage> {
        override fun convert(value: ResponseBody): NewsPage? {
            val html = value.string()
            println("HTML Content: $html") // 添加日志输出
            val doc: Document = Jsoup.parse(html)
            // 提取标题
            val titleElement: Element? = doc.select("h1.arti_title").first()
            val title = titleElement?.text() ?: ""
            // 提取来源
            val sourceElement: Element? = doc.select("span.arti_publisher").first()
            val source = sourceElement?.text() ?: ""
            // 提取浏览次数
            val viewCountElement: Element? = doc.select("span.arti_viwe span.WP_VisitCount").first()
            val viewCount = viewCountElement?.text() ?: ""
            // 提取发布时间
            val dateTimeElement: Element? = doc.select("span.arti_update").first()
            val dateTime = dateTimeElement?.text() ?: ""
            // 提取正文
            val contentElements: Elements = doc.select("p")
            var content = ""
            for (contentElement in contentElements) {
                content += contentElement.text() + '\n'
            }
    
            return NewsPage(title, source, viewCount, dateTime, content)
        }
    }
    

    总结

    通过以上步骤,用户可以检查自定义的Converter是否正确注册和实现,并通过日志输出定位问题所在。如果问题仍然存在,建议进一步检查HTML结构是否与代码中的选择器匹配,或者尝试使用其他HTML解析库进行调试。

    希望这个解决方案能帮助您解决问题!如果有其他问题,欢迎继续提问。

    如果该回答解决了您的问题,请采纳!如果没有,请私信联系或评论您的疑惑

    评论

报告相同问题?

问题事件

  • 修改了问题 1月14日
  • 创建了问题 1月14日