使用jsoup爬取漫画页面,及获得壁纸

项目依赖

jsoup-1.11.3.jar

httpclient-4.5.2.jar

htmlunit-2.35.0.jar 

建议使用maven.因为jar包依赖过多

 

1.最近追剧斩.赤瞳之战动漫.看完后这个受不了啊./笑哭

听说漫画剧情好.所以打算看漫画.结果发现各种收费.很是无奈.找到了一篇在线观看的 .

http://***/manhua/6413/2000102864.html

但是觉得太麻烦.想在本地看.就想着能不能把图片下载下来.想了想.那肯定可以啊.就把多久没用 的jsoup有又看了下.

Jsoup是用于解析HTML,就类似XML解析器用于解析XML。 Jsoup它解析HTML成为真实世界的HTML。 它与jquery选择器的语法非常相似,并且非常灵活容易使用以获得所需的结果。

但是写的时候发现没有想得那么简单.页面src内容是动态出现的.导致获得的src="".

这种动态页面如果想要爬取,一般是

1.先找到使用浏览器刷新请求.得到响应的规律.

2.使用代码模拟浏览器去请求.将响应分析.这里我用的是httpClient4.5.4

抱着这样的想法开始分析页面,结果重头到尾也没找到他是什么时候给src赋值的.

只看到了它已经发起请求进行获取图片了.就在我准备放弃 的时候.观察这个请求地址好像看到了些规律

从这个地址看因该是文件服务器.,(至于为什么有%2F,它是经过encoder转码的时候的"/",还有类似的空格代表%20)

这个是请求地址,也就是点击获得某一章节的地址.

通过对比发现,路径src好像在一开始就得到了.也就是某一张的地址貌似和图片服务器一样,如红框中.

再假设文件服务器开始00/13是不变的.那就基本得到路径了.再多看几个发现,下一页.也只是数字加1

这样的话.要想下载这一章节,就找到了规律.试着这个规律去爬取试试

这个是解析地址.也就是我要获得的某个章节的.url

.

发现可行.但是目前发现每个章节规律也就是图片名不同(比如这个图是001.jpg,下个是aa002.jpg.这就没有规律了),

还有一个每章页数问题.,有了页数.才能知道PAGE_SIZE的大小,这样如果修改起来就太麻烦了.

随后发现了请求当中有一个.其实已经把所有的数据都返回了,包括page_size./笑哭

准备请求这个.然后重新解析,看看能不能获得到他的js中pic的数据,模拟浏览器的工具包也有不少.在这我用 的是htmlUnit.发起请求,

通过发起模拟请求ajax请求已经可以得到最上面页面中src的路径了(就是开头说的src=""的问题.).但这样.需要请求的次数过多.所以想想还是用直接从js中获得吧.

所以我希望从js中能够得到图片路径,在网上搜了下.也没有发现具体的如何操作js的.只能通过正则.或者js拆分了.

写了这么一个正则.用来匹配js中图片地址.

大概意思 : 中括号开头,包含一个至少一个数字.中括号一个.等于号,双引号,()代表一个组,括号里面是懒惰模式 的任意字符. 双引号,分号结束

 

ok,语言表达好像不大好.下面是具体代码.有什么问题欢迎评论.因为担心url地址暴露后.遭到频繁请求.所以就打码了.有需要测试.的可以私聊单独发url地址,爬取页面.这种东东并不是通用的.需要单独页面单独分析.

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.*;
import java.net.*;
import java.text.DecimalFormat;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author W.Hou
 * @date 2019/4/28 15:08
 * @des 解析赤瞳页面
 * 使用方法 1.修改解析地址
 *         2.章节不同修改,修改章节.会创建不同文件夹.
 * 为避免一些麻烦.本文将对应网页url中域名进行了用*代替.如需测试.可以单独私我要url        
 */
public class JsoupRedPupil {

    public static void main (String[] args) {
        //静态页面
        //spiderImage();
        //刚开始的规律下载
        //spiderDynamicPage();
        //正则匹配js下载
        sendRequest();

        //改用读取文件
        //multipleExecute();
    }

    /**解析地址*/
    public static String RED_URL = "http://***/manhua/6413/"+ 2000003929 + ".html";

    /**章节名*/
    public static String DIR_NAME = "\\44";

    /**下载地址*/
    public static final String DOWNLOAD_DIR = "D:\\workspace\\PanDownload\\caricature";

    /**图片服务器*/
    public static final String IMG_PREFIX = "http://***/img/";

    /**匹配图片文件名正则*/
    public static final String REG_EPR = "\\[\\d{1,}\\]=\"(.*?)\";";

    /**
     * 起初写的爬取页面,
     * 但是发现获得页面总是不完整
     * 想了想,可能是有些内容是ajax动态的
     * 所以需要模拟浏览器请求.获得完整的页面
     */
    public static void spiderPage () {
        try {
            //1.获得文档
            //默认 JSoup 的限制是 1024*1024,也就是 1M 的大小。
            //因此我们需要在连接时设置一下 maxBodySize ,设置为 0 表示不限制大小
            Document document = Jsoup.connect(RED_URL)
                    .header("Accept-Encoding", "gzip, deflate")
                    .userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3472.3 Safari/537.36")
                    .maxBodySize(0)
                    .timeout(600000)
                    .get();
            //2.获得图片元素
            Elements elements = document.select("table>tbody>tr>td:eq(0)>a>div>img");
            //3.遍历返回元素信息.
            for (Element image : elements) {
                System.out.println("src = " + image.attr("src"));
                System.out.println("width = " + image.attr("width"));
                System.out.println("height = " + image.attr("height"));
                System.out.println("alt = " + image.attr("alt"));

                System.out.println("==================================================");
            }
            //4.下载到本地目录
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 随便获取一个壁纸页面的url
     * 测试爬取静态页面,某网站的壁纸
     */
    public static void spiderImage () {
        try {
            //==================================================
            //1.获得文档
            //默认 JSoup 的限制是 1024*1024,也就是 1M 的大小。
            //因此我们需要在连接时设置一下 maxBodySize ,设置为 0 表示不限制大小
            //为了.避免不必要的麻烦.这里将url去掉了.
            Document document = Jsoup.connect("http://***/bizhi/ipad-dongman-all-hot")
                    .header("Accept-Encoding", "gzip, deflate")
                    .userAgent("Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3472.3 Safari/537.36")
                    .maxBodySize(0)
                    .timeout(600000)
                    .get();
            //2.获得图片元素,通过id先择期.的后代的一个类选择器中的 img标签
            Elements elements = document.select("#jsimg-bounced>.unit-wallpapar img");
            //3.遍历返回元素信息.
            for (Element image : elements) {
                System.out.println("src = " + image.attr("src"));
                System.out.println("alt = " + image.attr("alt"));


                //4.下载到本地目录
                imageRequest("D:\\workspace\\PanDownload\\caricature\\壁纸\\", image.attr("src"));
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 找到了些规律,
     * 但是发现规律不对.有变化,文件名可能不同
     * 这个是每次图片名字规律可循的时候.可以用.
     * 目前未使用
     */
    public static void spiderDynamicPage () {
        // 59 51
        /**没章多少页*/
        int page_size = 53;
        //1.将url解析成图片路径  
        String prefix = "/00/13";
        //写的比较麻烦.只是为了得到/6413/2000102864  .笑哭
        String center = RED_URL.substring(RED_URL.indexOf("/", RED_URL.indexOf("/", RED_URL.indexOf(".")) + 1), RED_URL.lastIndexOf("."));
        //得到域名.  通过indexOf嵌套,得到下一位.当然也可以导入个stringUtils的包.也有这种东西
        //String realmName = RED_URL.substring(RED_URL.indexOf("//") + 2, RED_URL.indexOf("/", RED_URL.indexOf("//") + 2));
        //得到路径
        DecimalFormat df = new DecimalFormat("000");
        for (int i = 1; i <= page_size; i++) {
            //获得页数
            String page = df.format(i);
            StringBuilder sb = new StringBuilder(36);
            sb.append("http://jpgcdn.dmzx.com/img")
                    .append(prefix)
                    .append(center)
                    .append("/")
                    .append(page)
                    .append(".jpg");
            System.out.println("imagePath = " + sb);

            //每个章节不同文件夹
            imageRequest(DOWNLOAD_DIR + DIR_NAME, sb.toString());
        }

    }

    /**
     * 页面图片下载
     * @param filePath 要存储的文件路径
     * @param imageUrl 图片的url
     */
    public static void imageRequest (String filePath, String imageUrl) {
        //获得文件名
        File dir = new File(filePath);
        //不存在就创建
        if (!dir.exists()) {
            dir.mkdir();
        }
        //获得文件名
        String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1, imageUrl.length());
        //try {
            //因为文件名可能有空格,和中文.需要转换,还有编码.但是空格在urlencoder中会变成+号
            //去掉,因为截取的字符串已经被转化了
            //String encode = URLEncoder.encode(fileName, "UTF-8");
            //imageUrl = imageUrl.substring(0, imageUrl.lastIndexOf("/") + 1) + encode.replaceAll("\\+", "\\%20");

            //替换域名, 有时候会出现不认识的主机名.本以为换成ip会好的.结果发现没用
            //String realmName = imageUrl.substring(imageUrl.indexOf("//") + 2, imageUrl.indexOf("/", imageUrl.indexOf("//") + 2));
            //InetAddress inetAddress = InetAddress.getByName(realmName);
            //String address = inetAddress.getHostAddress();
            //imageUrl = imageUrl.replaceAll(realmName, address);
            //System.out.println("address = " + address);
        //} catch (UnsupportedEncodingException e) {
            //e.printStackTrace();
        //}
        System.out.println("==================================================");
        //结果路径
        File file = new File(filePath + File.separator + fileName);
        InputStream inputStream = null;
        BufferedOutputStream outputStream = null;
        //请求
        try {
            URL url = new URL(imageUrl);
            URLConnection connection = url.openConnection();
            connection.setConnectTimeout(600000);
            inputStream = connection.getInputStream();
            //声明输出流
            outputStream = new BufferedOutputStream(new FileOutputStream(file));
            byte[] buf = new byte[1024];
            int l;
            while ( (l = inputStream.read(buf)) != -1) {
                outputStream.write(buf, 0, l);
            }
            outputStream.flush();
            inputStream.close();
            outputStream.close();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 使用htmlUntil模拟浏览器
     * 改用方法.通过获得js中的变量.其中记录有图片地址
     * 使用正则.获得.在拆分
     * @return 页面字符串
     */
    public static void sendRequest () {
        //1.获得webClient,并设置一些初始配置
        //创建chrome浏览器
        WebClient webClient = new WebClient(BrowserVersion.CHROME);
        //执行js错误是否抛出异常
        webClient.getOptions().setThrowExceptionOnScriptError(false);
        //当http状态非200是否抛出异常
        webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
        //是否开启css.不展示页面所以不用
        webClient.getOptions().setCssEnabled(false);
        //是否开启js
        webClient.getOptions().setJavaScriptEnabled(true);
        //关闭ssl安全访问校验,貌似没用.还是会报ssl异常.此异常可以忽略
        webClient.getOptions().setUseInsecureSSL(false);
        //设置连接时间,我在用的时候会出现连接超时.不知道是不是我的网络不好.
        webClient.getOptions().setTimeout(200000);
        //支持ajax
        webClient.setAjaxController(new NicelyResynchronizingAjaxController());
        webClient.getOptions().setActiveXNative(false);
        HtmlPage page = null;
        try {
            //2.通过client获得页面
            page = webClient.getPage(RED_URL);

            //因为使用了ajax,是异步的,所以需要执行时间,阻塞下
            webClient.waitForBackgroundJavaScript(50000);

            //加载完后.获得结果
            String xmlPage = page.asXml();

            //使用jsoup解析
            Document document = Jsoup.parse(xmlPage);
            //获得js中的数据.因为.这个js里有整章的图片.如果从src里获`得则是.需要循环几十次
            //通过观察.script中有language的只有这一个
            Element script = document.select("script[language='javascript']").first();
            String html = script.html();//这里要用html
            //jsoup无法解析js,使用正则
            Pattern pattern = Pattern.compile(REG_EPR);
            Matcher matcher = pattern.matcher(html);
            String imageAllPath = "";
            while (matcher.find()) {
                //图片路径
                String imagePath = matcher.group(1);
                //拼接前缀
                imageAllPath = IMG_PREFIX + imagePath;
                System.out.println("imageAllPath = " + imageAllPath);
                //执行下载
                imageRequest(DOWNLOAD_DIR + DIR_NAME, imageAllPath);
            }
            System.out.println("本集下载完成!!!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (webClient != null) {
                webClient.close();
            }
        }
    }

    /**
     * 执行过慢.写入文件夹.读取执行
     * 不好用,暂无使用
     */
    public static void multipleExecute () {
        InputStream inputStream = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            inputStream = new FileInputStream("jsoup_demo/resource/images");
            isr = new InputStreamReader(inputStream);
            br = new BufferedReader(isr);
            String str = "";
            int i = 54;
            while ((str = br.readLine()) != null) {
                sendRequest();
                System.out.println("下载的第" + i + "篇!");
                i--;
            }

            if (i == 42) {
                System.out.println("异常停止!");
                return;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close();
                }
                if (isr != null) {
                    isr.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

 

写这个遇到的一些问题

当使用jsoup访问http的接口时,但如果遇到不能完整获取响应内容时,一般有以下几个原因。

1. 网络异常,造成读取不全。这个很少发生,因为jsoup会报告exception

2. 网络超时,此时可以设置 connection.timeout(n) 增加超时时间。

3. 一切看起来都正常,也没有异常发生。 但是获取的数据就是少了一截。

 

这里主要将第三点。

可能分析获取到的数据,发现得到数据都是1024k。

如果获取到的数据不超过1024k,程序正常,得到的数据也正常。

一旦数据超过1024k时,数据就只有预期得到数据的前1024k字节了。

仔细查找jsoup的api 发现,默认设置下,jsoup最大获取的响应长度正好时1M。

所以这个时候只要设置 connection.maxBodySize(0),设置为0,就可以得到不限响应长度的数据

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值