本文利用了线程池,动态链接函数,gson解析,注解,实现了多线程断点下载,并且在下载中途断网后会自动联网重新下载5次,5次连续下载失败后自动跳过该张图片,且对大部分的异常都进行了处理,本次数据来源于开源的天狗数据平台
首先解析动态函数接口的网址 http://www.tngou.net/tnfs/api/list ,默认加载20条数据,如果需要加载多条数据可以使用 http://www.tngou.net/tnfs/api/list?rows=%d 网址(%d表示你要访问几条数据),然后由该网址加载json数据,得到json对象,json对象中有图片的封面网址和size张图片集。源码是将它放在接口的注解中,反射获取后用gson解析,其中时间的转化需要适配器,json数据的格式为:
其中img为图片集的封面图片,其完整的网址为 http://tnfs.tngou.net/img (img为上图的数据),id里面有size张图片,需要下载下来,其完整的网址为http://www.tngou.net/tnfs/show/id ,id为上图的id,打开后的网页为xml数据,需要使用正则表达式解析
获取src和alt后,使用多线程下载这些数据,并并命名为alt,当然在下载过程中我们需要记录图片下载到哪里来了,省的网络异常或者自动断开后,又要重新下载,即数据加载从上次加载的地方继续加载。如果在加载中间通信失败,它会自动重新链接,如果通信失败超过5次,那么跳过该张图片,并会对该张图片进行提示。我这里在下载的时候会有进度条显示,可以看到你下载的进度【=====>>>>>>>>>>=====】,是不是很人性化啊。
该程序运行后的结果
哈哈,就是这么暴力,以后想看的话,直接运行程序,妈妈再也不要担心我要浏览网页了,,,其中.txt文件是用来记录图片下载到了哪里,省的下次又要下载,一次下载,终身免费使用。
.txt的数据格式为 封面.133390#性感气质美女短裙美腿私房照1.133390#性感气质美女短裙美腿私房照2.86636#性感气质美女短裙美腿私房照3.70731#
这里我也是使用正则表达式对数据进行处理的,当然写图片的流要用RandomAccessFile,可以从图片的任意位置写入,支持断点续传
下面为源码,没有参考任何人的代码,属于个人原创
//主函数
package com.duewang.zz;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Proiect:PhotoDownload
* Author:Duewang
* Time:2016/8/20 0020
*/
public class Main {
public static void main(String[] args) {
Assist assist = new Assist();//欢迎界面,输入你需要的数据条数
int rows=assist.welcome();
if(rows<=0) {
System.out.println("请输入大于0的整数");
System.exit(0);
}
HttpInter instance = Util.getInstance(HttpInter.class);//动态链接函数,gson解析,得到gson对象
Tangou photoList = instance.getList(rows);
if(photoList!=null){
int i=1;
System.out.println("本网站一共有:"+photoList.getTotal()+" 条数据,网页会随时跟新哦");
System.out.println("本次请求图片集的封面网址:");
for (PhotoNews photoMessage : photoList.getTangou()) {//输出你需要下载的真集封面网址
System.out.println(i+++"."+photoMessage.getTitle()+": http://tnfs.tngou.net/img"+photoMessage.getImg());
}
File path=new File("res/welfare");
Util util=new Util();
if (util.judgeFilePath(path)) {//如果存储文件目录存在,使用线程池开启多线程
ExecutorService pool= Executors.newFixedThreadPool(5);
for (i=0;i
//欢迎界面
package com.duewang.zz;
import java.util.Scanner;
/**
* Proiect:PhotoDownload
* Author:Duewang
* Time:2016/8/20 0020
*/
public class Assist {
public int welcome(){
System.out.println("欢迎使用duewang图片下载管理");
Scanner input=new Scanner(System.in);
System.out.print("你要下载多少条数据:");
int rows=input.nextInt();
input.close();
return rows;
}
}
package com.duewang.zz;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/19 0019
*/
public class Tangou {
@SerializedName("status")
private boolean status;
@SerializedName("total")
private int total;
@SerializedName("tngou")
private List tangou;
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public List getTangou() {
return tangou;
}
public void setTangou(List tangou) {
this.tangou = tangou;
}
}
package com.duewang.zz;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;
import java.util.Calendar;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/19 0019
*/
public class PhotoNews {
@SerializedName("id")
private long id;
@SerializedName("title")
private String title;
@SerializedName("gallertclass")
private int gallertclass;
@SerializedName("count")
private int count;
@SerializedName("size")
private int size;
@SerializedName("img")
private String img;
@JsonAdapter(CalendarType.class)
private Calendar time;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getGallertclass() {
return gallertclass;
}
public void setGallertclass(int gallertclass) {
this.gallertclass = gallertclass;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public Calendar getTime() {
return time;
}
public void setTime(Calendar time) {
this.time = time;
}
}
package com.duewang.zz;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Calendar;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/19 0019
*/
public class CalendarType extends TypeAdapter {
@Override
public void write(JsonWriter jsonWriter, Calendar calendar) throws IOException {
if (calendar!=null){
long timeInMillis = calendar.getTimeInMillis();
jsonWriter.value(timeInMillis);
}else {
jsonWriter.nullValue();
}
}
@Override
public Calendar read(JsonReader jsonReader) throws IOException {
if(jsonReader.hasNext()){
long l = jsonReader.nextLong();
Calendar instance = Calendar.getInstance();
instance.setTimeInMillis(l);
return instance;
}
return null;
}
}
//动态接口函数
package com.duewang.zz;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/19 0019
*/
public interface HttpInter {
@GET("http://www.tngou.net/tnfs/api/list?rows=%d")
Tangou getList(int rows);
}
//注解
package com.duewang.zz;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/19 0019
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {
String value();
}
//动态接口实现,多线程的主方法,记录与判断图片的下载,网络断开后可以自动重连5次,接着上次下载的地方继续下载
package com.duewang.zz;
import com.google.gson.Gson;
import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Proiect:PhotoDownload
* Author:Duewang
* Time:2016/8/20 0020
*/
public class Util {
public static T getInstance(Class t){
Object o = Proxy.newProxyInstance(t.getClassLoader(), new Class[]{t}, new MyHandler());
return (T) o;
}
private static class MyHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args){
GET get = method.getAnnotation(GET.class);
String url=String.format(get.value(),args);
Class> returnType = method.getReturnType();
boolean flag;
int count=0;
do{
if(count==5){//控制网络的访问次数
System.out.println("网络或者数据异常,我也无能为力");
break;
}
flag=false;
count++;
List list=linkWeb(url,0);
if(list.get(0).equals("true")){
int contentLength= (int) list.get(1);
InputStream is= (InputStream) list.get(2);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int length;
int sum=0;
byte[] buffer=new byte[102400];
try {
while ((length = is.read(buffer)) != -1) {
bos.write(buffer,0,length);
if(contentLength>1){
sum+=length;
System.out.print("\rjson数据获取中");
print(contentLength,sum);
}
}
is.close();
Gson gson=new Gson();
System.out.println("\n开始解析json数据");
return gson.fromJson(bos.toString("UTF-8"),returnType);
} catch (IOException e) {
System.out.println("网络数据转换失败,正在重新链接获取新数据");
flag=true;//当链接断开后,在次请求链接
}
}else{
System.out.println("网络异常,正在重新链接");
flag=true;//当网络访问失败后,在次请求链接
}
}while(flag);
return null;
}
}
public static void print(int contentLength, int sum){
double percent;
int p;
int i;
System.out.print("[");
percent= 1.0*sum/contentLength;
p= (int) (percent*20);
for(i=0;i<20;i++){
if(i");
}else{
System.out.print(" ");
}
}
System.out.print("]");
}
//返回网络链接的有效数据
public static List linkWeb(String url,int start){
HttpURLConnection connection;
List list=new ArrayList();
try {
connection = (HttpURLConnection) new URL(url).openConnection();
Map> headerFields = connection.getHeaderFields();
List range=headerFields.get("Accept-Ranges");
connection.disconnect();
connection = (HttpURLConnection) new URL(url).openConnection();
if(range!=null){//是否支持网络断点下载
connection.setRequestProperty("Range","bytes="+start+"-");
}
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.setReadTimeout(70000);
connection.setConnectTimeout(20000);
int responseCode=connection.getResponseCode();
int contentLength = connection.getContentLength();
if(responseCode==200||responseCode==206)
{
InputStream is = connection.getInputStream();
list.add("true");
list.add(contentLength);
list.add(is);
if(range!=null){
list.add("true");
}else{
list.add("false");
}
} else{
list.add("false");
}
} catch (IOException e) {
list.add("error");
}
return list;
}
public boolean judgeFilePath(File file){
if (file.exists()) {
if (file.isDirectory()) {
return true;
}else{
System.out.println("你的输入的不是一个存储目录");
}
}else{
System.out.println("你输入的存储目录不存在");
}
return false;
}
public void createFilePath(File file){
if (!file.exists()) {
file.mkdir();
}
}
public boolean createFile(File file){
if(!file.exists()){
try {
file.createNewFile();
return true;
} catch (IOException e) {
System.out.println("无法创建文件:"+file.toString());
return false;
}
}
return true;
}
public boolean create(File path,String fileName){
path = new File(path.toString() + "/" + fileName);
createFilePath(path);
File file=new File(path.toString()+"/"+fileName+".txt");
if(createFile(file)){
return true;
}
return false;
}
public void downloadThreadPoint(File path,Tangou photoList,int index){
String fileName = photoList.getTangou().get(index).getTitle();
if(create(path,fileName)){
String url="http://tnfs.tngou.net/img"+photoList.getTangou().get(index).getImg(),str;
long id=photoList.getTangou().get(0).getId();
try {
FileInputStream fis=new FileInputStream(path.toString()+"/"+fileName+"/"+fileName+".txt");
byte[] bytes=new byte[102400];
int length;
int start;
length=fis.read(bytes);
if(length>1) {
str=new String(bytes,0,length,"UTF-8");
}else{
str="";
}
fis.close();
Pattern compile = Pattern.compile("封面[.](.+?)#");
Matcher matcher = compile.matcher(str);
if(matcher.find()){
start=Integer.parseInt(matcher.group(1));
}else{
start=0;
}
int count=0;
boolean flag;
int sum=0;
do{
if(count==5){
System.out.println("网络或者数据异常,封面图片加载我也无能为力");
break;
}
flag=false;
count++;
RandomAccessFile raf=new RandomAccessFile(new File(path.toString()+"/"+fileName+"/"+"封面"+".jpg"),"rw");
List list=linkWeb(url,start);
if(list.get(0).equals("true")){
int contentLength= (int) list.get(1);
InputStream is= (InputStream) list.get(2);
if(list.get(3).equals("true")){
raf.seek(start);
sum=start;
}
if(contentLength>1){
contentLength+=sum;
}
try {
while ((length = is.read(bytes)) != -1) {
raf.write(bytes,0,length);
sum+=length;
if(start==0){
str=str+"封面."+sum+"#";
start=sum;
}else{
str=str.replaceAll("封面[.](.+?)#","封面."+sum+"#");
}
FileOutputStream fos=new FileOutputStream(path.toString()+"/"+fileName+"/"+fileName+".txt");
fos.write(str.getBytes("UTF-8"));
fos.close();
if(contentLength>1){
System.out.print("\r图片封面加载中");
print(contentLength,sum);
}
}
raf.close();
if(contentLength>1){
System.out.println("\n第"+(index+1)+"条数据的封面图片加载完毕");
}else{
System.out.println("第"+(index+1)+"条数据的封面图片加载完毕");
}
} catch (IOException e) {
System.out.println("网络数据转换失败,正在重新链接获取新数据");
flag=true;//当链接断开后,在次请求链接
}
}else if(list.get(0).equals("false")){
System.out.println("第"+(index+1)+"条数据的封面图片已经下载完毕,无需下载");
break;
}else{
System.out.println("网络异常,正在重新链接");
flag=true;
}
}while(flag);
} catch (FileNotFoundException e) {
System.out.println(path.toString()+"/"+fileName+"/"+fileName+".txt文件打开失败");
} catch (IOException e) {
System.out.println(path.toString()+"/"+fileName+"/"+fileName+"文件读取异常");
}
url="http://www.tngou.net/tnfs/show/"+id;
int count=0;
boolean flag;
do{
flag=false;
count++;
List list=linkWeb(url,0);
if(list.get(0).equals("true")){
int contentLength= (int) list.get(1);
InputStream is= (InputStream) list.get(2);
int sum=0;
int length;
byte[] buffer=new byte[102400];
ByteArrayOutputStream bos=new ByteArrayOutputStream();
try {
if(contentLength<2){
System.out.println("真集网址加载中[=====>>>>>>>>>>=====]");
}
while ((length = is.read(buffer)) != -1) {
bos.write(buffer,0,length);
if(contentLength>1){
sum+=length;
System.out.print("\r真集网址加载中");
print(contentLength,sum);
}
}
str=bos.toString("UTF-8");
is.close();
bos.close();
Pattern compile = Pattern.compile("");
Matcher matcher = compile.matcher(str);
if(contentLength>1){
System.out.println("\n真集网址已全部解析,正在全力加载真集图片");
}else{
System.out.println("真集网址已全部解析,正在全力加载真集图片");
}
while (matcher.find()) {
url=matcher.group(2);
String fileNameTemp=matcher.group(3);
FileInputStream fis=new FileInputStream(path.toString()+"/"+fileName+"/"+fileName+".txt");
int start;
length=fis.read(buffer);
if(length>1) {
str=new String(buffer,0,length,"UTF-8");
}else{
str="";
}
fis.close();
Pattern compileTemp = Pattern.compile(fileNameTemp+"[.](.+?)#");
Matcher matcherTemp = compileTemp.matcher(str);
if(matcherTemp.find()){
start=Integer.parseInt(matcherTemp.group(1));
}else{
start=0;
}
count=0;
do{
if(count==5){
System.out.println("\n网络或者数据异常,真集的加载我也无能为力,已为你跳过该张真集:"+fileNameTemp);
break;
}
flag=false;
count++;
RandomAccessFile raf=new RandomAccessFile(new File(path.toString()+"/"+fileName+"/"+fileNameTemp+".jpg"),"rw");
list=linkWeb(url,start);
if(list.get(0).equals("true")){
contentLength= (int) list.get(1);
is= (InputStream) list.get(2);
if(list.get(3).equals("true")){
raf.seek(start);
sum=start;
}
if(contentLength>1){
contentLength+=sum;
}
try {
while ((length = is.read(buffer)) != -1) {
raf.write(buffer,0,length);
sum+=length;
if(start==0){
str=str+fileNameTemp+"."+sum+"#";
start=sum;
}else{
str=str.replaceAll(fileNameTemp+"[.](.+?)#",fileNameTemp+"."+sum+"#");
}
FileOutputStream fos=new FileOutputStream(path.toString()+"/"+fileName+"/"+fileName+".txt");
fos.write(str.getBytes("UTF-8"));
fos.close();
if(contentLength>1){
System.out.print("\r“"+fileNameTemp+"”正在加载中");
print(contentLength,sum);
}
}
raf.close();
if(contentLength>1){
System.out.println("");
}
} catch (IOException e) {
System.out.println("网络数据转换失败,正在重新链接获取新数据:"+fileNameTemp);
flag=true;
}
}else if(list.get(0).equals("false")){
System.out.println("该张真集图片已经下载完毕,无需下载:"+fileNameTemp);
break;
}else{
System.out.println("网络异常,正在重新链接");
flag=true;
}
}while(flag);
}
System.out.println("\n本条数据已经全部下载完成:"+fileName);
} catch (IOException e) {
System.out.println("网络数据读取失败,正在重新链接获取新数据");
flag=true;
}
}else{
System.out.println("网络异常,正在重新链接真集网址");
}
if(count==4){
System.out.println("网络或者数据异常,真集网址加载我也无能为力");
break;
}
}while(flag);
}
}
}
//多线程实现Runnable接口
package com.duewang.zz;
import java.io.File;
/**
* Proiect:Day18
* Author:Duewang
* Time:2016/8/20 0020
*/
public class DownThread implements Runnable {
private File path;
private int index;
private Tangou list;
public DownThread(File path, int index, Tangou list) {
this.path = path;
this.index = index;
this.list = list;
}
@Override
public void run() {
Util util=new Util();
util.downloadThreadPoint(path,list,index);
}
}