记一次漫画网站解析案例 -奇漫屋

记一次漫画网站解析案例 -奇漫屋

目录

以下以 奇漫屋 网站的全职法师漫画作为样例讲解:以下以 奇漫屋 网站的全职法师漫画作为样例讲解:

全职法师链接:

全职法师_漫画最新章节免费阅读(下拉式) - 奇漫屋 (qiman57.com)

1.获取列表

1.分析

首先打开页面,我们能看到全部章节列表由图1.1.1中的两个模块构成:一个是打开页面就有的最新章节,另外一个是需要异步点击“查看更多章节”才会显示的全部章节。

图1.1.1

我们按F12查询一下整个列表的数据来源,如图1.1.2所示,果不其然,列表数据由两部分构成,一个是HTML中自带的部分章节;

图1.1.2

另一个是点击“查看更多章节”请求对应API接口从而获取的剩余章节数据,为json格式,如图1.1.3。然后分析其请求包:

  • 地址固定为:http://www.qiman57.com/bookchapter/
  • 请求方式post;
  • 参数方式为params;
  • 内容为id=9845&id2=1,很容易可得,id值为该漫画(全职法师)的页面id,id2默认为1(可能意思是多个章节列表,通常为1)。

图1.1.3

2.总结

因此,我们总结一下,获取漫画页面的HTML,可以获得部分章节名称和地址,在调取API,可获得剩余章节名称和地址。

2.获取具体章节全部漫画图片

1、分析

首先,打开奇漫屋的一个漫画的随便一章,打开F12,能够发现其中没有可疑的疑似图片链接的地址:

该章节初始html源码.txt

然后分析JS,首先,能够看到,HTML本身有一段JS,但是被混淆了,然后还引用了两个可疑的JS文件如图2.1.1所示:

图2.1.1

引用的两个可疑的JS文件中,最可疑的是lazyloadimg2.js,内容如例图2.1.2所示:

图2.1.2

观察其中图2.1.2,看到可疑变量imgs,试图控制台输出一下该变量,如图2.1.3所示:

图2.1.3

可以看到,我们找到了保存全部图片链接的变量。

那么现在要做的,就是找到赋值imgs的地方,逆向找到其源头。其中,找到这段可以代码,如图2.1.4所示:

图2.1.4

有一个newImgs的变量,但是没有在其他地方找到这个变量的定义。于是,我们开始试图解析一下那块被混淆的js代码,我们不会反混淆,但是我们可以简单分析其逻辑。

//此为html带的混淆的js代码
eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('g e=["2://1.5.0/4-7-i-8/d~6.3","2://1.5.0/4-7-i-8/c~6.3","2://1.5.0/4-7-i-8/b~6.3","2://1.5.0/4-7-i-8/a~6.3","2://1.5.0/4-7-i-8/9~6.3","2://1.5.0/4-7-i-8/f~6.3","2://1.5.0/4-7-i-8/p~6.3","2://1.5.0/4-7-i-8/n~6.3","2://1.5.0/4-7-i-8/o~6.3","2://1.5.0/4-7-i-8/m~6.3","2://1.5.0/4-7-i-8/l~6.3","2://1.5.0/4-7-i-8/h~6.3","2://1.5.0/4-7-i-8/j~6.3","2://1.5.0/4-7-i-8/k~6.3"]',26,26,'com|p3|https|jpg|tos|dcarimg|noop|cn|8gu37r9deh|fa9ed8b4a0104bb9b2bc73f12967dd5c|b20f0689aa3d49bc8f50fdc5fd9f7ade|6ef5c0d3146e410083da3ec2e0a42862|20db1e65fe4c48df9fccce5629ca16df|75aeed591e5c4944bc2e03271ba69adf|newImgs|63ef5b0a8f324f2aa4733dc5461800b0|var|269ddf907f914d8ebb38234cdcc0e157||370dbf041f1c4d458e21b8eb569b9c1f|ceaea1b1f81c4f6e8a75e84ce2c7c9a7|eabb5c0cddc64994bb06347a4ef931b3|3bb87b884282425c96de3bc531f532a1|4cad27d758ca4bc0baae2a5432f31112|1e4c243641064aae824059b29f6b00cc|54c01c7fdc3e49b494587a22913b227c'.split('|'),0,{}))

首先引入以下知识点:

  • eval(str)函数,将str默认为js代码,然后使其执行,例如eval("var a = 2;");便实际初始化时定义了a变量。

  • function(a,b,c,d)()写法,为:立即执行函数表达式,以下是样例:

    如下代码,(1,2,3)会被解析为函数的实参,(1,2,3)前面的()表达式会被解析为函数表达式。然后该函数会立即执行。

    function fun(a,b,c)

    {

    //…

    }(1,2,3)

然后,我们来仔细观看一下该混淆js,它便是eval(funtion(p,a,c,k,e,d){......}(......))的形式,那么eval中的funtion(p,a,c,k,e,d){......}(......)便是一个立即执行函数,并且可以返回出一个字符串,我们在控制台如下操作:

//将函数定义为变量,便于操作和使用
var deal = function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}
//定义变量a接收其返回的结果
var a = deal('g e=["2://1.5.0/4-7-i-8/d~6.3","2://1.5.0/4-7-i-8/c~6.3","2://1.5.0/4-7-i-8/b~6.3","2://1.5.0/4-7-i-8/a~6.3","2://1.5.0/4-7-i-8/9~6.3","2://1.5.0/4-7-i-8/f~6.3","2://1.5.0/4-7-i-8/p~6.3","2://1.5.0/4-7-i-8/n~6.3","2://1.5.0/4-7-i-8/o~6.3","2://1.5.0/4-7-i-8/m~6.3","2://1.5.0/4-7-i-8/l~6.3","2://1.5.0/4-7-i-8/h~6.3","2://1.5.0/4-7-i-8/j~6.3","2://1.5.0/4-7-i-8/k~6.3"]', 26, 26, 'com|p3|https|jpg|tos|dcarimg|noop|cn|8gu37r9deh|fa9ed8b4a0104bb9b2bc73f12967dd5c|b20f0689aa3d49bc8f50fdc5fd9f7ade|6ef5c0d3146e410083da3ec2e0a42862|20db1e65fe4c48df9fccce5629ca16df|75aeed591e5c4944bc2e03271ba69adf|newImgs|63ef5b0a8f324f2aa4733dc5461800b0|var|269ddf907f914d8ebb38234cdcc0e157||370dbf041f1c4d458e21b8eb569b9c1f|ceaea1b1f81c4f6e8a75e84ce2c7c9a7|eabb5c0cddc64994bb06347a4ef931b3|3bb87b884282425c96de3bc531f532a1|4cad27d758ca4bc0baae2a5432f31112|1e4c243641064aae824059b29f6b00cc|54c01c7fdc3e49b494587a22913b227c'.split('|'), 0, {});
console.log(a);

图2.1.5 控制台处理该段混淆js

可以看到,我们首先将function函数变为简单的函数变量(方便调用),然后在内填入所使用的参数,于是,神奇的一幕就发生了,控制台处理了该事件,并返回了一个字符串,这个字符串正是我们心心念的newImgs的定义:

//结果便是该章节全部漫画图片的请求地址
var newImgs=["https://p3.dcarimg.com/tos-cn-i-8gu37r 9deh/75aeed591e5c4944bc2e03271ba69adf~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/20db1e65fe4c48df9fccce5629ca16df~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/6ef5c0d3146e410083da3ec2e0a42862~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/b20f0689aa3d49bc8f50fdc5fd9f7ade~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/fa9ed8b4a0104bb9b2bc73f12967dd5c~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/63ef5b0a8f324f2aa4733dc5461800b0~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/54c01c7fdc3e49b494587a22913b227c~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/4cad27d758ca4bc0baae2a5432f31112~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/1e4c243641064aae824059b29f6b00cc~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/3bb87b884282425c96de3bc531f532a1~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/eabb5c0cddc64994bb06347a4ef931b3~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/269ddf907f914d8ebb38234cdcc0e157~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/370dbf041f1c4d458e21b8eb569b9c1f~noop.jpg","https://p3.dcarimg.com/tos-cn-i-8gu37r9deh/ceaea1b1f81c4f6e8a75e84ce2c7c9a7~noop.jpg"]

2.总结

因此,我们获取到了章节的HTML之后,我们就可以先获取其中的那段混淆后的JS,想办法使用爬虫代码去执行这串JS代码,拿到执行后的newImgs结果,就获得了该章节全部图片地址,然后再循环爬取漫画。

3.爬取流程图

图3.1.1

4.部分代码展示(仅供参考)

package py.main;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.Getter;
import lombok.Setter;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import py.laimanhua.net.Downloadmanhua;
import py.laimanhua.net.HttpUtils;
import py.utils.ZzSecurityHelper;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 爬取 奇漫屋 漫画 采用单线程下载;
 * 使用时替换id和path文件夹路径即可,path文件夹路径需已存在
 * HttpUtils(请求页面类)和Downloadmanhua(下载图片和控制错误输出类)由于代码写的太烂,还请各位自行编写
 */
public class DownLoadQiManWu {

    /**漫画id*/
    static String id = "24363";
    /**漫画 第一序列列表 根目录地址*/
    static String rooturl2 = "http://www.qiman57.com";
    /**漫画 第一序列列表 根目录页面*/
    static String rooturl = rooturl2+"/";
    /**漫画 第一序列列表 json文件*/
    static String listHeaderUrl = rooturl2+"/bookchapter/";
    /**自己定义下载的根目录,该目录必须为已存在*/
    static String path = "D:\\E\\漫画\\第一序列\\";

    /**
     * 处理js代码所构建的ScriptEngine对象
     */
    static ScriptEngineManager manager = new ScriptEngineManager();
    static ScriptEngine engine = manager.getEngineByName("js");

    public static void main(String[] args) {

        List<Data> urlList = new ArrayList<Data>();
        //获取漫画总目录的html
        String html = HttpUtils.sendGet(rooturl+id+"/",null);
        if (html == null) {
            System.out.println("获取漫画列表失败!请检查该链接是否访问成功?url="+rooturl+id+"/");
            return ;
        }

        //获取漫画总目录的html
        String jsonStr = HttpUtils.sendPost(listHeaderUrl,"id="+id+"&id2="+1 );
        if (jsonStr == null) {
            System.out.println("获取漫画列表失败!请检查该链接是否访问成功?url="+listHeaderUrl);
            return ;
        }
        //将获取到的json进行解析
        com.alibaba.fastjson.JSONArray jsonArray = JSONArray.parseArray(jsonStr);
        for (int i = 0 ; i < jsonArray.size() ; i++){
            //添加到urlList列表中
            Data data = new Data();
            data.setUrl(rooturl+"/"+id+"/"+jsonArray.getJSONObject(i).getString("chapterid")+".html");
            data.setName(jsonArray.getJSONObject(i).getString("chaptername"));
            urlList.add(data);
        }
        //解析为dom
        Document document =  Jsoup.parse(html.toString());
        //获取有contentKey的那个div
        Element listDiv = document.getElementById("chapter-list1");
        if (listDiv == null){
            System.out.println("获取有contentKey的那个div为空,请检查html"+html);
            return ;
        }
        //查找所有的<a>标签
        Elements es = listDiv.getElementsByTag("a");
        for (Element e : es){
            //循环<a>标签,将url和Name都保存到urlList中
            Data data = new Data();
            data.setUrl(rooturl2+e.attr("href"));
            data.setName(e.html());
            urlList.add(data);
        }

        /**单线程,多线程请用线程池自行处理**/
        //循环打开每一章漫画,然后下载全部漫画图片
        for (Data data : urlList){
            //flag为false说明是某地方报错,这不是请求失败导致的,而是更严重的问题,所以应该停止工作,解决问题
            boolean flag = GetOneHtml(data.getName() , data.getUrl());
            if (!flag){
                return ;
            }
        }
    }

    /**
    * 通过章节名称和链接,进行全部图片获取和下载的方法
    */
    public static boolean GetOneHtml(String title , String href){

        String html = HttpUtils.sendGet(href,null);  //获取该章节漫画的初始html
        if (html == null || "".equals(html)) {
            System.out.println("网页为空!漫画章节"+title+"未能成功加载,请检查网络通畅或url是否拼错!href = "+href);
            return false;
        }
        //解析为dom,以下奇漫屋特有解析过程
        Document document =  Jsoup.parse(html.toString());
        Element e = document.getElementsByTag("script").get(3);

        //这是用来存储该章漫画所有图片url的数组
        ScriptObjectMirror array = null;
        try {
            //执行js,其中执行的就是页面中那串被混淆后的代码,执行结束会生成newImgs数组保存所有图片地址
            engine.eval(e.html());
            // 获取我们想调用那个方法所属的js对象
            //获取js变量array(数组)
            array=(ScriptObjectMirror) engine.get("newImgs");
//            System.out.println(array.getSlot(0));
//            System.out.println(array.size());
        } catch (ScriptException e1) {
            e1.printStackTrace();
            return false;
        }


        for (int i = 0 ; i < array.size() ;){
            //getSlot(int index)函数用于获取下标为index的值
            String jpgurl = (String)array.getSlot(i);
            try {

                /*使用写好的下载方法*/
                boolean flag = Downloadmanhua.getImg(jpgurl, (++i)+".jpg" , path , title , false);
                /*返回状态码非200可还有一次下载机会,可能是网络问题*/
                if (!flag) {
                    Downloadmanhua.getImg(jpgurl, i+".jpg", path , title , true);
                }
            } catch (IOException e3) {
                //失败最多可再重试一次
                try {
                    Downloadmanhua.getImg(jpgurl, i+".jpg" , path , title , true);
                } catch (IOException e2) {
                    //重试一次还失败,则写入fail.txt
                    try {
                        System.out.println("error,已输出至失败文档!");
                        //使用Writefailmsg方法写入到失败文档中
                        Downloadmanhua.Writefailmsg(path+"\\"+title,i+".jpg" , jpgurl);
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }

            }
        }
        return true;
    }

}

@Getter
@Setter
class Data{
    /**
     * 存储章节名称
     * */
    public String name;
    /**
     * 存储章节跳转链接
     * */
    public String url;

    @Override
    public String toString() {
        return "Data{" +
                "name='" + name + '\'' +
                ", url='" + url + '\'' +
                '}';
    }
}
posted @ 2023-01-29 16:25  忘月水心  阅读(1337)  评论(0)    收藏  举报