项目依赖
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,就可以得到不限响应长度的数据