android从0开始实现动漫网站爬虫应用 免费看动漫APP

Flower ·
更新时间:2024-11-13
· 539 次阅读

前言

    为什么要写这个应用?因为博主爱看动漫,但是有些动漫需要VIP,而且有些动漫在我用的那几个视频网站里甚至都搜不到资源,相信爱看动漫的铁汁应该也遇到过这个问题。于是我就想着自己写一个动漫应用,这样就能一站式解决我的看番需求了,因为之前用爬虫写过一个小说APP,所以理由当然的第一时间就想到了用爬虫来完成这个应用。然后找资源网站,实现功能代码,优化观看体验。。。终于,在今天这个应用的1.0.0版本实现了。写下这篇文章一是记录整个开发的流程,便于日后参考,二是分享出来有需要的可以借鉴一下。下面进入正题。

正文 一、寻找合适的网站

    这一步就是根据自己需求,去找合适的网站,之后的数据都要通过这个网站来获取。博主需要爬取的是动漫资源,自然是找那种资源全面且免费的。刚开始是想爬B站,但是有些视频资源需要会员才能看,于是放弃,后续也没多久就锁定了目标-xxxx(就不透露姓名了哈哈),因为我之前有用这个网站看过一个B站收费的番。

二、获取目标网站的Html内容 1.获取网站的搜索接口

    博主想要做的是输入搜索关键词就能出来相应的搜索内容,于是我需要先搞到网站的搜索接口地址,这一步很简单,只需要在网站的搜索条里输入一个关键词,按F12打开开发者模式,点击搜索,再在开发者界面的network选项里查看调用的网址即可,或者直接看浏览器的网址输入条也行,一般你会得到这样一个网址:www.xxxx.com?keyword=xxx。这样的网址就一目了然,你要检索某个关键词,只需要拼接在keyword参数后面就行了,一般搜索接口地址都大同小异,你参照上面的地址找到相应的搜索词字段就行。

2.获取搜索接口的html内容

    这一步建议使用Jsoup自带的api去请求,流程相当简单:

Connection connection = Jsoup.connect(Constant.SAKURA_NEXT_PAGE_BASE_URL); connection.userAgent(Constant.USER_AGENT_FORPC); connection.data("searchword", word); connection.postDataCharset("GB2312"); Document document = connection.method(Connection.Method.POST).post(); //关键参数: public static final String USER_AGENT_FORPC = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36";

 细节解析:

    1.Jsoup.connect()--参数传入你想要解析的网站链接地址,如www.baidu.com;

    2.connection.userAgent()--将UA设置为PC端UA,以获取PC端打开该网页时的HTML内容,如果不设置的话默认为手机端UA;

    3.connection.posetDataCharset()--设置传递参数的编码方式,因为博主访问的网页编码方式是GB2312,因此传入了GB2312。这一步一般网站不用设置,因为大部分都是UTF-8编码,jsoup默认的参数格式也是UTF-8编码;

    4.document--post请求后得到的document会把通过解析html内容得到的所有element放进去,简而言之,你想要的这个网址的一切元素都在document里存着,后续通过jsoup的解析api一一获取即可,具体有哪些解析用的api可以到jsoup的github项目地址里查看,不去看也没关系,下一步就会提到一些常用的解析api。

三.html内容解析

    这一步需要你结合网址的html内容去做,因为需要使用到元素的id、class等属性值。建议你打开浏览器,进入要解析的网址,按下F12审查元素,逐个解析你想要的内容。

    提供一下我解析动漫搜索结果方法,可以作为你解析html的参考:

private void parseDocuments(Document document){ waitDialog.show(); searchResultBeanList = new ArrayList(); lastPageCodeList.clear(); if(searchType == Constant.FLAG_SEARCH_ANIM){ Element baseElement = document.selectFirst("div.pics"); Elements urlElements = baseElement.select("a[href][title]"); Elements imgElements = baseElement.select("img[src]"); Elements liElements = baseElement.getElementsByTag("li"); try{ Element pagesElement = document.selectFirst("div.pages"); basePageUrl = Constant.SAKURA_NEXT_PAGE_BASE_URL.concat(pagesElement.selectFirst("a[href]").attr("href")); LogUtils.i(TAG+" basePageUrl : "+basePageUrl); if(TextUtils.isEmpty(basePageUrl) || basePageUrl.equals(Constant.SAKURA_NEXT_PAGE_BASE_URL)){ refreshLayout.setEnableLoadMore(false); }else{ refreshLayout.setEnableLoadMore(true); } }catch (Exception e){ e.printStackTrace(); refreshLayout.setEnableLoadMore(false); LogUtils.e(TAG+" 该动漫没有更多分页"); } if(Utils.isListEmpty(liElements)){ tv_no_data.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.GONE); waitDialog.dismiss(); LogUtils.e(TAG+" no search result find"); return; } List aliasList = new ArrayList(); List infoList = new ArrayList(); List descList = new ArrayList(); for(Element li : liElements){ Element firstSpan = li.selectFirst("span"); aliasList.add(firstSpan); Element secondSpan = li.after(firstSpan).selectFirst("span"); infoList.add(secondSpan); Element p = li.selectFirst("p"); descList.add(p); } for(int i=0;i<urlElements.size();i++){ SearchResultBean resultBean = new SearchResultBean(); Element a = urlElements.get(i); resultBean.setUrl(Constant.SAKURA_SEARCH_URL+a.attr("href")); resultBean.setCover(imgElements.get(i).attr("src")); resultBean.setTitle(imgElements.get(i).attr("alt")); resultBean.setAlias(aliasList.get(i).text()); resultBean.setInfos(infoList.get(i).text()); resultBean.setDesc(descList.get(i).text()); searchResultBeanList.add(resultBean); String title = resultBean.getTitle(); if(!TextUtils.isEmpty(title)){ lastPageCodeList.add(title.hashCode()); } } }else if(searchType == Constant.FLAG_SEARCH_FILM_TELEVISION){ Element pageElement = document.selectFirst("div.page"); if(pageElement != null){ Element pageA = pageElement.selectFirst("a[target]"); if(pageA == null){ refreshLayout.setEnableLoadMore(false); LogUtils.e(TAG+" no more pages"); }else{ basePageUrl = Constant.JIJI_BASE_URL.concat(pageA.attr("href")); LogUtils.i(TAG+" basePageUrl = "+basePageUrl); if(TextUtils.isEmpty(basePageUrl) || basePageUrl.equals(Constant.JIJI_BASE_URL)){ refreshLayout.setEnableLoadMore(false); }else{ refreshLayout.setEnableLoadMore(true); } } } Element listContainerElement = document.selectFirst("ul.serach-ul"); if(listContainerElement == null || listContainerElement.childrenSize() == 0){ tv_no_data.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.GONE); waitDialog.dismiss(); LogUtils.e(TAG+" no film datas"); return; } Elements sectionListElements = listContainerElement.select("li"); LogUtils.i(TAG+" sctionListElements size = "+sectionListElements.size()); for(int i=0;i 0){ //导演列表 Elements directorAList = pList.get(0).select("a"); if(directorAList != null && directorAList.size() > 0){ List directorList = new ArrayList(); for(int k=0;k 0){ List actorList = new ArrayList(); for(int j=0;j<actorAList.size();j++){ String actor = actorAList.get(j).text(); actorList.add(actor); } resultBean.setActorList(actorList); } //type String type = pList.get(2).text().replaceAll("\"",""); resultBean.setType(type); //area Element areaA = pList.get(3).selectFirst("a"); if(areaA != null){ String area = areaA.text(); resultBean.setArea(area); } //上映时间 String publishDate = pList.get(4).text().replaceAll("\"",""); resultBean.setPublishDate(publishDate); //更新时间 String updateDate = pList.get(5).text().replaceAll("\"",""); resultBean.setUpdateDate(updateDate); //剧情介绍 String desc = pList.get(6).text().replaceAll("\"",""); resultBean.setDesc(desc); } //@} LogUtils.d(TAG+" "+resultBean.toString()); searchResultBeanList.add(resultBean); if(!TextUtils.isEmpty(title)){ lastPageCodeList.add(title.hashCode()); } } } waitDialog.dismiss(); }

上面这个方法的作用就是把解析的内容存到bean类里,然后通过List填充内容到RecyclerView的适配器里,这样就成功把网页的搜索结果展示到自己的应用中了。当然,有可能你搜索的结果会有好几页,也就是有好几个html去放置你要的搜索结果,这就需要实现分页了,博主的项目中已经实现了,整个流程说起来比较繁琐,烦请移步github项目地址查看。

细节解析:

    1.document.selectFirst("div.pics")--选取第一个class为pics的div元素;

    2.document.selectFirst("div#pics")--选取第一个id为pics的div元素(代码里没有用到,补充一下这个用法);

    3.baseElement.select("a[href][title]")--选取所有同时包含href和title两个属性的a标签;

    4.baseElement.getElementsByTag("li")--获取所有li标签;

    5. .attr("href")--获取标签的href属性值;

    6. .text()--获取标签内的文本内容,举个栗子:content,b.text()得到的就是content;

    7.document.select("div.pics.large")--获取第一个class为pics large的div元素(补充介绍,获取class有多个的元素)。

知道上面介绍的几种解析方式后,做一个爬虫够用了,博主整个应用写下来也就用到了以上几种方式。是不是帮你省去了逛github的时间~。~

四、播放视频

    是不是以为这一步就是把视频的地址加载进VideoView或者什么播放器控件里就能看了?NONONO,很遗憾没有你想的辣么简单(其实博主一开始也是这么想的)。实际情况就是你并没有办法获取到视频的资源链接地址,因为html里面没有,只有把视频内容封装进去的一个网址,就是咱到人家网址里看视频的那种页面,有广告、推荐内容等等。不能用视频播放器播放那咋办呢?还能咋办,只能用WebView加载视频播放地址观看了呗(当然,你如果有更好的办法欢迎在评论区分享)。

    用WebView加载视频播放地址确实可以看,但是一般这种播放页面都会插很多广告进去,博主解析的网站播放页面甚至有YELLOW广告,这怎么敢拿去公开给人用呢,到时候举报我涉H我可是吃不了兜着走,必须把这些垃圾广告和与观看内容无关的东西屏蔽才行。

1.屏蔽(清除)网页中除视频播放控件以外的元素

    这一步可费了我不少功夫,查了很多资料才完成,跟你们要给个免费的点赞关注不过分吧?下面言归正传。

    1.创建js文件,写入对应的js脚本代码

     在项目assets文件夹中创建一个文件,以js后缀结尾,写入js脚本代码。这里提供我的js代码(pureVideo.js)作为参考:

javascript:window.customScript.log('==========start==========='); var child = document.children; var arr = []; function fn(obj){ for(var i=0;i<obj.length;i++){ if(obj[i].children){ fn(obj[i].children); } arr.push(obj[i]); } } fn(child); for(var i=0;i<arr.length;i++){ var tagName = arr[i].tagName; var id = arr[i].id; var playerDiv = document.getElementsByClassName('player')[0]; if(tagName.indexOf('HTML') != -1 || tagName.indexOf('BODY') != -1 || (tagName.indexOf('HEAD') != -1 && tagName.indexOf('HEADER') == -1) || id.indexOf('mobile-index') != -1 || arr[i].parentNode == playerDiv){ continue; } var className = arr[i].className; if(className == '' || (className.indexOf('player') == -1 && className.indexOf('playbox') == -1) || className.indexOf('-') != -1){ arr[i].parentNode.removeChild(arr[i]); } } var html = document.getElementsByTagName('html')[0]; window.customScript.log('html : '+html.innerHTML); window.customScript.onJSLoadComplete(); //var videoParent = document.getElementById('mobile-index'); //videoParent.setAttribute('style','position:fixed;align-items: \"center\";');

细节解析博主在另外一篇博文里说的很详细了,请点这里查看。

    2.使用js脚本

    同样,博主在另一篇博客里写的很详细了,请点这里查看。脚本执行完毕后,视频播放页面就只剩下纯粹的视频播放器了,放一下清除广告的前后对比照给你们看下:

    去广告前:

    

    去广告后:

    

前后一对比你们应该就知道去广告的重要性了吧?

五、实现收藏和历史记录

    不能光能看视频就完事儿了,收藏和历史记录这种基本功能还是要提供的。这里博主是把相应的数据存到本地数据库里了,用的ROOM数据库框架,之前写的文章里吐槽并介绍了ROOM数据库的用法(点这里跳转),现在想来当时我吐槽它应该是没有充分体会到它的便利之处,经过这个项目我对ROOM框架黑转粉了,强推一波!

    这里简单讲一下思路,

    收藏:搜索结果页面跳转到选集页面时,把点击的这个搜索结果对应的bean类传过去,然后在选集页面点击收藏时把bean存到数据库中,在收藏页面从数据库中把数据取出即可。

    观看历史:在选集页面点击某一集后,把搜索结果页面传递过来的bean连同点击时间、播放的集数一起存进数据库中,在观看历史页面从数据库中把数据取出即可。

    因为之后会涉及到数据刷新问题,然后又不是自己公司的后台给的接口,没法儿要求人家给我一个唯一标识字段,于是我使用动漫名的hashCode值作为一条数据的唯一标识。基本不存在同名的动漫,因此用这个来作为唯一标识是可行的。

    实现流程详见github项目地址。

六、项目UI展示

    在博主写这篇文章的时候,已经把影视剧资源爬取的功能也写完了,因此你会看到列表页里有一些电视剧资源。

       

 

  

 

上面的图基本涵盖了所有UI了,如果有想使用的这里是APP下载地址。提醒:当前版本因为影视网站的重定向问题,影视剧只能看第一集,其他集数无法跳转成功,因此可以拿去看番剧,想看影视剧后续解决了这个问题会更新资源,届时再下载吧。

总结

    爬虫用到的主要技术其实就是html解析,当你使用jsoup去写爬虫的时候,你会发现爬虫的开发过程变得异常简单,当然,依然需要你的耐心和细心去一步一步爬取你需要的数据。其次就是如果你要发出来给别人用的话,UI和用户体验方面也需要加入一点思考、费一点功夫。

    附上github上的项目地址:https://github.com/hellojessehao/BiliParser

    以上就是这篇文章给的全部内容了,如果帮到你了给个免费的赞和关注吧,这是博主更新下去的全部动力。

    如果有问题欢迎评论区留言。


作者:Jesse_android



爬虫 app 动漫 Android

需要 登录 后方可回复, 如果你还没有账号请 注册新账号