记一次漫画网站解析案例 -奇漫屋
记一次漫画网站解析案例 -奇漫屋
目录
以下以 奇漫屋 网站的全职法师漫画作为样例讲解:以下以 奇漫屋 网站的全职法师漫画作为样例讲解:
全职法师链接:
全职法师_漫画最新章节免费阅读(下拉式) - 奇漫屋 (qiman57.com)
1.获取列表
1.分析
首先打开页面,我们能看到全部章节列表由图1.1.1中的两个模块构成:一个是打开页面就有的最新章节,另外一个是需要异步点击“查看更多章节”才会显示的全部章节。

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

另一个是点击“查看更多章节”请求对应API接口从而获取的剩余章节数据,为json格式,如图1.1.3。然后分析其请求包:
- 地址固定为:http://www.qiman57.com/bookchapter/;
- 请求方式post;
- 参数方式为params;
- 内容为
id=9845&id2=1,很容易可得,id值为该漫画(全职法师)的页面id,id2默认为1(可能意思是多个章节列表,通常为1)。

2.总结
因此,我们总结一下,获取漫画页面的HTML,可以获得部分章节名称和地址,在调取API,可获得剩余章节名称和地址。
2.获取具体章节全部漫画图片
1、分析
首先,打开奇漫屋的一个漫画的随便一章,打开F12,能够发现其中没有可疑的疑似图片链接的地址:
然后分析JS,首先,能够看到,HTML本身有一段JS,但是被混淆了,然后还引用了两个可疑的JS文件如图2.1.1所示:

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

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

可以看到,我们找到了保存全部图片链接的变量。
那么现在要做的,就是找到赋值imgs的地方,逆向找到其源头。其中,找到这段可以代码,如图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);

可以看到,我们首先将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.爬取流程图

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 + '\'' +
'}';
}
}

浙公网安备 33010602011771号