├── src ├── test │ ├── java │ │ └── .gitkeep │ └── resources │ │ └── .gitkeep └── main │ ├── resources │ ├── .gitkeep │ └── log4j.properties │ └── java │ └── cn │ └── blackgray │ └── douban │ └── album │ └── download │ ├── resources │ ├── html │ │ ├── bg.jpg │ │ ├── half-d.png │ │ ├── half-l.png │ │ └── numberBg.png │ └── images │ │ └── ui │ │ ├── doupic.jpg │ │ ├── face.jpg │ │ └── icon-dir.gif │ ├── test │ ├── TestGetSource.java │ ├── TestGetImageSize.java │ ├── TestCelebrityRegex.java │ ├── TestImageExists.java │ ├── TestImageSort.java │ ├── TestJarFile.java │ ├── TestFailFileDocCreate.java │ ├── TestCheckExists.java │ ├── TestDownload.java │ ├── TetsInputStream.java │ ├── TestImageLengthCompare.java │ └── TestReflect.java │ ├── service │ ├── image │ │ ├── ImageInfo.java │ │ ├── ImageListComparator.java │ │ └── ImageUtils.java │ ├── handler │ │ ├── finder │ │ │ ├── IAlbumURLFinder.java │ │ │ └── impl │ │ │ │ ├── PersonageIndexFinder.java │ │ │ │ ├── MovieIndexFinder.java │ │ │ │ ├── OnlineIndexFinder.java │ │ │ │ ├── MovieAlbumListFinder.java │ │ │ │ ├── AlbumDouListFinder.java │ │ │ │ └── AlbumListFinder.java │ │ ├── handler │ │ │ ├── DefaultAlbumHandler.java │ │ │ ├── PersonageAlbumHandler.java │ │ │ ├── SiteAlbumHandler.java │ │ │ ├── MovieAlbumHandler.java │ │ │ ├── OnlineAlbumHandler.java │ │ │ ├── UserAlbumHandler.java │ │ │ └── SitePublicAlbumHandler.java │ │ ├── AlbumHandler.java │ │ ├── PageAnalyzer.java │ │ └── AlbumHandlerFactory.java │ ├── DownloadService.java │ ├── download │ │ ├── DownloadManager.java │ │ ├── DownloadFailManager.java │ │ ├── DownloadThread.java │ │ └── DownloadProcessing.java │ └── AlbumFactory.java │ ├── common │ ├── utils │ │ ├── CommonUtils.java │ │ ├── FileUtils.java │ │ ├── ReflectUtils.java │ │ ├── HTMLUtils.java │ │ ├── LoginUtils.java │ │ └── URLUtils.java │ ├── VersionChecker.java │ ├── Console.java │ └── Common.java │ ├── ui │ ├── LoginQrFrame.java │ ├── component │ │ ├── DropTextArea.java │ │ └── JPopupTextArea.java │ └── FailFileFrame.form │ ├── model │ ├── BGImage.java │ └── Album.java │ └── version.info ├── .settings ├── org.eclipse.wst.jsdt.ui.superType.name ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.m2e.core.prefs ├── org.eclipse.wst.common.project.facet.core.xml ├── org.eclipse.core.resources.prefs ├── com.genuitec.eclipse.core.prefs ├── .jsdtscope └── org.eclipse.jdt.core.prefs ├── README.md ├── .gitignore ├── .project ├── .classpath └── pom.xml /src/test/java/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/html/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/html/bg.jpg -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/html/half-d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/html/half-d.png -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/html/half-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/html/half-l.png -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/html/numberBg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/html/numberBg.png -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/images/ui/doupic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/images/ui/doupic.jpg -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/images/ui/face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/images/ui/face.jpg -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/resources/images/ui/icon-dir.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackGray/douban-album/master/src/main/java/cn/blackgray/douban/album/download/resources/images/ui/icon-dir.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # douban-album 2 | Java版豆瓣相册下载工具(桌面版) 3 | 4 | 特点:
5 | 1.可下载图片描述。
6 | 2.可下载大图。
7 | 3.可生成浏览网页,网页支持查询。
8 | 4.支持Windows,Mac OS等系统。
9 | 10 | ------------------------------------ 11 | 12 | 详细说明请查看日志: 13 | https://www.douban.com/note/206320326/ 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | bin 15 | /target/ 16 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/main/java=UTF-8 3 | encoding//src/main/java/cn/blackgray/douban/album/download/resources/html/Template.html=UTF-8 4 | encoding//src/main/resources=UTF-8 5 | encoding//src/test/java=UTF-8 6 | encoding//src/test/resources=UTF-8 7 | encoding/=UTF-8 8 | -------------------------------------------------------------------------------- /.settings/com.genuitec.eclipse.core.prefs: -------------------------------------------------------------------------------- 1 | #Mon Dec 30 01:34:05 CST 2013 2 | clsCnt=45 3 | eclipse.preferences.version=1 4 | locCnt=5547 5 | pkgCnt=23 6 | sTime=2013\u5E7412\u670830\u65E5 \u661F\u671F\u4E00 \u4E0A\u534801\u65F634\u520605\u79D2 CST 7 | srcCnt=1 8 | validator.Checked=src/cn/blackgray/douban/album/download/resources/html/Template.html 9 | validator.Unchecked= 10 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestGetSource.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 4 | 5 | public class TestGetSource { 6 | 7 | public static void main(String[] args) { 8 | String source = URLUtils.readSource("http://www.douban.com/photos/album/51681522/"); 9 | System.out.println(source); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestGetImageSize.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import cn.blackgray.douban.album.download.service.image.ImageInfo; 4 | import cn.blackgray.douban.album.download.service.image.ImageUtils; 5 | 6 | 7 | public class TestGetImageSize { 8 | 9 | public static void main(String[] args) { 10 | ImageInfo rawInfo = ImageUtils.getImageSize("/Users/blackgray/Downloads/p2626439856.jpg"); 11 | System.out.println(rawInfo); 12 | 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = info,a2 2 | log4j.appender.a1 = org.apache.log4j.RollingFileAppender 3 | log4j.appender.a1.file=../Log4j.txt 4 | log4j.appender.a1.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.a1.MaxFileSize=1024KB 6 | log4j.appender.a1.MaxBackupIndex=2000 7 | log4j.appender.a1.layout.ConversionPattern="%-4r %d{yyyy-MM-dd HH\:mm\:ss} [%t] %-5p %c %x - %m%n 8 | log4j.appender.a2 = org.apache.log4j.ConsoleAppender 9 | log4j.appender.a2.layout = org.apache.log4j.PatternLayout 10 | log4j.appender.a2.layout.ConversionPattern=\u3010%-5p\u3011"%-2r %d{yyyy-MM-dd HH\:mm\:ss} %c[%t] %n %x\u3010^_^\u3011%m%n -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/image/ImageInfo.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.image; 2 | 3 | /** 4 | * 图片信息MODEL - 用于对比图片宽高尺寸 5 | * @author BlackGray 6 | */ 7 | public class ImageInfo { 8 | 9 | private int height; 10 | private int width; 11 | 12 | public ImageInfo(int width, int height) { 13 | super(); 14 | this.height = height; 15 | this.width = width; 16 | } 17 | 18 | public int getWidth() { 19 | return width; 20 | } 21 | 22 | public int getHeight() { 23 | return height; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "height: " + height + ", width: " + width; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/IAlbumURLFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 相册URL获取器接口 - 用于获取相册地址(如根据用户首页、活动页首页等获取对应下属相册地址) 7 | * @author BlackGray 8 | */ 9 | public interface IAlbumURLFinder { 10 | 11 | 12 | /** 13 | * 获取相册URL正则 14 | * @return 15 | */ 16 | public String getURLRegex(); 17 | 18 | /** 19 | * 根据正则获取相册URL 20 | * @param url 21 | * @return 22 | */ 23 | public List findAlbumURL(String url); 24 | 25 | /** 26 | * 获取相册地址失败提醒消息 27 | * 部分相册随豆瓣改版影响,可能无法访问,暂时保留相关查询类 28 | * 但此方法若返回值不为null,认为无法解析获取,并在界面显示提醒文字 29 | * @return 30 | */ 31 | public String getFindFailMsg(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestCelebrityRegex.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class TestCelebrityRegex { 7 | 8 | public static void main(String[] args) { 9 | String url = "http://movie.douban.com/celebrity/1040543/photos/"; 10 | String regex = url + "\\?(\\w+=\\w+&*(amp;)*)+"; 11 | Pattern p = Pattern.compile(regex); 12 | Matcher m = p.matcher("http://movie.douban.com/celebrity/1040543/photos/?type=C&start=0&sortby=all&size=a&subtype=a http://movie.douban.com/celebrity/1040543/photos/?type=C&start=0&sortby=all&size=a&subtype=a"); 13 | while (m.find()) { 14 | System.out.println(m.group()); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestImageExists.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.IOException; 5 | import java.net.MalformedURLException; 6 | 7 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 8 | 9 | public class TestImageExists { 10 | 11 | public static void main(String[] args) throws MalformedURLException, FileNotFoundException, IOException { 12 | boolean i = URLUtils.exists("http://img4.douban.com/view/photo/photo/public/p2112306858.jpg"); 13 | System.out.println(i); 14 | System.out.println("=========="); 15 | i = URLUtils.exists("http://img3.douban.com/view/photo/photo/public/p1215810444.jpg"); 16 | System.out.println(i); 17 | 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | /** 4 | * 公共工具类 5 | * @author BlackGray 6 | * @createTime 2022-10-19 01:18:31 7 | * 8 | */ 9 | public class CommonUtils { 10 | 11 | /** 12 | * 判断当前系统是否为MacOS 13 | */ 14 | public static boolean isMacOS() { 15 | String osName = System.getProperty("os.name").toLowerCase(); 16 | if (osName.indexOf("mac") >= 0) { 17 | return true; 18 | }else { 19 | return false; 20 | } 21 | } 22 | 23 | /** 24 | * 判断当前系统是否为Windows系统 25 | */ 26 | public static boolean isWindows() { 27 | String osName = System.getProperty("os.name").toLowerCase(); 28 | if (osName.indexOf("windows") >= 0) { 29 | return true; 30 | }else { 31 | return false; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/VersionChecker.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common; 2 | 3 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 4 | 5 | 6 | 7 | /** 8 | * 版本检查器 9 | * 用于检查相册下载器是否为最新版本 10 | * @author BlackGray 11 | * 12 | */ 13 | public class VersionChecker { 14 | 15 | /** 16 | * 获取版本信息 17 | * @return 18 | */ 19 | public static boolean haveNewVersion(){ 20 | String note = URLUtils.readSource(Common.URL_HELP); 21 | int begin = note.indexOf("最新版本:"); 22 | int end = note.indexOf("
", begin); 23 | String version = note.substring(begin + 5 ,end); 24 | if (Common.VERSION.equals(version)) { 25 | return false; 26 | }else{ 27 | return true; 28 | } 29 | } 30 | 31 | public static void main(String[] args) { 32 | System.out.println(haveNewVersion()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestImageSort.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import cn.blackgray.douban.album.download.service.image.ImageListComparator; 8 | 9 | public class TestImageSort { 10 | 11 | public static void main(String[] args) { 12 | 13 | List imageList = new ArrayList(); 14 | imageList.add("https://img9.doubanio.com/view/photo/l/public/p1154450175.jpg"); 15 | imageList.add("https://img2.doubanio.com/view/photo/l/public/p656889432.jpg"); 16 | imageList.add("https://img1.doubanio.com/view/photo/l/public/p1079652422.jpg"); 17 | 18 | //排序 19 | Collections.sort(imageList,new ImageListComparator()); 20 | 21 | for (int i = 0; i < imageList.size(); i++) { 22 | System.out.println(imageList.get(i)); 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestJarFile.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.IOException; 4 | import java.util.Enumeration; 5 | import java.util.jar.JarEntry; 6 | import java.util.jar.JarFile; 7 | 8 | public class TestJarFile { 9 | 10 | public static void main(String[] args) throws IOException { 11 | 12 | JarFile jar = new JarFile("C:/Users/Think/Desktop/x.jar"); 13 | String pakageName = "cn.blackgray.douban.album.download.service.handler.finder.impl"; 14 | pakageName = pakageName.replaceAll("\\.", "/"); 15 | 16 | Enumeration e = jar.entries(); 17 | while (e.hasMoreElements()) { 18 | JarEntry jarEntry = (JarEntry) e.nextElement(); 19 | String entryName = jarEntry.getName(); 20 | if (entryName.indexOf(pakageName) != -1 && entryName.endsWith(".class")) { 21 | System.out.println("name:" + entryName); 22 | } 23 | } 24 | jar.close(); 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 14 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning 15 | org.eclipse.jdt.core.compiler.release=disabled 16 | org.eclipse.jdt.core.compiler.source=1.8 17 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/PersonageIndexFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 7 | 8 | 9 | /** 10 | * 相册URL获取器实现 - 根据人士首页获取相册 11 | */ 12 | public class PersonageIndexFinder implements IAlbumURLFinder{ 13 | 14 | @Override 15 | public List findAlbumURL(String url) { 16 | // https://www.douban.com/personage/27246297/ 17 | // https://www.douban.com/personage/27246297/photos/ 18 | List list = new ArrayList(); 19 | list.add(url + "photos/"); 20 | return list; 21 | } 22 | 23 | @Override 24 | public String getURLRegex() { 25 | return "(http|https)://www.douban.com/personage/\\d+/"; 26 | } 27 | 28 | @Override 29 | public String getFindFailMsg() { 30 | return null; 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestFailFileDocCreate.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.TreeMap; 7 | 8 | import cn.blackgray.douban.album.download.service.download.DownloadFailManager; 9 | 10 | public class TestFailFileDocCreate { 11 | 12 | public static void main(String[] args) { 13 | 14 | 15 | List albumPathList = new ArrayList(); 16 | albumPathList.add("/Users/blackgray/Documents/temp/test-douban"); 17 | 18 | Map failFileMap = new TreeMap(); 19 | failFileMap.put("https://img1.doubanio.com/view/photo/l/public/p1932868158.jpg", "/Users/blackgray/Documents/temp/test-douban"); 20 | failFileMap.put("https://img1.doubanio.com/view/photo/raw/public/p1932870238.jpg", "/Users/blackgray/Documents/temp/test-douban/raw"); 21 | 22 | DownloadFailManager.createAlbumFailFileDoc(albumPathList, failFileMap); 23 | 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/image/ImageListComparator.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.image; 2 | 3 | import java.util.Comparator; 4 | 5 | import cn.blackgray.douban.album.download.common.Common; 6 | 7 | /** 8 | * 图片对比排序 9 | * @author BlackGray 10 | */ 11 | public class ImageListComparator implements Comparator{ 12 | 13 | @Override 14 | public int compare(String p1, String p2) { 15 | String regex = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 16 | p1 = p1.substring(p1.lastIndexOf("/") + 1); // p1473298817.jpg 17 | p2 = p2.substring(p2.lastIndexOf("/") + 1); 18 | if (p1.matches(regex) && p2.matches(regex)) { 19 | long i1 = Long.parseLong(p1.substring(1,p1.lastIndexOf("."))); //1473298817 20 | long i2 = Long.parseLong(p2.substring(1,p2.lastIndexOf("."))); 21 | if(i1 < i2) { 22 | return 1; 23 | }else if(i1 == i2){ 24 | return 0; 25 | }else { 26 | return -1; 27 | } 28 | }else{ 29 | return p1.compareTo(p2); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | douban-album 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.wst.jsdt.core.javascriptValidator 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.m2e.core.maven2Nature 31 | org.eclipse.jdt.core.javanature 32 | org.eclipse.wst.jsdt.core.jsNature 33 | org.eclipse.wst.common.project.facet.core.nature 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestCheckExists.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.IOException; 4 | import java.net.HttpURLConnection; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | 8 | public class TestCheckExists { 9 | 10 | public static void main(String[] args) { 11 | 12 | for (int i = 0; i < 1000; i++) { 13 | try { 14 | URL url = new URL("http://img3.douban.com/view/photo/photo/public/p1601592092.jpg"); 15 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 16 | HttpURLConnection.setFollowRedirects(false); 17 | 18 | /* 设置 URL 请求的方法, GET POST HEAD OPTIONS PUT DELETE TRACE 以上方法之一是合法的,具体取决于协议的限制。*/ 19 | conn.setRequestMethod("HEAD");; 20 | System.out.println(i + " = " + conn.getResponseCode()); 21 | //System.out.println(HttpURLConnection.HTTP_OK); 22 | 23 | } catch (MalformedURLException e) { 24 | e.printStackTrace(); 25 | } catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/MovieIndexFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 7 | 8 | /** 9 | * 相册URL获取器实现 - 根据电影首页获取相册 10 | */ 11 | public class MovieIndexFinder implements IAlbumURLFinder{ 12 | 13 | @Override 14 | public List findAlbumURL(String url) { 15 | //首页 http://movie.douban.com/subject/3652297/ 16 | //相册 http://movie.douban.com/subject/3652297/photos?type=S 17 | //相册 http://movie.douban.com/subject/3652297/photos?type=R 18 | //相册 http://movie.douban.com/subject/3652297/photos?type=W 19 | List list = new ArrayList(); 20 | list.add(url + "photos?type=S"); 21 | list.add(url + "photos?type=R"); 22 | list.add(url + "photos?type=W"); 23 | return list; 24 | 25 | } 26 | 27 | @Override 28 | public String getURLRegex() { 29 | return "(http|https)://movie.douban.com/subject/\\d+/"; 30 | } 31 | 32 | public static void main(String[] args) { 33 | System.out.println(new MovieAlbumListFinder().findAlbumURL("(http|https)://movie.douban.com/subject/3652297/"));; 34 | } 35 | 36 | @Override 37 | public String getFindFailMsg() { 38 | return null; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/Console.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.logging.Logger; 6 | 7 | import javax.swing.JTextArea; 8 | 9 | /** 10 | * 控制台日志输出 11 | * @author BlackGray 12 | */ 13 | public class Console { 14 | 15 | public static JTextArea consoleArea; 16 | public static Long count = 0L; 17 | private static Integer MAX_LINE = 1000; 18 | private static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); 19 | private static boolean isDebug = true; 20 | 21 | public static void print(String text){ 22 | if (isDebug) { 23 | System.out.println(text); 24 | } 25 | if (consoleArea != null) { 26 | synchronized (consoleArea) { 27 | int lineCount = consoleArea.getLineCount(); 28 | if (lineCount >= MAX_LINE) { 29 | int end = 0; 30 | try { 31 | end = consoleArea.getLineEndOffset(MAX_LINE); 32 | } catch (Exception e) { 33 | } 34 | consoleArea.replaceRange("", 0, end); 35 | } 36 | consoleArea.append(sdf.format(new Date()) + " | " + text + "\r\n"); 37 | consoleArea.setCaretPosition(consoleArea.getText().length()); //滚动条显示到最下方 38 | count++; 39 | } 40 | } 41 | } 42 | 43 | public static void setArea(JTextArea consoleArea) { 44 | Console.consoleArea = consoleArea; 45 | } 46 | 47 | public static void print(Logger log, String msg) { 48 | log.info(msg); 49 | print(msg); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/OnlineIndexFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 9 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 10 | 11 | /** 12 | * 相册URL获取器实现 - 根据活动首页获取相册 13 | */ 14 | public class OnlineIndexFinder implements IAlbumURLFinder{ 15 | 16 | @Override 17 | public List findAlbumURL(String url) { 18 | //首页 http://www.douban.com/online/11127307/ 19 | //相册 http://www.douban.com/online/11127307/album/72416214/ 20 | //相册 http://www.douban.com/online/11023488/album/64765014/?start=0 21 | //照片 http://www.douban.com/online/11127307/photo/1573338563/ 22 | List list = new ArrayList(); 23 | String source = URLUtils.readSource(url); 24 | String regex = url + "album/\\d+"; 25 | Pattern p = Pattern.compile(regex); 26 | Matcher m = p.matcher(source); 27 | if (m.find()) { 28 | String str = m.group(); 29 | if (str.endsWith("/")) { 30 | list.add(str); 31 | }else{ 32 | list.add(str + "/"); 33 | } 34 | } 35 | return list; 36 | 37 | } 38 | 39 | @Override 40 | public String getURLRegex() { 41 | return "(http|https)://www.douban.com/online/\\d+/"; 42 | } 43 | 44 | @Override 45 | public String getFindFailMsg() { 46 | return null; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/MovieAlbumListFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 7 | 8 | /** 9 | * 相册URL获取器实现 - 根据电影相册列表获取相册 10 | */ 11 | public class MovieAlbumListFinder implements IAlbumURLFinder{ 12 | 13 | @Override 14 | public List findAlbumURL(String url) { 15 | //首页 http://movie.douban.com/subject/3652297/all_photos 16 | //相册 http://movie.douban.com/subject/3652297/photos?type=S 17 | //相册 http://movie.douban.com/subject/3652297/photos?type=R 18 | //相册 http://movie.douban.com/subject/3652297/photos?type=W 19 | url = url.substring(0,url.lastIndexOf("all_photos")); 20 | List list = new ArrayList(); 21 | list.add(url + "photos?type=S"); 22 | list.add(url + "photos?type=R"); 23 | list.add(url + "photos?type=W"); 24 | return list; 25 | 26 | } 27 | 28 | @Override 29 | public String getURLRegex() { 30 | return "(http|https)://movie.douban.com/subject/\\d+/all_photos(/)*"; 31 | } 32 | 33 | public static void main(String[] args) { 34 | MovieAlbumListFinder finder = new MovieAlbumListFinder(); 35 | String url = "(http|https)://movie.douban.com/subject/3652297/all_photos/"; 36 | System.out.println(url.matches(finder.getURLRegex())); 37 | System.out.println(finder.findAlbumURL(url)); 38 | } 39 | 40 | @Override 41 | public String getFindFailMsg() { 42 | return null; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestDownload.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.InputStream; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | 11 | public class TestDownload { 12 | 13 | public static void main(String[] args) { 14 | try { 15 | URL url = new URL("https://img1.doubanio.com/view/photo/l/public/p2257588668.jpg"); 16 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 17 | HttpURLConnection.setFollowRedirects(false); 18 | 19 | conn.setConnectTimeout(10*1000); //设置连接超时 20 | conn.setReadTimeout(10*1000); //设置读取超时 21 | conn.setDoInput(true); //默认为true 22 | conn.connect(); 23 | System.out.println("【getContentLength】" + conn.getContentLength()); 24 | System.out.println("【getContentType】" + conn.getContentType()); 25 | InputStream in = conn.getInputStream(); 26 | 27 | 28 | 29 | BufferedInputStream inputStream = new BufferedInputStream(in); 30 | BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(new File("/Users/blackgray/Desktop/douban/test.jpg"))); 31 | byte[] data = new byte[1024]; 32 | int n = 0; 33 | while ((n = inputStream.read(data)) != -1) { 34 | outputStream.write(data,0,n); 35 | } 36 | outputStream.flush(); 37 | in.close(); 38 | inputStream.close(); 39 | outputStream.close(); 40 | 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/ui/LoginQrFrame.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.ui; 2 | 3 | import java.awt.EventQueue; 4 | 5 | import javax.swing.JFrame; 6 | import javax.swing.JPanel; 7 | import javax.swing.border.EmptyBorder; 8 | 9 | import cn.blackgray.douban.album.download.common.utils.LoginUtils; 10 | 11 | import java.awt.event.WindowAdapter; 12 | import java.awt.event.WindowEvent; 13 | 14 | public class LoginQrFrame extends JFrame { 15 | 16 | private static final long serialVersionUID = 1L; 17 | private JPanel contentPane; 18 | 19 | /** 20 | * Create the frame. 21 | */ 22 | public LoginQrFrame() { 23 | setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 24 | addWindowListener(new WindowAdapter() { 25 | @Override 26 | public void windowClosing(WindowEvent e) { 27 | closeWindow(); 28 | } 29 | }); 30 | setTitle("请使用豆瓣APP扫描二维码登录"); 31 | setBounds(100, 100, 450, 300); 32 | contentPane = new JPanel(); 33 | contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 34 | 35 | setContentPane(contentPane); 36 | } 37 | 38 | 39 | /** 40 | * 关闭窗口 41 | */ 42 | private void closeWindow(){ 43 | //取消登录中标识 44 | LoginUtils.IS_LOGGING_IN = false; 45 | this.removeAll(); 46 | this.revalidate(); 47 | //销毁本组件 48 | this.dispose(); 49 | } 50 | 51 | 52 | /** 53 | * Launch the application. 54 | */ 55 | public static void main(String[] args) { 56 | EventQueue.invokeLater(new Runnable() { 57 | public void run() { 58 | try { 59 | LoginQrFrame frame = new LoginQrFrame(); 60 | frame.setVisible(true); 61 | } catch (Exception e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | }); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/DefaultAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.util.Map; 4 | 5 | import cn.blackgray.douban.album.download.model.Album; 6 | import cn.blackgray.douban.album.download.model.BGImage; 7 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 8 | 9 | /** 10 | * 默认相册处理器 11 | */ 12 | public class DefaultAlbumHandler extends AlbumHandler { 13 | 14 | @Override 15 | public String getURLRegex() { 16 | return null; 17 | } 18 | 19 | @Override 20 | public String getAlbumURL() { 21 | String url = super.getAlbumURL(); 22 | if (url.endsWith("/")) { 23 | return url.substring(0, url.length() - 1); 24 | }else{ 25 | return url; 26 | } 27 | } 28 | 29 | @Override 30 | public String getPageRegex() { 31 | return null; 32 | } 33 | 34 | @Override 35 | public Integer getPageSize() { 36 | return 0; 37 | } 38 | 39 | @Override 40 | public String getPageTag() { 41 | return null; 42 | } 43 | 44 | @Override 45 | public String getImageNameRegex() { 46 | return null; 47 | } 48 | 49 | @Override 50 | public String getRawURL(String imageURL) { 51 | return null; 52 | } 53 | 54 | 55 | @Override 56 | public void createBGImage(Album album, String source, String pageURL, String imageURL,Map map) { 57 | map.put(imageURL, new BGImage("", imageURL)); 58 | } 59 | 60 | @Override 61 | public String getCommentURL(Album album, BGImage image) { 62 | return null; 63 | } 64 | 65 | @Override 66 | public String getAlbumDesc(String source) { 67 | return null; 68 | } 69 | 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TetsInputStream.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | import java.util.Arrays; 12 | 13 | public class TetsInputStream { 14 | 15 | public static void main(String[] args) throws IOException { 16 | 17 | for (int i = 0; i < 10; i++) { 18 | String url = "http://img3.douban.com/icon/ul37817084-33.jpg"; 19 | URL image = new URL(url); 20 | HttpURLConnection conn = (HttpURLConnection) image.openConnection(); 21 | conn.setConnectTimeout(10*1000); //设置连接超时 22 | conn.setReadTimeout(10*1000); //设置读取超时 23 | conn.setDoInput(true); //默认为true 24 | conn.connect(); 25 | InputStream in = conn.getInputStream(); 26 | 27 | //执行下载 28 | File file = new File("D:/TEST/test" + (i+1) + ".jpg"); 29 | BufferedInputStream inputStream = new BufferedInputStream(in); 30 | System.out.println("xxxxxxxxx" + in.getClass()); 31 | BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file)); 32 | byte[] data = new byte[1024]; 33 | int n = 0; 34 | int total = 0; 35 | while ((n = inputStream.read(data)) != -1) { 36 | outputStream.write(data,0,n); 37 | total += n; 38 | System.out.println("i:" + (i+1) + "\tn:" + n + "\tdata:" + Arrays.toString(data)); 39 | } 40 | System.out.println("i:" + (i+1) + "\ttotal:" + total); 41 | System.out.println("i:" + (i+1) + "\tFINISH"); 42 | outputStream.flush(); 43 | in.close(); 44 | inputStream.close(); 45 | outputStream.close(); 46 | conn.disconnect(); 47 | } 48 | 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestImageLengthCompare.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.HttpURLConnection; 6 | import java.net.MalformedURLException; 7 | import java.net.ProtocolException; 8 | import java.net.URL; 9 | 10 | /** 11 | * 网络资源大小与本地文件大小对比测试 12 | * @author blackgray 13 | * @createTime 2022-10-24 00:34:46 14 | */ 15 | public class TestImageLengthCompare { 16 | 17 | 18 | /** 19 | * 获取网络资源图片大小 20 | * @param url 21 | * @return 22 | * @throws MalformedURLException 23 | * @throws ProtocolException 24 | * @throws IOException 25 | */ 26 | public static long getNetworkResourceContentLength(String url) throws MalformedURLException, ProtocolException, IOException { 27 | 28 | //配置网络资源 29 | URL image = new URL(url); 30 | HttpURLConnection conn = (HttpURLConnection) image.openConnection(); 31 | 32 | //2016-03-16 如不加referer信息,下载影人相册时,大图监测返回403异常 33 | conn.setRequestProperty("referer", "https://www.douban.com/"); 34 | 35 | conn.setConnectTimeout(10*1000); //设置连接超时 36 | conn.setReadTimeout(10*1000); //设置读取超时 37 | conn.setDoInput(true); //默认为true 38 | conn.connect(); 39 | // InputStream in = conn.getInputStream(); 40 | // inputStream = new BufferedInputStream(in); 41 | // outputStream = new BufferedOutputStream(new FileOutputStream(file)); 42 | // byte[] data = new byte[2048]; 43 | // int n = 0; 44 | // while ((n = inputStream.read(data)) != -1) { 45 | // outputStream.write(data,0,n); 46 | // } 47 | // outputStream.flush(); 48 | // in.close(); 49 | // inputStream.close(); 50 | // outputStream.close(); 51 | 52 | conn.disconnect(); 53 | 54 | return conn.getContentLength(); 55 | } 56 | 57 | 58 | public static long getLocalFileLength(String path) { 59 | File file = new File(path); 60 | return file.length(); 61 | } 62 | 63 | 64 | 65 | 66 | public static void main(String[] args) throws MalformedURLException, ProtocolException, IOException { 67 | long networkResourceContentLength = getNetworkResourceContentLength("https://img9.doubanio.com/view/photo/l/public/p2626439856.jpg"); 68 | long localFileLength = getLocalFileLength("/Users/blackgray/Downloads/p2626439856.jpg"); 69 | 70 | System.out.println("networkResourceContentLength = " + networkResourceContentLength + "| localFileLength = " + localFileLength); 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | import java.io.File; 4 | import java.io.FilenameFilter; 5 | 6 | import cn.blackgray.douban.album.download.common.Common; 7 | import cn.blackgray.douban.album.download.common.Console; 8 | import cn.blackgray.douban.album.download.model.Album; 9 | import cn.blackgray.douban.album.download.service.download.DownloadFailManager; 10 | 11 | /** 12 | * 目录、文件工具类 13 | * @author BlackGray 14 | */ 15 | public class FileUtils { 16 | 17 | /** 18 | * 获取相册目录 19 | * @param album 20 | * @return 21 | */ 22 | private static File getDir(Album album){ 23 | //判断目录下是否存在相册目录,如果已经存在,更新目录名称,如果不存在,创建新目录 24 | File parentDir = new File(Common.PATH_DOWNLOAD); 25 | File[] files = parentDir.listFiles(new FilenameFilter() { 26 | @Override 27 | public boolean accept(File dir, String name) { 28 | if (name.startsWith(album.getName())) { 29 | return true; 30 | }else{ 31 | return false; 32 | } 33 | } 34 | }); 35 | if (files.length != 0) { 36 | return files[0]; 37 | }else{ 38 | return null; 39 | } 40 | } 41 | 42 | /** 43 | * 生成相册目录 44 | */ 45 | public static void createDir(Album album){ 46 | File dir = getDir(album); 47 | if (dir != null) { 48 | //更新目录名称 49 | String newName = dir.getParent() + File.separator + dir.getName().replaceAll("\\(\\d+\\)", "").trim(); 50 | Console.print("相册已存在,更新目录:" + dir.getAbsolutePath() + " -> " + newName); 51 | File newDir = new File(newName); 52 | boolean flag = dir.renameTo(newDir); 53 | if (flag) { 54 | dir = newDir; 55 | } 56 | album.setUpdate(true); 57 | //删除早期下载失败记录文档 58 | DownloadFailManager.deleteAlbumFailFileDoc(newDir.getAbsolutePath()); 59 | }else{ 60 | String path = Common.PATH_DOWNLOAD + File.separator + album.getName().trim(); 61 | dir = new File(path); 62 | if (!dir.exists()) { 63 | dir.mkdirs(); 64 | Console.print("新建目录:" + path); 65 | } 66 | } 67 | album.setPath(dir.getAbsolutePath()); 68 | } 69 | 70 | 71 | /** 72 | * 删除图片文件 - 用于下载失败时,删除不完整图片文件 73 | * @param url 74 | * @param filePath 75 | */ 76 | public static void deleteImageFile(String url,String filePath) { 77 | String fileName = url.substring(url.lastIndexOf('/')); 78 | File file = new File(filePath + File.separatorChar + fileName); 79 | if(file.exists()) { 80 | file.delete(); 81 | } 82 | } 83 | 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.blackgrays 7 | douban-album 8 | 2024.10.03 9 | 10 | 11 | UTF-8 12 | 13 13 | 13 14 | 4.4.0 15 | 4.12 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.seleniumhq.selenium 23 | selenium-java 24 | ${seleniumhq.version} 25 | 26 | 27 | 28 | commons-io 29 | commons-io 30 | 2.11.0 31 | 32 | 33 | 34 | commons-logging 35 | commons-logging 36 | 1.1 37 | 38 | 39 | log4j 40 | log4j 41 | 1.2.17 42 | 43 | 44 | 45 | junit 46 | junit 47 | test 48 | ${junit.version} 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-assembly-plugin 57 | 58 | 59 | package 60 | 61 | single 62 | 63 | 64 | 65 | 66 | 67 | cn.blackgray.douban.album.download.ui.MainFrame 68 | 69 | 70 | 71 | 72 | jar-with-dependencies 73 | 74 | 75 | 76 | 77 | 78 | 79 | maven-compiler-plugin 80 | 3.8.1 81 | 82 | 1.8 83 | 1.8 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/AlbumDouListFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Set; 6 | import java.util.TreeSet; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import cn.blackgray.douban.album.download.common.Console; 11 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 12 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 13 | 14 | /** 15 | * 相册URL获取器实现 - 根据豆列获取相册 16 | * @author BlackGray 17 | */ 18 | public class AlbumDouListFinder implements IAlbumURLFinder{ 19 | 20 | // http://www.douban.com/doulist/3353890/; 21 | // http://www.douban.com/doulist/3353890/?start=25&filter=undo 22 | private static final int PAGE_SIZE_ALBUM = 25; //相册分页大小(一页25个相册) 23 | 24 | 25 | @Override 26 | public List findAlbumURL(String url) { 27 | if (!url.endsWith("/")) { 28 | url = url + "/"; 29 | } 30 | Console.print("扫描相册豆列首页:" + url); 31 | //【得到所有相册分页(每页25个相册)】 32 | List pageURLList = new ArrayList(); 33 | String source = URLUtils.readSource(url); 34 | String regex = url + "\\?start=\\d+"; 35 | Pattern p = Pattern.compile(regex); 36 | Matcher m = p.matcher(source); 37 | //查找出页面中所有分页连接 38 | int maxStartNum = 0; 39 | while (m.find()) { 40 | String u = m.group(); 41 | int num = Integer.parseInt(u.substring(u.lastIndexOf("=") + 1)); 42 | maxStartNum = num > maxStartNum ? num : maxStartNum; 43 | } 44 | //根据最大起始数字,生成页面地址 45 | for (int i = 0; i <= maxStartNum; i += PAGE_SIZE_ALBUM) { 46 | String u = url + "?start=" + i; 47 | pageURLList.add(u); 48 | Console.print("获取相册分页地址:" + u); 49 | } 50 | 51 | //【得到所有相册】 52 | Set albumURLSet = new TreeSet(); 53 | for (int i = 0; i < pageURLList.size(); i++) { 54 | source = URLUtils.readSource(pageURLList.get(i)); 55 | String albumRegex = "(http|https)://www.douban.com/photos/album/\\d+"; 56 | Pattern pattern = Pattern.compile(albumRegex); 57 | Matcher matcher = pattern.matcher(source); 58 | while (matcher.find()) { 59 | String u = matcher.group(); 60 | if (!u.endsWith("/")) { 61 | u += "/"; 62 | } 63 | albumURLSet.add(u); 64 | } 65 | } 66 | return new ArrayList(albumURLSet); 67 | } 68 | 69 | @Override 70 | public String getURLRegex() { 71 | return "(http|https)://www.douban.com/doulist/\\d+/"; 72 | } 73 | 74 | @Override 75 | public String getFindFailMsg() { 76 | return null; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/test/TestReflect.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.test; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Method; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.Enumeration; 10 | import java.util.List; 11 | import java.util.jar.JarEntry; 12 | import java.util.jar.JarFile; 13 | 14 | public class TestReflect { 15 | 16 | public static List getJarMethod(String jarFile) throws Exception { 17 | String NORMAL_METHOD = "waitequalsnotifynotifyAlltoStringhashCodegetClass"; 18 | List a = new ArrayList(); 19 | try { 20 | // 通过jarFile 和JarEntry得到所有的类 21 | JarFile jar = new JarFile(jarFile);// "D:/sip-test.jar" 22 | Enumeration e = jar.entries(); 23 | 24 | while (e.hasMoreElements()) { 25 | JarEntry entry = (JarEntry) e.nextElement(); 26 | // entry.getMethod() 27 | if (entry.getName().indexOf("META-INF") < 0) { 28 | String sName = entry.getName(); 29 | String substr[] = sName.split("/"); 30 | String pName = ""; 31 | for (int i = 0; i < substr.length - 1; i++) { 32 | if (i > 0) 33 | pName = pName + "/" + substr[i]; 34 | else 35 | pName = substr[i]; 36 | } 37 | if (sName.indexOf(".class") < 0) { 38 | sName = sName.substring(0, sName.length() - 1); 39 | } else { 40 | // 通过URLClassLoader.loadClass方法得到具体某个类 41 | URL url1 = new URL("file:D:\\sip-test.jar"); 42 | URLClassLoader myClassLoader = new URLClassLoader(new URL[] { url1 }, Thread.currentThread().getContextClassLoader()); 43 | String ppName = sName.replace("/", ".").replace(".class", ""); 44 | System.out.println("ppName:" + ppName); 45 | Class myClass = myClassLoader.loadClass(ppName); 46 | // 通过getMethods得到类中包含的方法 47 | Method m[] = myClass.getMethods(); 48 | for (int i = 0; i < m.length; i++) { 49 | String sm = m[i].getName(); 50 | if (NORMAL_METHOD.indexOf(sm) < 0) { 51 | String[] c = { sm, sName }; 52 | a.add(c); 53 | } 54 | } 55 | myClassLoader.close(); 56 | } 57 | String[] b = { sName, pName }; 58 | a.add(b); 59 | } 60 | } 61 | jar.close(); 62 | return a; 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | return a; 67 | } 68 | 69 | public static void main(String[] args) throws Exception { 70 | List list = TestReflect.getJarMethod("C:/Users/Think/Desktop/x.jar"); 71 | for (String[] arr : list) { 72 | System.out.println(Arrays.toString(arr)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/finder/impl/AlbumListFinder.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.finder.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Set; 6 | import java.util.TreeSet; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | import cn.blackgray.douban.album.download.common.Console; 11 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 12 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 13 | 14 | /** 15 | * 相册URL获取器实现 - 根据相册列表获取相册 16 | */ 17 | public class AlbumListFinder implements IAlbumURLFinder{ 18 | 19 | private static final int PAGE_SIZE_ALBUM = 16; //相册分页大小(一页16个相册) 20 | 21 | 22 | @Override 23 | public List findAlbumURL(String albumListURL) { 24 | if (albumListURL.endsWith("/")) { 25 | albumListURL = albumListURL.substring(0,albumListURL.length() - 1); 26 | } 27 | Console.print("扫描相册列表首页:" + albumListURL); 28 | // http://www.douban.com/people/BlackGray/photos 29 | // http://www.douban.com/photos/album/xxxxx 30 | //【得到所有相册分页(每页16个相册)】 31 | List pageURLList = new ArrayList(); 32 | String source = URLUtils.readSource(albumListURL); 33 | String regex = albumListURL + "\\?\\w+=\\d+"; 34 | Pattern p = Pattern.compile(regex); 35 | Matcher m = p.matcher(source); 36 | //查找出页面中所有分页连接 37 | int maxStartNum = 0; 38 | while (m.find()) { 39 | String url = m.group(); 40 | int num = Integer.parseInt(url.substring(url.lastIndexOf("=") + 1)); 41 | maxStartNum = num > maxStartNum ? num : maxStartNum; 42 | } 43 | //根据最大起始数字,生成页面地址 44 | for (int i = 0; i <= maxStartNum; i += PAGE_SIZE_ALBUM) { 45 | String u = albumListURL + "?start=" + i; 46 | pageURLList.add(u); 47 | Console.print("获取相册分页地址:" + u); 48 | } 49 | 50 | //【得到所有相册】 51 | Set albumURLSet = new TreeSet(); 52 | for (int i = 0; i < pageURLList.size(); i++) { 53 | source = URLUtils.readSource(pageURLList.get(i)); 54 | String albumRegex = "(http|https)://www.douban.com/photos/album/\\d+"; 55 | Pattern pattern = Pattern.compile(albumRegex); 56 | Matcher matcher = pattern.matcher(source); 57 | while (matcher.find()) { 58 | String url = matcher.group(); 59 | if (!url.endsWith("/")) { 60 | url += "/"; 61 | } 62 | albumURLSet.add(url); 63 | } 64 | } 65 | return new ArrayList(albumURLSet); 66 | } 67 | 68 | @Override 69 | public String getURLRegex() { 70 | return "(http|https)://www.douban.com/people/\\w+/photos/"; 71 | } 72 | 73 | @Override 74 | public String getFindFailMsg() { 75 | return "相册获取失败,因豆瓣改版“用户相册首页”只支持登录后访问,程序无权限。"; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/image/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.image; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.IOException; 8 | import java.util.Iterator; 9 | 10 | import javax.imageio.ImageIO; 11 | import javax.imageio.ImageReader; 12 | import javax.imageio.stream.ImageInputStream; 13 | 14 | /** 15 | * 图片工具类 16 | * @author BlackGray 17 | */ 18 | public class ImageUtils { 19 | 20 | /** 21 | * 获取图片尺寸信息 22 | * 通过ImageReader获取图片尺寸 23 | * 效率高于BufferedImage 24 | * @param path 25 | */ 26 | public static ImageInfo getImageSize(String path){ 27 | try { 28 | File file = new File(path); 29 | String readImageFormat = path.substring(path.lastIndexOf(".") + 1, path.length()); 30 | Iterator readers = ImageIO.getImageReadersByFormatName(readImageFormat); 31 | ImageReader reader = (ImageReader) readers.next(); 32 | ImageInputStream iis = ImageIO.createImageInputStream(file); 33 | reader.setInput(iis, true); 34 | return new ImageInfo(reader.getWidth(0), reader.getHeight(0)); 35 | } catch (IOException e) { 36 | //如果出现异常,使用备用方法获取尺寸 37 | //javax.imageio.IIOException: Not a JPEG file: starts with 0x89 0x50 38 | //上述异常表是图片为PNG格式,后缀为JPG 39 | return getImageSizeByBufferedImage(path); 40 | } 41 | } 42 | 43 | private static ImageInfo getImageSizeByBufferedImage(String path){ 44 | File picture = new File(path); 45 | BufferedImage sourceImg; 46 | try { 47 | sourceImg = ImageIO.read(new FileInputStream(picture)); 48 | return new ImageInfo(sourceImg.getWidth(), sourceImg.getHeight()); 49 | } catch (FileNotFoundException e) { 50 | e.printStackTrace(); 51 | } catch (IOException e) { 52 | //e.printStackTrace(); 53 | //javax.imageio.IIOException: Error reading PNG image data 54 | throw new RuntimeException("图片文件尺寸获取异常:" + e.getMessage()); 55 | } 56 | return null; 57 | } 58 | 59 | 60 | 61 | public static void main(String[] args) { 62 | 63 | //【TEST - 图片大小信息获取对比测试】 64 | String path = "D:\\raw"; 65 | File dir = new File(path); 66 | //1.通过ImageReader获取图片尺寸 67 | long begin = System.currentTimeMillis(); 68 | for (File picture : dir.listFiles()) { 69 | getImageSize(picture.getPath()); 70 | }; 71 | System.out.println("【getImageSize】" + (System.currentTimeMillis() - begin)); 72 | 73 | //2.通过BufferedImage获取图片尺寸 74 | begin = System.currentTimeMillis(); 75 | for (File picture : dir.listFiles()) { 76 | getImageSizeByBufferedImage(picture.getPath()); 77 | }; 78 | System.out.println("【getImageSizeByBufferedImage】" + (System.currentTimeMillis() - begin)); 79 | 80 | 81 | 82 | 83 | } 84 | } 85 | 86 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/model/BGImage.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.model; 2 | 3 | import cn.blackgray.douban.album.download.common.Common; 4 | 5 | /** 6 | * 图片MODEL 7 | * @author BlackGray 8 | */ 9 | public class BGImage { 10 | 11 | private String ownerName; //照片所有者昵称 12 | private String ownerURL; //照片所有者ID 13 | private String desc; //照片描述 14 | private String commentURL; //照片评论 15 | private Integer commentTotal; //照片评论数 16 | private String url; //照片地址 17 | private String name; //照片名 18 | private String number; //照片编号 19 | private String id; //照片ID - 豆瓣照片ID 20 | 21 | public BGImage() { 22 | super(); 23 | } 24 | 25 | public BGImage(String desc, String url) { 26 | super(); 27 | this.desc = desc; 28 | this.url = url; 29 | } 30 | 31 | public BGImage(String number, String url, String desc) { 32 | super(); 33 | this.desc = desc; 34 | this.url = url; 35 | this.number = number; 36 | } 37 | 38 | 39 | public BGImage(String desc, String url, Integer commentTotal) { 40 | super(); 41 | this.desc = desc; 42 | this.commentTotal = commentTotal; 43 | this.url = url; 44 | } 45 | 46 | public String getId() { 47 | String name = this.getName(); 48 | if (name.matches("p\\d+.(" + Common.IMAGE_TYPE + ")")) { 49 | return name.substring(name.indexOf("p") + 1,name.lastIndexOf(".")); 50 | }else if(name.matches("x\\d+.(" + Common.IMAGE_TYPE + ")")) { 51 | return name.substring(name.indexOf("x") + 1,name.lastIndexOf(".")); 52 | }else{ 53 | return null; 54 | } 55 | } 56 | 57 | public void setId(String id) { 58 | this.id = id; 59 | } 60 | 61 | public String getOwnerName() { 62 | return ownerName; 63 | } 64 | public String getOwnerURL() { 65 | return ownerURL; 66 | } 67 | public String getDesc() { 68 | return desc; 69 | } 70 | public String getCommentURLURL() { 71 | return commentURL; 72 | } 73 | public String getUrl() { 74 | return url; 75 | } 76 | public void setOwnerName(String ownerName) { 77 | this.ownerName = ownerName; 78 | } 79 | public void setOwnerURL(String ownerURL) { 80 | this.ownerURL = ownerURL; 81 | } 82 | public void setDesc(String desc) { 83 | this.desc = desc; 84 | } 85 | public void setCommentURLURL(String commentURL) { 86 | this.commentURL = commentURL; 87 | } 88 | public void setUrl(String url) { 89 | this.url = url; 90 | } 91 | public String getName() { 92 | return this.url.substring(this.url.lastIndexOf("/") + 1); 93 | } 94 | public void setName(String name) { 95 | this.name = name; 96 | } 97 | public String getNumber() { 98 | return number; 99 | } 100 | public void setNumber(String number) { 101 | this.number = number; 102 | } 103 | 104 | public String getCommentURL() { 105 | return commentURL; 106 | } 107 | 108 | public void setCommentURL(String commentURL) { 109 | this.commentURL = commentURL; 110 | } 111 | 112 | public Integer getCommentTotal() { 113 | return commentTotal; 114 | } 115 | 116 | public void setCommentTotal(Integer commentTotal) { 117 | this.commentTotal = commentTotal; 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return "BGImage [ownerName=" + ownerName + ", ownerURL=" + ownerURL 123 | + ", desc=" + desc + ", commentURL=" + commentURL 124 | + ", commentTotal=" + commentTotal + ", url=" + url + ", name=" 125 | + name + ", number=" + number + ", id=" + id + "]"; 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/version.info: -------------------------------------------------------------------------------- 1 | 【更新日志】 2 | ------------------------------------------------------------------------------------- 3 | 【2024-10-03 更新】 4 | 1.增加豆瓣APP扫码登录功能 5 | 2.支持登陆后具有权限的私密相册下载(仅自己可见的、朋友设置为朋友可见的相册) 6 | 3.修复Windows下生成的HTML页面乱码异常 7 | 注意:需下载与自己Chrome大版本一致的chromedriver文件,并拷贝至相册下载器相同目录下,才能正常执行登陆。 8 | 9 | chromedriver下载地址: 10 | 1.早期版本 - https://chromedriver.storage.googleapis.com/index.html 11 | 2.129及之后版本 - https://googlechromelabs.github.io/chrome-for-testing/ 12 | 注:开发阶段,chromedriver需拷贝至target/classes目录下。 13 | 14 | 15 | 【2024-06-23 异常修复】 16 | 1.影人、音乐人相册无法正常下载修复 17 | 2.版本更新(v0.5.14 → v20240623) 18 | 3.增加User-Agent配置,解决下载403问题 19 | 4.删除影人、音乐人相册处理,新增人士相册处理 20 | 5.去除地址中多余斜杠。使用豆瓣早期影人、音乐人相册地址访问,跳转后URL地址错误,会多出斜杠,需去除,否则无法正常识别相册类型。 21 | 6.调整MacOS下载目录(Sonoma 14.5测试正常) 22 | 7.所有User-Agent参数改为随机生成 23 | 24 | 【2022-10-24 更新&修复】v0.5.14 25 | 1.增加下载后文件校验 - 下载完成后对比本地图片文件大小,若小于网络资源大小,判定为下载失败,重新下载,防止出现下载文件不完整情况 26 | 2.增加下载失败图片记录文档 - 若最终仍有下载失败图片,相册根目录下生成“下载失败图片记录.txt”,用于后续重试或使用其他下载工具下载 27 | 3.优化下载,自动删除因下载失败生成的错误文件,已存在且正常的图片,不再建立网络连接获取信息 28 | 4.处理单元大小由50降低为20、页面请求参数User-Agent改为随机生成、每个页面抓取及分析增加500毫秒间隔,以降低被禁IP可能 29 | 5.下载失败后自动重试次数由5次改为10次 30 | 6.若页面无图,或页面解析失败,不再生成空相册目录 31 | 7.MacOS下日志输出、异常文件下载界面控件增加全选、复制、粘贴等快捷键 32 | 8.修正图片对比排序可能出现的异常 33 | 9.优化下载失败图片处理代码实现 34 | 35 | 【2020-03-02 修复】v0.5.13 36 | 1.调整代码字符集(GBK->UTF-8) 37 | 2.更新版本日志 38 | 39 | 【2019-06-05 修复】 40 | 1.修复个人相册下载异常(豆瓣更新,用户相册分页参数由start更新为m_start,导致只能下载第一页) 41 | 2.修复图片下载缺失bug 42 | 3.修复取消“保存大图”后,仍下载大图bug 43 | 44 | 【2018-10-17 修复】 45 | 1.修正小站相册下载异常 46 | 47 | 【2017-11-06 修复】 48 | 1.修复影人、电影、音乐人、用户相册下载不完整及大图下载异常bug 49 | 2.修复小站相册无法正常识别分页bug 50 | (豆瓣官方更新了相册照片地址、每页照片数量,造成解析及下载失败) 51 | 52 | 【2017-07-25 修复】 53 | 1.支持音乐人相册下载 54 | 55 | 【2017-07-04 修复】 56 | 1.修复原始大图无法下载异常 57 | 58 | 【2016-04-11 修复】 59 | 1.修复个人与活动相册中,相册描述与照片描述无法下载异常 60 | 61 | 【2016-03-02 修复】 62 | 1.修复https协议下,相册无法下载异常。(近期豆瓣由http协议改为https协议,导致下载器正则解析异常而无法下载图片,已修正正则判断,恢复下载) 63 | 64 | 【2013-12-30 更新&修复】 65 | 1.支持影人下载 66 | 2.支持电影相册下载(剧照、海报、壁纸) 67 | 3.支持小站公共相册下载 68 | 4.支持相册豆列下载 69 | 5.HTML页面增加相册描述、照片数量、下载时间显示 70 | 6.HTML页面照片描述增加评论数显示 71 | 7.HTML页面增加评论数排序功能(方便查看最热门的图片) 72 | 8.增加软件更新提示(启动时检查是否有新版本,如果有,提示是否更新) 73 | 9.下载超时自动终止下载线程(默认超时时间10秒) 74 | 10.调整HTML页面生成顺序,防止异常图片重下完毕后生成文件缺失 75 | 11.图片尺寸对比处理中,保留BufferedImage,增加ImageReader处理方式,提高HTML生成效率。 76 | 12.自动重下失败图片(默认重试次数5次,失败后转入手动选择界面) 77 | 13.增加资源是否存在校验,减少下载错误率 78 | 14.支持不同类型相册批量下载(此前程序只支持同类型相册批量下载) 79 | 15.地址栏支持地址格式化,快捷键Ctrl+F 80 | 16.增加默认下载线程数 81 | 82 | 【2013-10-14 Bug修复】 83 | 1.修复图片下载停顿,网页无法生成的bug 84 | 85 | 【2013-05-06 Bug修复】 86 | 1.修复活动相册无法正常下载bug 87 | 2.修复小站相册无法正常获取描述的bug 88 | 3.其他已知bug 89 | 90 | 【2013-04-22 Bug修复】 91 | 1.调整同时下载线程数 92 | 2.修复mac下无法正常下载的bug(因目录分隔符造成的低级错误。囧。。) 93 | 3.调整生成后的html页面 94 | 95 | 【2013-04-10 Bug修复】 96 | 1.可下载个人相册原始图片。 - @ ♫欧德伯裔@法雲地學士郎 97 | 2.修复地址尾部带有“?start=”分页信息时,无法下载全部图片的bug。 98 | 3.修复小站相册下载。(因豆瓣改版,小站相册地址更变格式而无法下载的bug。) - @月岛雯_Orange 99 | 100 | 【2012-06-24 更新】 101 | 1. 支持豆瓣活动下载 102 | 2. 支持豆瓣小站相册下载 103 | 3. 增加处理单元,优化页面获取和分析处理 104 | 4. 去除相册首页地址输入框,只保留"相册地址"输入,程序会自动判断输入地址的类型。 105 | 5. "相册地址"增加右键菜单,提供基础的剪切、复制、粘贴和全选功能 106 | 6. "相册地址"支持地址拖放,自动换行 107 | 7. "相册地址"支持文件夹拖放,自动识别相册地址 – 用于更新 108 | 8. HTML新增评论连接,点击图片跳转至图片原始出处,方便查看评论~ 109 | 9. HTML修改CSS样式,统一字体。 110 | 10. 页面图片显示改为JavaScript生成, 111 | 防止活动相册等超大相册在一个页面一次性显示图片过多,造成浏览器挂掉~ 112 | 11. HTML增加工具栏,功能分别为:回到顶部,上一页,下一页,跳转, 113 | 分页大小,搜索,排序。 114 | 12. 多线程下载,默认5线程,优化下载。 115 | 116 | 修复bug: 117 | 1.根据用户下载时,出错 118 | 2.相册封面判断不准确 119 | 3.其他bug若干 120 | 121 | 【2012-03-31更新】 122 | 1.HTML增加TOP按钮,快速返回顶部。 123 | 2.菜单增加手动生成HTML页面选项。 124 | 3.可以下载非豆瓣相册的图片(有公共访问权限的)。 125 | 4.自动转换字符集,解决页面字符集不同,生成文件的乱码问题。 126 | 5.增加HTML页面特殊字符转换 127 | 6.修复bug若干,囧 128 | 129 | 【2012-03-27更新】 130 | 1.获取照片描述 131 | 2.生成HTML文件,方便浏览 -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/ui/component/DropTextArea.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.ui.component; 2 | 3 | import java.awt.datatransfer.DataFlavor; 4 | import java.awt.datatransfer.UnsupportedFlavorException; 5 | import java.awt.dnd.DnDConstants; 6 | import java.awt.dnd.DropTarget; 7 | import java.awt.dnd.DropTargetDragEvent; 8 | import java.awt.dnd.DropTargetDropEvent; 9 | import java.awt.dnd.DropTargetEvent; 10 | import java.awt.dnd.DropTargetListener; 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.FileNotFoundException; 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | import java.util.List; 17 | 18 | import javax.swing.JOptionPane; 19 | 20 | import cn.blackgray.douban.album.download.common.Common; 21 | import cn.blackgray.douban.album.download.ui.MainFrame; 22 | 23 | /** 24 | * 拖放文本域控件 25 | * @author BlackGray 26 | */ 27 | public class DropTextArea extends JPopupTextArea implements DropTargetListener{ 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | public DropTextArea() { 32 | new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this); 33 | } 34 | 35 | @Override 36 | public void dragEnter(DropTargetDragEvent dtde) { 37 | 38 | } 39 | 40 | @Override 41 | public void dragOver(DropTargetDragEvent dtde) { 42 | 43 | } 44 | 45 | @Override 46 | public void dropActionChanged(DropTargetDragEvent dtde) { 47 | 48 | } 49 | 50 | @Override 51 | public void dragExit(DropTargetEvent dte) { 52 | 53 | } 54 | 55 | @Override 56 | public void drop(DropTargetDropEvent dtde) { 57 | try { 58 | // Transferable tr = dtde.getTransferable(); 59 | if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 60 | this.setText(""); 61 | dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); 62 | @SuppressWarnings("unchecked") 63 | List list = (List) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); 64 | for (File file : list) { 65 | if (file.isDirectory()) { 66 | //获取目录地址 67 | File dir = new File(file.getAbsolutePath()); 68 | if (dir.isDirectory()) { 69 | File doc = new File(dir.getAbsolutePath() + "/描述.txt"); 70 | if (doc.exists()) { 71 | try { 72 | BufferedReader br = new BufferedReader(new FileReader(doc)); 73 | String str = br.readLine(); 74 | br.close(); 75 | if (str != null && str.length() != 0) { 76 | if (str.split(" ").length >= 2) { 77 | //暂时只支持单个相册更新 78 | this.setText(str.split(" ")[1]); 79 | Common.PATH_DOWNLOAD = dir.getParent(); 80 | Common.IS_UPDATE = true; 81 | } 82 | } 83 | } catch (FileNotFoundException e) { 84 | e.printStackTrace(); 85 | } catch (IOException e) { 86 | e.printStackTrace(); 87 | } 88 | } else { 89 | JOptionPane.showMessageDialog(MainFrame.getInstance(),"所需要的描述文档不存在,无法更新,T^T"); 90 | } 91 | } 92 | } 93 | } 94 | dtde.dropComplete(true); 95 | this.updateUI(); 96 | } else { 97 | // dtde.rejectDrop(); 98 | dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); 99 | String str = (String) dtde.getTransferable().getTransferData(DataFlavor.stringFlavor); 100 | this.append(str + "\n"); 101 | dtde.dropComplete(true); 102 | this.updateUI(); 103 | } 104 | } catch (IOException ioe) { 105 | ioe.printStackTrace(); 106 | } catch (UnsupportedFlavorException ufe) { 107 | ufe.printStackTrace(); 108 | } 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/DownloadService.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.swing.JLabel; 7 | import javax.swing.JProgressBar; 8 | 9 | import cn.blackgray.douban.album.download.common.Common; 10 | import cn.blackgray.douban.album.download.common.Console; 11 | import cn.blackgray.douban.album.download.model.Album; 12 | import cn.blackgray.douban.album.download.service.creator.HtmlCreator; 13 | import cn.blackgray.douban.album.download.service.download.DownloadFailManager; 14 | import cn.blackgray.douban.album.download.ui.FailFileFrame; 15 | import cn.blackgray.douban.album.download.ui.MainFrame; 16 | 17 | /** 18 | * 下载业务类 19 | * @author BlackGray 20 | */ 21 | public class DownloadService { 22 | 23 | private static MainFrame mainFrame = MainFrame.getInstance(); 24 | private static JProgressBar albumListProgressBar = mainFrame.albumListProgressBar; //相册进度条 25 | private static JLabel albumListCountLabel = mainFrame.albumListCountLabel; //相册进度条 26 | 27 | /** 28 | * 下载 29 | * @param urlList 30 | */ 31 | public static void download(List urlList) { 32 | 33 | 34 | //【获取相册列表】 35 | List albums = new ArrayList(); 36 | for (String url : urlList) { 37 | albums.addAll(AlbumFactory.getFromURL(url)); 38 | } 39 | 40 | //【统计】 41 | long time = System.currentTimeMillis(); //总耗时 42 | long imagesCount = 0; //照片总数 43 | 44 | //----------设置进度条&标签----------- 45 | albumListProgressBar.setMaximum(albums.size()); 46 | albumListProgressBar.setValue(0); 47 | albumListCountLabel.setText("[0/" + albums.size() + "] "); 48 | //----------------------------------- 49 | 50 | //【执行处理】 51 | for (int i = 0; i < albums.size(); i++) { 52 | Album album = albums.get(i); 53 | //初始化相册对象 54 | album.init(); 55 | //执行下载 56 | album.download(); 57 | //修改进度条 58 | albumListProgressBar.setValue(i+1); 59 | albumListCountLabel.setText(i+1 + "/" + albums.size() + " "); 60 | imagesCount += album.getPhotosList().size(); 61 | } 62 | 63 | //【显示统计信息】 64 | if (imagesCount != 0) { 65 | String sp = " "; 66 | StringBuffer sb = new StringBuffer(); 67 | sb.append("==============================\r\n"); 68 | sb.append(sp).append(" 相册总数:").append(albums.size()).append("(个)\r\n"); 69 | sb.append(sp).append(" 照片总数:").append(imagesCount).append("(张)\r\n"); 70 | sb.append(sp).append(" 成功:").append(imagesCount - DownloadFailManager.getFailSize()).append("(张)\r\n"); 71 | sb.append(sp).append(" 失败:").append(DownloadFailManager.getFailSize()).append("(张)\r\n"); 72 | sb.append(sp).append(" 总耗时:").append((System.currentTimeMillis() - time)/1000 + "s").append(sp).append("~\\(≧▽≦)/~"); 73 | Console.print(sb.toString()); 74 | } 75 | 76 | //【重下异常文件】 77 | //自动重新下载 78 | if (DownloadFailManager.getFailSize() != 0) { 79 | Console.print("【存在下载失败文件,尝试重新下载】"); 80 | int num = 1; 81 | boolean success = false; 82 | while ((num < Common.AUTO_DOWNLOAD_FAIL_FILE) && !success) { 83 | success = DownloadFailManager.downloadFailFile(); 84 | if (success) { 85 | Console.print("【失败文件下载完成】"); 86 | }else{ 87 | Console.print("【部分文件依然下载失败,显示失败文件列表】 - " + num); 88 | } 89 | num++; 90 | } 91 | } 92 | //重下完成,监测是否依然存在错误文件,如果存在,显示错误文件窗口,否则生成HTML文档 93 | List finishedAlbumPathList = new ArrayList(); 94 | for (Album a : albums) { 95 | finishedAlbumPathList.add(a.getPath()); 96 | } 97 | if (DownloadFailManager.getFailSize() > 0) { 98 | //下载失败,显示错误文件窗口 99 | FailFileFrame frame = FailFileFrame.getInstance(finishedAlbumPathList); 100 | frame.setVisible(true); 101 | }else{ 102 | //【生成HTML文档】 103 | if(imagesCount != 0) { 104 | Console.print("【正在生成HTML文档,请稍等】"); 105 | HtmlCreator.createAlbumHTML(finishedAlbumPathList); 106 | } 107 | Console.print("【FINISH】"); 108 | //设置界面下载按钮可用 109 | MainFrame.getInstance().downloadBtn.setEnabled(true); 110 | } 111 | 112 | 113 | 114 | } 115 | 116 | 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/download/DownloadManager.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.download; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.IOException; 5 | import java.net.MalformedURLException; 6 | import java.net.URISyntaxException; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | 13 | import javax.swing.JProgressBar; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.common.Console; 17 | import cn.blackgray.douban.album.download.model.Album; 18 | import cn.blackgray.douban.album.download.ui.MainFrame; 19 | 20 | /** 21 | * 图片工具类,负责下载 22 | * @author BlackGray 23 | */ 24 | public class DownloadManager { 25 | 26 | //主进度条 - 显示照片下载等进度 27 | private final static JProgressBar mainProgressBar = MainFrame.getInstance().progressBar; 28 | public static Integer updateCount = 0; 29 | private static final Integer TIMEOUT = 10; //单图片下载超时时间 30 | 31 | /** 32 | * 批量下载图片 33 | * @param imageURLList 34 | * @param path 35 | * @return 36 | */ 37 | public static int downloadImage(Album album, List imageURLList,String path) { 38 | 39 | mainProgressBar.setMaximum(imageURLList.size()); //进度条设置 - 最大值 40 | mainProgressBar.setValue(0); //进度条设置 - 初始值 41 | List threadList = new ArrayList(); 42 | int imageSize = imageURLList.size(); 43 | //创建多个线程,开始批量下载图片 44 | for (int i = 0; i < Common.DOWNLOAD_THREAD; i++) { 45 | String threadName = "线程0"; 46 | if (i < 10) { 47 | threadName += i; 48 | }else{ 49 | threadName = "线程" + String.valueOf(i); 50 | } 51 | DownloadThread thread = new DownloadThread(album, threadName,imageURLList, imageSize, path, mainProgressBar); 52 | thread.start(); 53 | threadList.add(thread); 54 | } 55 | Map waitThreadMap = new HashMap(); 56 | //循环中每隔1s判断下载完成情况 57 | while (true) { 58 | try { 59 | //睡眠1s 60 | Thread.sleep(1000); 61 | } catch (InterruptedException e) { 62 | e.printStackTrace(); 63 | } 64 | //判断线程是否都已经结束 65 | if (imageURLList.size() == 0) { 66 | for (DownloadThread thread : threadList) { 67 | if (thread.isAlive()) { 68 | if (waitThreadMap.containsKey(thread)) { 69 | waitThreadMap.put(thread, waitThreadMap.get(thread)+1); 70 | }else{ 71 | waitThreadMap.put(thread, 0); 72 | } 73 | //判断超时 74 | if (waitThreadMap.get(thread) > TIMEOUT) { 75 | //中断线程 76 | try { 77 | String url = thread.getUrl(); 78 | Console.print("下载超时,中断线程,请稍等.. - " + thread.getName() + " - " + url); 79 | thread.closeStream(); 80 | //添加至错误集合 81 | DownloadFailManager.add(url, path); 82 | } catch (IOException e) { 83 | Console.print("线程中断操作异常:" + e.getMessage()); 84 | e.printStackTrace(); 85 | } 86 | waitThreadMap.remove(thread); 87 | } 88 | }else{ 89 | waitThreadMap.remove(thread); 90 | } 91 | } 92 | //如果结束,跳出循环,否则提示 93 | if (waitThreadMap.size() == 0) { 94 | mainProgressBar.setValue(mainProgressBar.getMaximum()); 95 | break; 96 | }else{ 97 | //提示 98 | Console.print("就快好了~ ~( ̄▽ ̄~)(~ ̄▽ ̄)~ "); 99 | Console.print("---------------------------------------------------"); 100 | for (Entry entry : waitThreadMap.entrySet()) { 101 | DownloadThread t = entry.getKey(); 102 | Integer time = entry.getValue(); 103 | StringBuffer sb = new StringBuffer(); 104 | sb.append("等待线程").append(" - ").append(t.getName()).append(" - [").append(time).append("s]"); 105 | sb.append(" = ").append(t.getUrl()); 106 | Console.print(sb.toString()); 107 | } 108 | 109 | } 110 | } 111 | } 112 | return updateCount; 113 | } 114 | 115 | 116 | public static void main(String[] args) throws MalformedURLException, FileNotFoundException, IOException, URISyntaxException, InterruptedException { 117 | System.out.println("START"); 118 | //https://www.douban.com/personage/27503633/photo/1946222503/ 119 | //https://img3.doubanio.com/view/photo/l/public/p1946222503.jpg 120 | new DownloadThread().downloadImage("https://img3.doubanio.com/view/photo/raw/public/p1946222503.jpg", "/Users/blackgray/Documents/temp", false, false); 121 | System.out.println("FINISH"); 122 | } 123 | 124 | } 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/ui/component/JPopupTextArea.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.ui.component; 2 | 3 | /** 4 | * @author songyinghao 5 | * @email haoyingsong@foxmail.com 6 | * @desc All Rights Reserved 7 | * @license LGPL 8 | */ 9 | import java.awt.datatransfer.DataFlavor; 10 | import java.awt.datatransfer.Transferable; 11 | import java.awt.datatransfer.UnsupportedFlavorException; 12 | import java.awt.event.ActionEvent; 13 | import java.awt.event.ActionListener; 14 | import java.awt.event.InputEvent; 15 | import java.awt.event.MouseEvent; 16 | import java.awt.event.MouseListener; 17 | import java.io.IOException; 18 | 19 | import javax.swing.JMenuItem; 20 | import javax.swing.JPopupMenu; 21 | import javax.swing.JSeparator; 22 | import javax.swing.JTextArea; 23 | import javax.swing.KeyStroke; 24 | 25 | /** 26 | * 右键菜单文本域 27 | * @author BlackGray 28 | */ 29 | public class JPopupTextArea extends JTextArea implements MouseListener, 30 | ActionListener { 31 | 32 | private static final long serialVersionUID = -406608462064697359L; 33 | private JPopupMenu popupMenu = null; 34 | private JMenuItem cutMenu = null, copyMenu = null, pasteMenu = null, 35 | selectAllMenu = null,formatMenu = null; 36 | 37 | public JPopupTextArea() { 38 | super(); 39 | popupMenu = new JPopupMenu(); 40 | cutMenu = new JMenuItem("剪切"); 41 | copyMenu = new JMenuItem("复制"); 42 | pasteMenu = new JMenuItem("粘贴"); 43 | selectAllMenu = new JMenuItem("全选"); 44 | formatMenu = new JMenuItem("格式化"); 45 | 46 | cutMenu.setAccelerator(KeyStroke.getKeyStroke('X', InputEvent.CTRL_MASK)); 47 | copyMenu.setAccelerator(KeyStroke.getKeyStroke('C',InputEvent.CTRL_MASK)); 48 | pasteMenu.setAccelerator(KeyStroke.getKeyStroke('V',InputEvent.CTRL_MASK)); 49 | selectAllMenu.setAccelerator(KeyStroke.getKeyStroke('A',InputEvent.CTRL_MASK)); 50 | formatMenu.setAccelerator(KeyStroke.getKeyStroke('F',InputEvent.CTRL_MASK)); 51 | 52 | 53 | cutMenu.addActionListener(this); 54 | copyMenu.addActionListener(this); 55 | pasteMenu.addActionListener(this); 56 | selectAllMenu.addActionListener(this); 57 | formatMenu.addActionListener(this); 58 | 59 | 60 | popupMenu.add(cutMenu); 61 | popupMenu.add(copyMenu); 62 | popupMenu.add(pasteMenu); 63 | popupMenu.add(new JSeparator()); 64 | popupMenu.add(selectAllMenu); 65 | popupMenu.add(new JSeparator()); 66 | popupMenu.add(formatMenu); 67 | 68 | this.add(popupMenu); 69 | this.addMouseListener(this); 70 | 71 | } 72 | 73 | public void actionPerformed(ActionEvent e) { 74 | 75 | if (e.getSource() == copyMenu) { 76 | this.copy(); 77 | } 78 | if (e.getSource() == pasteMenu) { 79 | this.paste(); 80 | } 81 | if (e.getSource() == cutMenu) { 82 | this.cut(); 83 | } 84 | if (e.getSource() == selectAllMenu) { 85 | this.selectAll(); 86 | } 87 | if (e.getSource() == formatMenu) { 88 | this.setText(this.getText().replaceAll("[^\r\n]http://", "/\r\nhttp://").trim()); 89 | this.setText(this.getText().replaceAll("[^\r\n]https://", "/\r\nhttps://").trim()); 90 | } 91 | 92 | } 93 | 94 | public void mousePressed(MouseEvent e) { 95 | popupMenuTrigger(e); 96 | } 97 | 98 | public void mouseReleased(MouseEvent e) { 99 | popupMenuTrigger(e); 100 | } 101 | 102 | public void mouseClicked(MouseEvent e) { 103 | } 104 | 105 | public void mouseEntered(MouseEvent e) { 106 | } 107 | 108 | public void mouseExited(MouseEvent e) { 109 | } 110 | 111 | private void popupMenuTrigger(MouseEvent e) { 112 | if (e.isPopupTrigger()) { 113 | this.requestFocusInWindow(); 114 | cutMenu.setEnabled(isAbleToCopyAndCut()); 115 | copyMenu.setEnabled(isAbleToCopyAndCut()); 116 | pasteMenu.setEnabled(isAbleToPaste()); 117 | selectAllMenu.setEnabled(isAbleToSelectAll()); 118 | popupMenu.show(this, e.getX() + 3, e.getY() + 3); 119 | } 120 | } 121 | 122 | private boolean isAbleToSelectAll() { 123 | return !("".equalsIgnoreCase(this.getText()) || (null == this.getText())); 124 | } 125 | 126 | private boolean isAbleToCopyAndCut() { 127 | return (this.getSelectionStart() != this.getSelectionEnd()); 128 | } 129 | 130 | private boolean isAbleToPaste() { 131 | Transferable content = this.getToolkit().getSystemClipboard().getContents(this); 132 | try { 133 | return (content.getTransferData(DataFlavor.stringFlavor) instanceof String); 134 | } catch (UnsupportedFlavorException e) { 135 | e.printStackTrace(); 136 | return false; 137 | } catch (IOException e) { 138 | e.printStackTrace(); 139 | return false; 140 | } 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/AlbumFactory.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileFilter; 6 | import java.io.FileReader; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.Comparator; 12 | import java.util.Date; 13 | import java.util.List; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.model.Album; 17 | import cn.blackgray.douban.album.download.model.BGImage; 18 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 19 | import cn.blackgray.douban.album.download.service.handler.AlbumHandlerFactory; 20 | 21 | /** 22 | * 相册工厂 23 | * @author BlackGray 24 | */ 25 | public class AlbumFactory { 26 | 27 | /** 28 | * 根据地址获取相册 29 | * @param url 30 | * @return 31 | */ 32 | public static List getFromURL(String url){ 33 | List albums = new ArrayList(); 34 | //根据地址获取处理器 35 | List albumHandlers = AlbumHandlerFactory.getHandler(url); 36 | for (AlbumHandler albumHandler : albumHandlers) { 37 | Album album = new Album(); 38 | album.setAlbumHandler(albumHandler); 39 | album.setUrl(albumHandler.getAlbumURL()); 40 | albums.add(album); 41 | } 42 | return albums; 43 | } 44 | 45 | /** 46 | * 根据本地文件获取相册 47 | * @return 48 | * @throws IOException 49 | */ 50 | public static Album getFromLocalFile(String path) throws IOException{ 51 | Album album = new Album(); 52 | File descFile = new File(path + File.separator + Common.DEFAULT_DOC_NAME); 53 | String url = ""; 54 | String charset = "GBK"; 55 | String albumDesc = null; 56 | Date downloadTime = null; 57 | List photosList = new ArrayList(); 58 | final List imageNameList = new ArrayList(); 59 | //【1】如果文件存在,获取信息(相册地址,照片地址,描述) 60 | if (descFile.exists()) { 61 | BufferedReader reader = new BufferedReader(new FileReader(descFile)); 62 | String str; 63 | //获取相册信息 64 | //首行为相册基本信息,根据长度不同,附带的信息内容有增加 65 | if ((str = reader.readLine()) != null) { 66 | String[] strArray = str.trim().split(" ",4); 67 | if (strArray.length == 1) { 68 | url = strArray[0]; 69 | } 70 | if (strArray.length == 2) { 71 | charset = strArray[0]; 72 | url = strArray[1]; 73 | } 74 | if (strArray.length == 3) { 75 | charset = strArray[0]; 76 | url = strArray[1]; 77 | downloadTime = new Date(Long.valueOf(strArray[2])); 78 | } 79 | if (strArray.length == 4) { 80 | charset = strArray[0]; 81 | url = strArray[1]; 82 | downloadTime = new Date(Long.valueOf(strArray[2])); 83 | albumDesc = strArray[3]; 84 | } 85 | List handlerList = AlbumHandlerFactory.getHandler(url,false); 86 | if (handlerList != null && handlerList.size() != 0) { 87 | album.setAlbumHandler(handlerList.get(0)); 88 | } 89 | } 90 | //获取图片信息 91 | List bgImages = album.getAlbumHandler().getBGImageFromDescDoc(descFile); 92 | photosList.addAll(bgImages); 93 | boolean flag = true; 94 | for (BGImage bgImage : bgImages) { 95 | imageNameList.add(bgImage.getName()); 96 | //根据第一张图片名称判断是否为私密相册,私密相册图片名称前缀为x,公共为p 97 | if (flag) { 98 | if (bgImage.getName().indexOf("x") >=0 ) { 99 | album.setIsPrivateAlbum(flag); 100 | } 101 | flag = false; 102 | } 103 | } 104 | reader.close(); 105 | } 106 | 107 | //【2】添加文档中没有,但目录下有的图片 108 | File dir = new File(path); 109 | File[] images = dir.listFiles(new FileFilter() { 110 | @Override 111 | public boolean accept(File file) { 112 | if (!imageNameList.contains(file.getName())) { 113 | if (file.isFile()) { 114 | String name = file.getName(); 115 | return name.substring(name.lastIndexOf(".") + 1).matches("(" + Common.IMAGE_TYPE + ")"); 116 | }else{ 117 | return false; 118 | } 119 | }else{ 120 | return false; 121 | } 122 | } 123 | }); 124 | String imagePath; 125 | List list = new ArrayList(Arrays.asList(images)); 126 | Collections.sort(list,new Comparator() { 127 | @Override 128 | public int compare(File o1, File o2) { 129 | return (int) (o1.lastModified() - o2.lastModified()); 130 | } 131 | }); 132 | for (int i = 0; i < list.size(); i++) { 133 | File imageFile = list.get(i); 134 | imagePath = imageFile.getAbsolutePath().replaceAll("\\\\", "/"); 135 | BGImage bgImage = new BGImage("O-" + (i + 1), imagePath, imageFile.getName()); 136 | photosList.add(bgImage); 137 | } 138 | path = path.replaceAll("\\\\", "/"); 139 | album.setName(path.substring(path.lastIndexOf("/") + 1)); 140 | album.setUrl(url); 141 | album.setCharset(charset); 142 | album.setPath(path); 143 | album.setPhotosList(photosList); 144 | album.setDesc(albumDesc); 145 | album.setDate(downloadTime); 146 | return album; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/PersonageAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.util.Map; 4 | 5 | import cn.blackgray.douban.album.download.common.Common; 6 | import cn.blackgray.douban.album.download.model.Album; 7 | import cn.blackgray.douban.album.download.model.BGImage; 8 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 9 | 10 | /** 11 | * 人士相册处理器 12 | * 13 | * 豆瓣2024年改版,将原有影人(celebrity)、音乐人(musician)合并为人士(personage)。 14 | */ 15 | public class PersonageAlbumHandler extends AlbumHandler { 16 | 17 | // 人士相册示例地址 18 | // https://www.douban.com/personage/27246297/ 19 | // https://www.douban.com/personage/27246297/photos/ 20 | 21 | public static final int PAGE_SIZE_IMAGES_CELEBRITY = 30;//影人照片分页大小(一页30张图) 22 | public static final String PAGE_TAG = "start"; 23 | public static final String IMAGE_NAME_REGEX = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 24 | public static final String ALBUM_URL_REGEX = "(http|https)://www.douban.com/personage/\\d+/photos/"; 25 | 26 | 27 | @Override 28 | public String getURLRegex() { 29 | return ALBUM_URL_REGEX; 30 | } 31 | 32 | @Override 33 | public String albumNameProcess(String name) { 34 | return name = "人士-" + super.albumNameProcess(name); 35 | } 36 | 37 | @Override 38 | public String getPageRegex() { 39 | //影人相册分页含有多个参数 40 | //?start=30&sortby=like&size=a&subtype=a 41 | //?start=30&sortby=comment&size=a&subtype=a 42 | //?start=30&sortby=time&size=a&subtype=a 43 | return "\\?\\w+=\\d+"; 44 | } 45 | 46 | @Override 47 | public boolean removeURLParameter() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public Integer getPageSize() { 53 | return PAGE_SIZE_IMAGES_CELEBRITY; 54 | } 55 | 56 | @Override 57 | public String getPageTag() { 58 | return PAGE_TAG; 59 | } 60 | 61 | @Override 62 | public String getImageNameRegex() { 63 | return IMAGE_NAME_REGEX; 64 | } 65 | 66 | 67 | @Override 68 | public boolean hasRaw(Album album) { 69 | return true; 70 | } 71 | 72 | @Override 73 | public String getRawURL(String imageURL) { 74 | //https://img2.doubanio.com/view/photo/photo/public/p2905078171.webp 75 | //https://img2.doubanio.com/view/photo/l/public/p2905078171.webp 76 | //https://img2.doubanio.com/view/photo/raw/public/p2905078171.jpg 77 | return imageURL.replace("photo/l", "photo/raw").trim(); 78 | } 79 | 80 | @Override 81 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 82 | // ===============图片描述=============== 83 | //
  • 84 | // 85 | // 86 | // 87 | //
    1334x719
    88 | //
    鼻血。。。 89 | // 51回应 90 | //
    91 | //
  • 92 | String imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 93 | String personageId = pageURL.substring(pageURL.indexOf("personage/") + 10, pageURL.indexOf("/photos")); 94 | //【描述】 95 | String startIndexStr = ""; 100 | int start = source.indexOf(str, (descStartIndex + startIndexStr.length())); 101 | desc = source.substring(start + str.length(), source.indexOf("<",start+str.length())); 102 | }else{ 103 | desc = ""; 104 | } 105 | //【照片评论数】 106 | //51回应 107 | String commentTatolStartIndexStr = ""; 108 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 109 | Integer commentTatol = null; 110 | if (commentTatolStartIndex != -1) { 111 | //“51回应” 112 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 113 | commentTatol = Integer.valueOf(s.replace("回应", "")); 114 | } 115 | //【照片】 116 | imageURL = imageURL.replace("photo/photo", "photo/l").trim(); //thumb——>photo:缩略图——>大图 117 | desc = desc.replace("\\t\\n","").trim(); 118 | if (!map.containsKey(imageURL)) { 119 | BGImage bgImage = new BGImage(desc, imageURL, commentTatol); 120 | map.put(imageURL, bgImage); 121 | } 122 | } 123 | 124 | @Override 125 | public boolean checkBGImage(BGImage bgImage) { 126 | return bgImage.getUrl().indexOf("albumicon") < 0; 127 | } 128 | 129 | @Override 130 | public String getCommentURL(Album album, BGImage image) { 131 | //https://www.douban.com/personage/27503633/photos/ 132 | //https://www.douban.com/personage/27503633/photo/2145121738/ 133 | return album.getUrl().replace("photos", "photo") + "/" + image.getId(); 134 | } 135 | 136 | @Override 137 | public String getAlbumDesc(String source) { 138 | return null; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/SiteAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.util.Map; 4 | 5 | import cn.blackgray.douban.album.download.common.Common; 6 | import cn.blackgray.douban.album.download.model.Album; 7 | import cn.blackgray.douban.album.download.model.BGImage; 8 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 9 | 10 | /** 11 | * 小站相册处理器 12 | */ 13 | public class SiteAlbumHandler extends AlbumHandler { 14 | 15 | public static final int PAGE_SIZE_IMAGES_SITE = 30; //小站照片分页大小(一页30张图) 16 | public static final String PAGE_TAG = "start"; 17 | public static final String IMAGE_NAME_REGEX = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 18 | public static final String ALBUM_URL_REGEX = "(http|https)://site.douban.com/\\d+/widget/photos/\\d+/"; 19 | 20 | @Override 21 | public String getURLRegex() { 22 | return ALBUM_URL_REGEX; 23 | } 24 | 25 | @Override 26 | public String getPageRegex() { 27 | return "/widget/photos/\\d+/\\?\\w+=\\d+"; 28 | } 29 | 30 | @Override 31 | public boolean removeURLParameter() { 32 | return true; 33 | } 34 | 35 | @Override 36 | public Integer getPageSize() { 37 | return PAGE_SIZE_IMAGES_SITE; 38 | } 39 | 40 | @Override 41 | public String getPageTag() { 42 | return PAGE_TAG; 43 | } 44 | 45 | @Override 46 | public String getImageNameRegex() { 47 | return IMAGE_NAME_REGEX; 48 | } 49 | 50 | 51 | @Override 52 | public boolean hasRaw(Album album) { 53 | return true; 54 | } 55 | 56 | @Override 57 | public String getRawURL(String imageURL) { 58 | // https://img3.doubanio.com/view/photo/photo/public/p1826089744.webp 59 | // https://img3.doubanio.com/view/photo/raw/public/p1826089744.jpg 60 | return imageURL.replace("photo/photo", "photo/raw").trim(); 61 | } 62 | 63 | @Override 64 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 65 | 66 | // =================旧 67 | // http://site.douban.com/widget/photos/1803367/ 68 | // http://site.douban.com/widget/photos/1803367/?start=0 69 | //
  • 70 | //
    71 | // 72 | //
    73 | // 74 | //

    Dina Larot. Kun...

    75 | // 5推荐 76 | //
    77 | //
    78 | //
  • 79 | // =================新 80 | // http://site.douban.com/108128/widget/photos/7398196/ 81 | // 84 | // 85 | //
    86 | // 87 | //

    一瞬间的温暖耗...

    88 | // 1回应 89 | //
    90 | // 91 | // 92 | 93 | String imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 94 | String siteAlbumId = pageURL.substring(pageURL.indexOf("photos/") + 7, pageURL.lastIndexOf("/")); 95 | //【描述】 96 | String startIndexStr = pageURL.substring(0,pageURL.indexOf(siteAlbumId)) + siteAlbumId + "/photo/" + imageId + "/\" title=\""; 97 | int descStartIndex = source.indexOf(startIndexStr); 98 | String desc; 99 | if (descStartIndex != -1) { 100 | int start = descStartIndex + startIndexStr.length(); 101 | desc = source.substring(start, source.indexOf("\"",start)); 102 | }else{ 103 | desc = ""; 104 | } 105 | //【照片评论数】 106 | //1回应 107 | String commentTatolStartIndexStr = pageURL.substring(0,pageURL.indexOf(siteAlbumId)) + siteAlbumId + "/photo/" + imageId + "/#comments\">"; 108 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 109 | Integer commentTatol = null; 110 | if (commentTatolStartIndex != -1) { 111 | //“3回应” 112 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 113 | commentTatol = Integer.valueOf(s.replace("回应", "")); 114 | } 115 | //【照片】 116 | // http://img1.douban.com/view/photo/albumicon/public/p1097123994.jpg 117 | imageURL = imageURL.replace("thumb", "photo").trim(); //thumb——>photo:缩略图——>大图 118 | desc = desc.replace("\\t\\n","").trim(); 119 | if (!map.containsKey(imageURL) && !imageURL.contains("sqs")) { 120 | BGImage bgImage = new BGImage(desc, imageURL, commentTatol); 121 | map.put(imageURL, bgImage); 122 | } 123 | 124 | } 125 | 126 | 127 | @Override 128 | public boolean checkBGImage(BGImage bgImage) { 129 | return bgImage.getUrl().indexOf("albumicon") < 0; 130 | } 131 | 132 | @Override 133 | public String getCommentURL(Album album, BGImage image) { 134 | //http://site.douban.com/widget/photos/1164317/ 135 | //http://site.douban.com/widget/photos/1164317/photo/1597074577/ 136 | return album.getUrl() + "photo/" + image.getId(); 137 | } 138 | 139 | @Override 140 | public String getAlbumDesc(String source) { 141 | return null; 142 | } 143 | 144 | } 145 | 146 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | 4 | import java.io.File; 5 | import java.io.FilenameFilter; 6 | import java.io.IOException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.net.URLDecoder; 11 | import java.util.ArrayList; 12 | import java.util.Enumeration; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import java.util.jar.JarEntry; 19 | import java.util.jar.JarFile; 20 | 21 | /** 22 | * 反射工具类 23 | * @author BlackGray 24 | */ 25 | public class ReflectUtils { 26 | 27 | public static Integer PARA_TYPE_INCLUDE = 0; 28 | public static Integer PARA_TYPE_EXCLUDE = 1; 29 | 30 | /** 31 | * 通过反射获取对象的所有属性 32 | * 返回MAP集合 33 | * 键值为对象属性名称 34 | * @param obj 35 | * @return 36 | * @throws IllegalArgumentException 37 | * @throws IllegalAccessException 38 | * @throws InvocationTargetException 39 | */ 40 | public static Map getFieldValues(Object obj) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{ 41 | return getFieldValues(obj,null); 42 | } 43 | 44 | public static Map getFieldValues(Object obj,Integer paraType, Object... paras) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException{ 45 | //如果有参数,判断参数类型并处理 46 | Set feildNameSet = new HashSet(); 47 | if (paraType != null) { 48 | if (paraType == PARA_TYPE_INCLUDE || paraType == PARA_TYPE_EXCLUDE) { 49 | for (String name : paras[0].toString().split(",")) { 50 | feildNameSet.add(name.trim()); 51 | } 52 | } 53 | } 54 | Map map = new HashMap(); 55 | Method[] methods = obj.getClass().getMethods(); 56 | for (int i = 0; i < methods.length; i++) { 57 | String methodName = methods[i].getName(); 58 | if (methodName.startsWith("get") || methodName.startsWith("is")) { 59 | //判断返回值类型,获取变量名 60 | Class returnTypeClass = methods[i].getReturnType(); 61 | String feildName; 62 | if(returnTypeClass.equals(boolean.class) || returnTypeClass.equals(Boolean.class)){ 63 | feildName = methodName.substring(2,3).toLowerCase() + methodName.substring(3); 64 | }else{ 65 | feildName = methodName.substring(3,4).toLowerCase() + methodName.substring(4); 66 | } 67 | boolean flag = true; 68 | if (paraType == PARA_TYPE_INCLUDE) { 69 | flag = feildNameSet.contains(feildName); 70 | } 71 | if (paraType == PARA_TYPE_EXCLUDE) { 72 | flag = !feildNameSet.contains(feildName); 73 | } 74 | if (flag) { 75 | //获取变量类型 76 | //Type type = methods[i].getGenericReturnType(); 77 | map.put(feildName, methods[i].invoke(obj)); 78 | } 79 | } 80 | } 81 | return map; 82 | } 83 | 84 | /** 85 | * 根据包获取所有类 86 | * @param pkg 87 | * @return 88 | */ 89 | public static List> getClassWithPackage(String pkg){ 90 | //判断环境&//获取JAR包 91 | try { 92 | String jarPath = URLDecoder.decode(ReflectUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(),"utf-8"); 93 | File file = new File(jarPath); 94 | if (file.isFile() && file.getName().endsWith("jar")) { 95 | JarFile jar = new JarFile(jarPath); 96 | return getClassWithPackageFromJar(jar,pkg); 97 | }else{ 98 | return getClassWithPackageFromDir(pkg); 99 | } 100 | } catch (UnsupportedEncodingException e1) { 101 | e1.printStackTrace(); 102 | } catch (IOException e) { 103 | e.printStackTrace(); 104 | } catch (ClassNotFoundException e) { 105 | e.printStackTrace(); 106 | } 107 | return null; 108 | } 109 | 110 | /** 111 | * 从Jar中加载类文件 - 运行环境中使用 112 | * @param pkg 113 | * @return 114 | * @throws IOException 115 | * @throws ClassNotFoundException 116 | */ 117 | private static List> getClassWithPackageFromJar(JarFile jar,String pkg) throws IOException, ClassNotFoundException{ 118 | //获取所有指定包内的类 119 | String pakageName = pkg.replaceAll("\\.", "/"); 120 | List> list = new ArrayList>(); 121 | ClassLoader loader = ReflectUtils.class.getClassLoader(); 122 | Enumeration e = jar.entries(); 123 | while (e.hasMoreElements()) { 124 | JarEntry jarEntry = (JarEntry) e.nextElement(); 125 | String entryName = jarEntry.getName(); 126 | if (entryName.indexOf(pakageName) != -1 && entryName.endsWith(".class")) { 127 | list.add(loader.loadClass(entryName.replace("/", ".").replace(".class", ""))); 128 | } 129 | } 130 | return list; 131 | } 132 | 133 | /** 134 | * 从目录中加载类文件 - 开发环境中使用 135 | * @param pkg 136 | * @return 137 | * @throws UnsupportedEncodingException 138 | * @throws ClassNotFoundException 139 | */ 140 | private static List> getClassWithPackageFromDir(String pkg) throws UnsupportedEncodingException, ClassNotFoundException{ 141 | List> list = new ArrayList>(); 142 | String jarPath = URLDecoder.decode(ReflectUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(),"utf-8"); 143 | File dir = new File(jarPath + "/" + pkg.replaceAll("\\.", "/")); 144 | File[] files = dir.listFiles(new FilenameFilter() { 145 | @Override 146 | public boolean accept(File dir, String name) { 147 | return name.endsWith(".class"); 148 | } 149 | }); 150 | for (File f : files) { 151 | String className = f.getName().substring(0, f.getName().indexOf(".")); 152 | Class clazz = Class.forName(pkg + "." + className); 153 | list.add(clazz); 154 | } 155 | return list; 156 | } 157 | 158 | 159 | public static void main(String[] args) { 160 | List> list = getClassWithPackage("cn.blackgray.douban.album.download.service.handler.finder.impl"); 161 | for (Class cls : list) { 162 | System.out.println(cls); 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/AlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.common.Console; 17 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 18 | import cn.blackgray.douban.album.download.model.Album; 19 | import cn.blackgray.douban.album.download.model.BGImage; 20 | import cn.blackgray.douban.album.download.service.image.ImageListComparator; 21 | 22 | /** 23 | * 相册处理器父类 - 子类用于处理并获取相册下照片信息(地址、描述、评论数等) 24 | * @author BlackGray 25 | */ 26 | public abstract class AlbumHandler{ 27 | 28 | protected String albumURL; 29 | 30 | public AlbumHandler() { 31 | super(); 32 | } 33 | 34 | public AlbumHandler(String albumURL) { 35 | super(); 36 | this.albumURL = albumURL; 37 | } 38 | 39 | public String getAlbumURL() { 40 | return albumURL; 41 | } 42 | 43 | /** 44 | * 获取相册描述 45 | */ 46 | public abstract String getAlbumDesc(String source); 47 | 48 | 49 | public void setAlbumURL(String albumURL) { 50 | boolean isRemoveURLPara = removeURLParameter(); 51 | if (isRemoveURLPara && (albumURL.indexOf("?") > 0)) { 52 | this.albumURL = albumURL.substring(0, albumURL.indexOf("?")); 53 | }else{ 54 | this.albumURL = albumURL; 55 | } 56 | } 57 | 58 | /** 59 | * 获取页面照片数大小 60 | */ 61 | public abstract Integer getPageSize(); 62 | 63 | 64 | /** 65 | * 获取分页标签 66 | * 豆瓣为start 67 | */ 68 | public abstract String getPageTag(); 69 | 70 | /** 71 | * 相册名称处理 72 | * @param name 73 | * @return 74 | */ 75 | public String albumNameProcess(String name){ 76 | return name; 77 | } 78 | 79 | /** 80 | * 获取相册URL正则 81 | * @return 82 | */ 83 | public abstract String getURLRegex(); 84 | 85 | /** 86 | * 获取相册分页正则 87 | * @return 88 | */ 89 | public abstract String getPageRegex(); 90 | 91 | /** 92 | * 图片名称正则 93 | * 可提高准确率 94 | * @return 95 | */ 96 | public abstract String getImageNameRegex(); 97 | 98 | /** 99 | * 是否有大图 100 | * @return 101 | */ 102 | public boolean hasRaw(Album album){ 103 | return false; 104 | } 105 | 106 | /** 107 | * 获取大图地址 108 | * @return 109 | */ 110 | public abstract String getRawURL(String imageURL); 111 | 112 | /** 113 | * 获取评论地址 114 | * @param album 115 | * @param image 116 | * @return 117 | */ 118 | public abstract String getCommentURL(Album album, BGImage image); 119 | 120 | 121 | /** 122 | * 删除URL中的参数 123 | * @return 124 | */ 125 | public boolean removeURLParameter(){ 126 | return false; 127 | } 128 | 129 | 130 | /** 131 | * 根据源码和图片路径分析并创建图片对象 132 | * @param source 133 | * @param imageURL 134 | * @param map 135 | * @param imageURL2 136 | * @return 137 | */ 138 | public abstract void createBGImage(Album album, String source,String pageURL, String imageURL, Map map); 139 | 140 | /** 141 | * 大图地址处理 142 | * @return 143 | */ 144 | public boolean checkBGImage(BGImage bgImage){ 145 | return true; 146 | } 147 | 148 | /** 149 | * 创建描述文档 150 | * @param album 151 | */ 152 | public void createDescDoc(Album album) { 153 | List imageList = album.getPhotosList(); 154 | Map map = new HashMap(); 155 | for (BGImage bgImage : imageList) { 156 | map.put(bgImage.getUrl(), bgImage); 157 | } 158 | List keyList = new ArrayList(map.keySet()); 159 | //排序 160 | Collections.sort(keyList,new ImageListComparator()); 161 | try { 162 | BufferedWriter bw = new BufferedWriter(new FileWriter(album.getPath() + "/" + Common.DEFAULT_DOC_NAME)); 163 | //输出相册地址 164 | if (album.getDesc() == null) { 165 | bw.write(URLUtils.charset + " " + album.getUrl() + " " + album.getDate().getTime() + " -"); 166 | }else{ 167 | bw.write(URLUtils.charset + " " + album.getUrl() + " " + album.getDate().getTime() + " " + album.getDesc()); 168 | } 169 | bw.newLine(); 170 | //输出照片地址和描述,格式:序号 + 评论数 + 照片地址 + 描述 171 | for (int i = 0; i < keyList.size(); i++) { 172 | BGImage bgImage = map.get(keyList.get(i)); 173 | Integer commentTotal = bgImage.getCommentTotal(); 174 | String commentTotalStr = commentTotal==null?"-":String.valueOf(commentTotal); 175 | bw.write((i + 1) + " " + keyList.get(i) + " " + commentTotalStr + " " + bgImage.getDesc()); 176 | bw.newLine(); 177 | } 178 | bw.flush(); 179 | bw.close(); 180 | Console.print("生成描述文档:成功"); 181 | } catch (IOException e) { 182 | Console.print("生成描述文档:失败"); 183 | e.printStackTrace(); 184 | } 185 | } 186 | 187 | /** 188 | * 从描述文档获取图片集合 189 | * @param descFile 190 | * @return 191 | * @throws IOException 192 | */ 193 | public List getBGImageFromDescDoc(File descFile) throws IOException{ 194 | List list = new ArrayList(); 195 | if (descFile.exists()) { 196 | BufferedReader reader = new BufferedReader(new FileReader(descFile)); 197 | String str; 198 | int line = 0; 199 | while ((str = reader.readLine()) != null) { 200 | if (line == 0) { 201 | line++; 202 | }else{ 203 | //获取照片地址&描述信息 204 | String[] info = str.split(" ",4); 205 | //info[0],info[1],info[2],info[3]分别为照片编号、原始URL地址、评论数、照片描述 206 | BGImage bgImage = new BGImage(info[0],info[1],info[3]); 207 | //评论数 208 | if (!info[2].equals("-")) { 209 | bgImage.setCommentTotal(Integer.valueOf(info[2])); 210 | } 211 | list.add(bgImage); 212 | } 213 | } 214 | reader.close(); 215 | } 216 | return list; 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/PageAnalyzer.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler; 2 | 3 | import java.net.MalformedURLException; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Map.Entry; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import cn.blackgray.douban.album.download.common.Common; 13 | import cn.blackgray.douban.album.download.common.Console; 14 | import cn.blackgray.douban.album.download.common.utils.HTMLUtils; 15 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 16 | import cn.blackgray.douban.album.download.model.Album; 17 | import cn.blackgray.douban.album.download.model.BGImage; 18 | 19 | /** 20 | * 页面分析器 21 | * @author BlackGray 22 | */ 23 | public class PageAnalyzer { 24 | 25 | private static final String DEFAULT_ALBUM_NAME = "NO-NAME"; 26 | 27 | private static String source; 28 | 29 | 30 | /** 31 | * 获取相册名称 32 | * @return 33 | */ 34 | public static String findAlbumName() { 35 | String name; 36 | //判断标题是否存在 37 | if (source.indexOf("") != -1) { 38 | name = source.substring(source.indexOf("<title>") + 7,source.indexOf("")).replace("\\t\\n","").trim(); 39 | }else{ 40 | return DEFAULT_ALBUM_NAME; 41 | } 42 | if (name.length() != 0) { 43 | //转换HTML中的特殊字符 44 | name = HTMLUtils.htmlToText(name); 45 | //去除结尾的点,防止windows文件夹命名错误 46 | while(name.endsWith(".")){ 47 | name = name.substring(0, name.length() - 1); 48 | } 49 | //过滤windows下文件名不能使用的字符,并返回结果 50 | return name.replaceAll("[\\\\ / : \\* \\? < > \\|]", " "); 51 | }else{ 52 | return DEFAULT_ALBUM_NAME; 53 | } 54 | } 55 | 56 | /** 57 | * 获取相册描述 58 | * @return 59 | */ 60 | public static String findAlbumDesc(AlbumHandler albumHandler) { 61 | String desc = albumHandler.getAlbumDesc(source); 62 | if (desc != null) { 63 | return HTMLUtils.htmlToText(desc); 64 | }else{ 65 | return null; 66 | } 67 | } 68 | 69 | 70 | /** 71 | * 根据相册地址,获取所有分页地址 72 | * @param albumURL 73 | * @return 74 | * @throws MalformedURLException 75 | */ 76 | public static List findPageURL(AlbumHandler albumHandler) { 77 | //相册地址 78 | String albumURL = albumHandler.getAlbumURL(); 79 | //页面集合 80 | List pageURLList = new ArrayList(); 81 | source = URLUtils.readSource(albumURL); 82 | String regex = albumHandler.getPageRegex(); 83 | if (regex != null) { 84 | Pattern p = Pattern.compile(regex); 85 | Matcher m = p.matcher(source); 86 | //查找出页面中所有分页连接 87 | int maxStartNum = 0; 88 | while (m.find()) { 89 | String url = m.group(); 90 | int num = 0; 91 | Pattern tp = Pattern.compile(albumHandler.getPageTag() + "=\\d+"); 92 | Matcher tm = tp.matcher(url); 93 | if (tm.find()) { 94 | String pageTagStr = tm.group(); 95 | num = Integer.parseInt(pageTagStr.substring(pageTagStr.lastIndexOf("=") + 1)); 96 | } 97 | //找到分页值 98 | maxStartNum = num > maxStartNum ? num : maxStartNum; 99 | } 100 | //根据最大起始数字,生成页面地址 101 | int size = albumHandler.getPageSize(); 102 | for (int i = 0; i <= maxStartNum; i+=size) { 103 | String pageURL = albumURL; 104 | if (albumURL.indexOf("?") == -1) { 105 | pageURL += "?"; 106 | }else{ 107 | pageURL += "&"; 108 | } 109 | pageURL += albumHandler.getPageTag() + "=" + i; 110 | pageURLList.add(pageURL); 111 | } 112 | }else{ 113 | pageURLList.add(albumURL); 114 | } 115 | 116 | for (int i = 0; i < pageURLList.size(); i++) { 117 | Console.print("获取图片地址-页面(" + (i+1) + "/" + pageURLList.size() + "):" + pageURLList.get(i)); 118 | } 119 | return pageURLList; 120 | }; 121 | 122 | 123 | /** 124 | * 根据分页地址,获取所有图片信息(地址、描述、所有者等) 125 | * @param pageURL 126 | * @return 127 | */ 128 | public static Map findImageURLAndDesc(Album album,String pageURL) { 129 | // http://www.douban.com/photos/album/65283821/?start=0 - 豆瓣相册单页地址 130 | // http://www.douban.com/photos/photo/1424544615/ - 豆瓣图片页地址 131 | // http://img3.douban.com/view/photo/photo/public/p1424544615.jpg - 豆瓣图片地址 132 | 133 | Map result = new HashMap(); 134 | //读取页面源码 135 | String source = URLUtils.readSource(pageURL); 136 | 137 | //判断相册是否仅自己可见 138 | boolean isVisibleToOneself = source.indexOf("这个相册已被设为“仅自己可见”,抱歉你无法访问。") > 0; 139 | if (isVisibleToOneself) { 140 | //状态设置为true,在DownloadProcessing的downloadAlbum方法中做统一提醒输出。 141 | album.setIsVisibleToSelf(true); 142 | } 143 | //判断相册是否仅朋友可见 144 | boolean isVisibleToFriend = source.indexOf("这个相册设置为仅朋友可见。你还不是他/她的朋友,所以无法访问。") > 0; 145 | if (isVisibleToFriend) { 146 | //状态设置为true,在DownloadProcessing的downloadAlbum方法中做统一提醒输出。 147 | album.setIsVisibleToFriend(true); 148 | } 149 | 150 | 151 | //获取单页所有图片地址 152 | String regex = "(http|https)://(\\w|\\s|\\.|-|_|/)+[\\.](" + Common.IMAGE_TYPE + ")"; 153 | Pattern p = Pattern.compile(regex); 154 | Matcher m = p.matcher(source); 155 | AlbumHandler albumHandler = album.getAlbumHandler(); 156 | while (m.find()) { 157 | String imageURL = m.group().trim(); 158 | //获取名字 159 | String imageName = imageURL.substring(imageURL.lastIndexOf("/") + 1); 160 | //分析并创建图片对象 161 | String imageNameRegex = albumHandler.getImageNameRegex(); 162 | if (imageNameRegex == null || (imageNameRegex != null && imageName.matches(imageNameRegex))) { 163 | albumHandler.createBGImage(album, source, pageURL, imageURL, result); 164 | } 165 | } 166 | 167 | //去除albumicon图片 168 | List removeList = new ArrayList(); 169 | for (Entry element : result.entrySet()) { 170 | if(!albumHandler.checkBGImage(element.getValue())){ 171 | removeList.add(element.getKey()); 172 | } 173 | } 174 | for (String key : removeList) { 175 | result.remove(key); 176 | } 177 | return result; 178 | } 179 | 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/MovieAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.util.Map; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import cn.blackgray.douban.album.download.common.Common; 8 | import cn.blackgray.douban.album.download.model.Album; 9 | import cn.blackgray.douban.album.download.model.BGImage; 10 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 11 | 12 | /** 13 | * 电影相册处理器 14 | */ 15 | public class MovieAlbumHandler extends AlbumHandler { 16 | 17 | //电影首页 18 | //http://movie.douban.com/subject/3652297/?from=hot_movie 19 | //电影相册首页 20 | //http://movie.douban.com/subject/3652297/all_photos 21 | //剧照 22 | //http://movie.douban.com/subject/3652297/photos?type=S 23 | //海报 24 | //http://movie.douban.com/subject/3652297/photos?type=R 25 | //壁纸 26 | //http://movie.douban.com/subject/3652297/photos?type=W 27 | 28 | public static final int PAGE_SIZE_IMAGES_MOVIE = 30;//电影照片分页大小(一页40张图) 29 | public static final String PAGE_TAG = "start"; 30 | public static final String IMAGE_NAME_REGEX = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 31 | public static final String ALBUM_URL_REGEX = "(http|https)://movie.douban.com/subject/\\d+/photos\\?(\\w+=\\w+&*)+"; 32 | 33 | @Override 34 | public String getURLRegex() { 35 | return ALBUM_URL_REGEX; 36 | } 37 | 38 | @Override 39 | public String getPageRegex() { 40 | String url = getAlbumURL(); 41 | if (url.indexOf("?") != -1) { 42 | url = url.substring(0,url.indexOf("?")); 43 | } 44 | System.out.println("=============="); 45 | System.out.println(getAlbumURL() + "\\?(\\w+=\\w+&*(amp;)*)+"); 46 | System.out.println(url + "\\?(\\w+=\\w+&*(amp;)*)+"); 47 | System.out.println("=============="); 48 | return url + "\\?(\\w+=\\w+&*(amp;)*)+"; 49 | } 50 | 51 | @Override 52 | public void setAlbumURL(String albumURL) { 53 | super.setAlbumURL(albumURL); 54 | Pattern p = Pattern.compile("type=\\w+"); 55 | Matcher m = p.matcher(albumURL); 56 | if(m.find()) { 57 | this.albumURL += ("?" + m.group()); 58 | } 59 | } 60 | 61 | @Override 62 | public String getAlbumURL() { 63 | if (albumURL.endsWith("/")) { 64 | return albumURL.substring(0, albumURL.length() - 1); 65 | } 66 | return albumURL; 67 | } 68 | 69 | @Override 70 | public boolean removeURLParameter() { 71 | return true; 72 | } 73 | 74 | @Override 75 | public Integer getPageSize() { 76 | return PAGE_SIZE_IMAGES_MOVIE; 77 | } 78 | 79 | @Override 80 | public String getPageTag() { 81 | return PAGE_TAG; 82 | } 83 | 84 | @Override 85 | public String getImageNameRegex() { 86 | return IMAGE_NAME_REGEX; 87 | } 88 | 89 | 90 | @Override 91 | public boolean hasRaw(Album album) { 92 | return true; 93 | } 94 | 95 | @Override 96 | public String getRawURL(String imageURL) { 97 | return imageURL.replace("photo/l", "photo/raw").trim(); 98 | } 99 | 100 | @Override 101 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 102 | //
  • 103 | //
    104 | // 105 | // 106 | // 107 | //
    108 | // 109 | //
    110 | // 1024x576 111 | //
    112 | //
    113 | // 你妹啊!!!说好了的... 114 | // 1回应 115 | //
    116 | //
  • 117 | String imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 118 | //【描述】 119 | String startIndexStr = ""; 120 | int descStartIndex = source.indexOf(startIndexStr); 121 | String desc; 122 | if (descStartIndex != -1) { 123 | String str = "
    "; 124 | int start = source.indexOf(str, (descStartIndex + startIndexStr.length())); 125 | desc = source.substring(start + str.length(), source.indexOf("<",start+str.length())); 126 | }else{ 127 | desc = ""; 128 | } 129 | //【照片评论数】 130 | //29回应 131 | String commentTatolStartIndexStr = ""; 132 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 133 | Integer commentTatol = null; 134 | if (commentTatolStartIndex != -1) { 135 | //“3回应” 136 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 137 | commentTatol = Integer.valueOf(s.replace("回应", "")); 138 | } 139 | //【照片】 140 | // http://img3.douban.com/view/photo/thumb/public/p2125663360.jpg 141 | // http://img3.douban.com/view/photo/photo/public/p2109950882.jpg 142 | imageURL = imageURL.replace("photo/m", "photo/l").trim(); //thumb——>photo:缩略图——>大图 143 | desc = desc.replace("\\t\\n","").trim(); 144 | if (!map.containsKey(imageURL)) { 145 | BGImage bgImage = new BGImage(desc, imageURL, commentTatol); 146 | map.put(imageURL, bgImage); 147 | } 148 | } 149 | 150 | @Override 151 | public boolean checkBGImage(BGImage bgImage) { 152 | return bgImage.getUrl().indexOf("albumicon") < 0; 153 | } 154 | 155 | @Override 156 | public String getCommentURL(Album album, BGImage image) { 157 | //http://img3.douban.com/view/photo/thumb/public/p2109950882.jpg 158 | //http://movie.douban.com/photos/photo/2109950882/ 159 | return "http://movie.douban.com/photos/photo/" + image.getId(); 160 | } 161 | 162 | @Override 163 | public String getAlbumDesc(String source) { 164 | return null; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/AlbumHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | 9 | import cn.blackgray.douban.album.download.common.Console; 10 | import cn.blackgray.douban.album.download.common.utils.ReflectUtils; 11 | import cn.blackgray.douban.album.download.service.handler.finder.IAlbumURLFinder; 12 | import cn.blackgray.douban.album.download.service.handler.handler.DefaultAlbumHandler; 13 | 14 | 15 | /** 16 | * 相册处理器工厂类,根据不同的消息,生成不同的消息处理器实现 17 | */ 18 | public class AlbumHandlerFactory { 19 | 20 | public static final String PACKAGE_FINDER = "cn.blackgray.douban.album.download.service.handler.finder.impl"; 21 | public static final String PACKAGE_HANDER = "cn.blackgray.douban.album.download.service.handler.handler"; 22 | 23 | public static Map albumURLFinderMap = new HashMap(); 24 | public static Map> albumHandlerClassMap = new HashMap>(); 25 | 26 | 27 | static{ 28 | //反射获取所有相册地址查询器对象 29 | List> finderClassList = ReflectUtils.getClassWithPackage(PACKAGE_FINDER); 30 | for (Class finderClass : finderClassList) { 31 | try { 32 | IAlbumURLFinder obj = (IAlbumURLFinder) finderClass.newInstance(); 33 | albumURLFinderMap.put(obj.getURLRegex(), obj); 34 | } catch (InstantiationException e) { 35 | e.printStackTrace(); 36 | } catch (IllegalAccessException e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | //反射获取所有处理器类 41 | List> handlerClassList = ReflectUtils.getClassWithPackage(PACKAGE_HANDER); 42 | for (Class handerClass : handlerClassList) { 43 | try { 44 | AlbumHandler obj = (AlbumHandler) handerClass.newInstance(); 45 | albumHandlerClassMap.put(obj.getURLRegex(), handerClass); 46 | } catch (InstantiationException e) { 47 | e.printStackTrace(); 48 | } catch (IllegalAccessException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | } 53 | 54 | public static List getHandler(String url) { 55 | return getHandler(url,true); 56 | } 57 | 58 | public static List getHandler(String url, boolean isPrintLog) { 59 | List albumURLList = new ArrayList(); 60 | //1.查看是否存在URL查询器,如果有,执行查询,获取相册地址 61 | boolean hasFinder = false; 62 | for (Entry element : albumURLFinderMap.entrySet()) { 63 | if (url.matches(element.getKey())) { 64 | IAlbumURLFinder albumURLFinder = element.getValue(); 65 | if (albumURLFinder.getFindFailMsg() != null) { 66 | //提示相册地址解析失败 67 | Console.print("地址:" + url); 68 | Console.print(albumURLFinder.getFindFailMsg()); 69 | hasFinder = true; 70 | break; 71 | }else { 72 | //正常获取相册地址 73 | List albumURLs = albumURLFinder.findAlbumURL(url); 74 | for (String u : albumURLs) { 75 | if(isPrintLog){ 76 | Console.print("获取相册地址:" + u); 77 | } 78 | } 79 | albumURLList.addAll(albumURLs); 80 | hasFinder = true; 81 | break; 82 | } 83 | } 84 | } 85 | if (hasFinder == false) { 86 | albumURLList.add(url); 87 | if(isPrintLog){ 88 | Console.print("获取图片地址:" + url); 89 | } 90 | } 91 | //2.根据相册地址,获取处理器 92 | List handlerList = new ArrayList(); 93 | boolean hasHander = false; 94 | for (String albumURL : albumURLList) { 95 | for (Entry> element : albumHandlerClassMap.entrySet()) { 96 | if (element.getKey() != null && albumURL.matches(element.getKey())) { 97 | Class clazz = element.getValue(); 98 | AlbumHandler handler; 99 | try { 100 | handler = (AlbumHandler) clazz.newInstance(); 101 | handler.setAlbumURL(albumURL); 102 | handlerList.add(handler); 103 | if(isPrintLog){ 104 | Console.print("创建相册处理器:" + clazz.getSimpleName() + " - " + albumURL); 105 | } 106 | hasHander = true; 107 | } catch (IllegalArgumentException e) { 108 | e.printStackTrace(); 109 | } catch (SecurityException e) { 110 | e.printStackTrace(); 111 | } catch (InstantiationException e) { 112 | e.printStackTrace(); 113 | } catch (IllegalAccessException e) { 114 | e.printStackTrace(); 115 | } 116 | break; 117 | } 118 | } 119 | if (hasHander == false) { 120 | DefaultAlbumHandler defaultAlbumHandler = new DefaultAlbumHandler(); 121 | defaultAlbumHandler.setAlbumURL(albumURL); 122 | handlerList.add(defaultAlbumHandler); 123 | if(isPrintLog){ 124 | Console.print("创建默认相册处理器:" + defaultAlbumHandler.getClass().getSimpleName() + " - " + albumURL); 125 | } 126 | } 127 | } 128 | return handlerList; 129 | 130 | } 131 | 132 | public static void main(String[] args) { 133 | 134 | System.out.println("-------------相册列表--------------"); 135 | getHandler("http://www.douban.com/people/blackgray/photos/"); 136 | System.out.println("-------------相册--------------"); 137 | getHandler("http://www.douban.com/photos/album/67952443/"); 138 | 139 | System.out.println("-------------小站--------------"); 140 | getHandler("http://site.douban.com/108128/widget/photos/7528342/"); 141 | getHandler("http://site.douban.com/zheng/widget/photos/17304118/"); 142 | 143 | System.out.println("-------------影人首页--------------"); 144 | getHandler("http://movie.douban.com/celebrity/1048027/"); 145 | System.out.println("-------------影人--------------"); 146 | getHandler("http://movie.douban.com/celebrity/1048027/photos/"); 147 | 148 | System.out.println("-------------活动首页--------------"); 149 | getHandler("http://www.douban.com/online/11127307/"); 150 | System.out.println("-------------活动--------------"); 151 | getHandler("http://www.douban.com/online/11127307/album/72416214/"); 152 | 153 | System.out.println("-------------其他--------------"); 154 | getHandler("http://www.baidu.com/"); 155 | 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/ui/FailFileFrame.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
    117 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/Common.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common; 2 | 3 | import java.awt.Desktop; 4 | import java.io.BufferedReader; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URI; 10 | import java.net.URLDecoder; 11 | import java.text.SimpleDateFormat; 12 | 13 | import javax.swing.JFrame; 14 | import javax.swing.JOptionPane; 15 | 16 | import cn.blackgray.douban.album.download.common.utils.CommonUtils; 17 | 18 | /** 19 | * 公共变量 20 | * @author BlackGray 21 | */ 22 | public class Common { 23 | 24 | public static String VERSION = "v20241003"; 25 | 26 | public static final String DEFAULT_DOC_NAME = "描述.txt"; 27 | public static final String DEFAULT_FAIL_FILE_DOC_NAME = "下载失败图片记录.txt"; 28 | public static final String DEFAULT_HTML_NAME = "index.html"; 29 | public static final String DEFAULT_HTML_RESOURCE_DIR = "resource"; 30 | public static final String DEFAULT_RAW_DIR = "raw"; 31 | public static final String DEFAULT_ALBUM_ROOT_PATH_STR = "根目录"; //相册默认根目录占位符 32 | 33 | //时间格式化 34 | public static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 35 | 36 | //图片下载结果&状态 37 | /** 图片下载结果&状态 - 图片已存在 */ 38 | public static final int IMAGE_DOWNLOAD_STATUS_EXISTS = 0; 39 | /** 图片下载结果&状态 - 下载完成 */ 40 | public static final int IMAGE_DOWNLOAD_STATUS_FINISH = 1; 41 | /** 图片下载结果&状态 - 图片网络资源不存在 */ 42 | public static final int IMAGE_DOWNLOAD_STATUS_URL_NOT_EXISTS = 2; 43 | /** 图片下载结果&状态 - 图片下载异常,已下载文件小于网络资源大小 */ 44 | public static final int IMAGE_DOWNLOAD_STATUS_DOWNLOAD_FAIL = 3; 45 | 46 | // public static int albumType; //相册类型 - 普通、豆瓣相册、豆瓣活动 47 | 48 | public static final String URL_HELP = "http://www.douban.com/note/206320326/"; 49 | public static final String URL_DOUPIC = "http://www.douban.com/group/doupic/"; 50 | 51 | public static final long TIME_PROCESS_MIN = 1*60*1000; //边界时间 - 单位毫秒 52 | public static final long TIME_PROCESS_SLEEP = 60; //休眠时间 - 单位秒 53 | public static final long TIME_ONE_PAGE_INFO_PROCESS_SLEEP = 500; //单页面图片信息处理间隔时间 - 单位毫秒 54 | 55 | public static final int PROCESS_UNIT_SIZE = 20; //处理单元大小 56 | 57 | public static final String CHARTSET_UTF8 = "utf-8"; 58 | public static final String CHARTSET_GBK = "gbk"; 59 | public static final String CHARTSET_GB2312 = "gb2312"; 60 | 61 | public static final int DOWNLOAD_THREAD = 15; //下载线程数 62 | 63 | public static String PATH_DOWNLOAD = ""; 64 | public static String PATH_APP = ""; 65 | public static boolean IS_UPDATE = false; 66 | public static boolean IS_DOWNLOAD_RAW = false; 67 | 68 | public static final String IMAGE_TYPE = "gif|jpg|png"; 69 | 70 | public static final Integer AUTO_DOWNLOAD_FAIL_FILE = 10; //自动下载错误文件次数 71 | 72 | public static String HTML_TEMPLATE_PAGE = ""; 73 | public static String HTML_TEMPLATE_IMAGE = ""; 74 | 75 | public static final String HTML_TAG_IMAGES = "${images}"; 76 | public static final String HTML_TAG_IMAGES_TOTAL = "${imagesTotal}"; 77 | public static final String HTML_TAG_NAME = "${name}"; 78 | public static final String HTML_TAG_URL = "${url}"; 79 | public static final String HTML_TAG_TITLE = "${title}"; 80 | public static final String HTML_TAG_ALBUM_DESC = "${albumDesc}"; 81 | public static final String HTML_TAG_DOWNLOAD_TIME = "${downloadTime}"; 82 | public static final String HTML_TAG_CHARSET = "GBK"; 83 | 84 | 85 | public static final String HTML_TAG_OWNER = "${owner}"; 86 | public static final String HTML_TAG_DESC = "${desc}"; 87 | public static final String HTML_TAG_COMMENT_URL = "${commentURL}"; 88 | public static final String HTML_TAG_NUMBER = "${num}"; 89 | public static final String HTML_TAG_IMAGE = "${image}"; 90 | public static final String HTML_TAG_RAW = "${raw}"; 91 | 92 | 93 | /*大图类型 - 1大图,2大小相同,高质量未压缩图*/ 94 | public static final Integer RAW_TYPE_LARGE = 1; 95 | public static final Integer RAW_TYPE_UNCOMPRESSED = 2; 96 | 97 | static{ 98 | //JAR包路径 99 | String jarPath; 100 | try { 101 | jarPath = URLDecoder.decode(Common.class.getProtectionDomain().getCodeSource().getLocation().getPath(),"utf-8"); 102 | //配置文件路径 103 | PATH_DOWNLOAD = jarPath.substring(1, jarPath.lastIndexOf("/")); 104 | //设置默认下载路径为程序所在目录 105 | PATH_DOWNLOAD = jarPath; 106 | if (CommonUtils.isWindows()) { 107 | //如果是Windows系统,若路径首字符为/,去除 108 | if(PATH_DOWNLOAD.startsWith("/")) { 109 | PATH_DOWNLOAD = jarPath.substring(1, jarPath.lastIndexOf("/")); 110 | } 111 | }else { 112 | PATH_DOWNLOAD = jarPath.substring(0, jarPath.lastIndexOf("/")); 113 | } 114 | // //程序所在目录 115 | PATH_APP = PATH_DOWNLOAD; 116 | } catch (UnsupportedEncodingException e) { 117 | e.printStackTrace(); 118 | } 119 | 120 | //加载HTML页面模版 121 | StringBuffer sb = new StringBuffer(); 122 | InputStream inputStream = Common.class.getResourceAsStream("/cn/blackgray/douban/album/download/resources/html/Template.html"); 123 | BufferedReader bw; 124 | try { 125 | bw = new BufferedReader(new InputStreamReader(inputStream,"UTF-8")); 126 | String str; 127 | try { 128 | while ((str = bw.readLine()) != null) { 129 | sb.append(str); 130 | } 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | HTML_TEMPLATE_PAGE = sb.toString(); 135 | 136 | //初始化HTML图片单元模版 137 | HTML_TEMPLATE_IMAGE = "
    ${owner}${desc}
    ${num}
    "; 138 | 139 | } catch (UnsupportedEncodingException e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | 144 | /** 145 | * 通过系统默认浏览器打开页面 146 | * @param url 147 | * @param frame 148 | */ 149 | public static void openURLWithBrowse(String url, JFrame frame){ 150 | //判断当前系统是否支持Java AWT Desktop扩展 151 | if(Desktop.isDesktopSupported()){ 152 | try { 153 | //创建一个URI实例 154 | URI uri = URI.create(url); 155 | //获取当前系统桌面扩展 156 | Desktop desktop = Desktop.getDesktop(); 157 | //判断系统桌面是否支持要执行的功能 158 | if(desktop.isSupported(Desktop.Action.BROWSE)){ 159 | //获取系统默认浏览器打开链接 160 | desktop.browse(uri); 161 | } 162 | } catch(java.lang.NullPointerException e){ 163 | e.printStackTrace(); 164 | } catch (java.io.IOException e) { 165 | e.printStackTrace(); 166 | String msg = "无法获取系统默认浏览器,地址:" + url; 167 | JOptionPane.showMessageDialog(frame, msg, "555~",JOptionPane.ERROR_MESSAGE); 168 | } 169 | }else{ 170 | String msg = "当前JDK版本过低,无法执行打开操作,地址:" + url; 171 | JOptionPane.showMessageDialog(frame, msg, "555~",JOptionPane.ERROR_MESSAGE); 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/HTMLUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Map.Entry; 6 | 7 | /** 8 | * HTML字符转义工具类 9 | * @author BlackGray 10 | */ 11 | public class HTMLUtils { 12 | 13 | private static Map htmlSignMap = new HashMap(); //HTML特殊字符集合(HTML-TEXT) 14 | private static Map htmlSignMapReversed = new HashMap(); //HTML特殊字符集合(TEXT-HTML) 15 | 16 | static{ 17 | //初始化符号集合 18 | htmlSignMap.put("Α","Α"); 19 | htmlSignMap.put("Β", "Β"); 20 | htmlSignMap.put("Γ", "Γ"); 21 | htmlSignMap.put("Δ", "Δ"); 22 | htmlSignMap.put("Ε", "Ε"); 23 | htmlSignMap.put("Ζ", "Ζ"); 24 | htmlSignMap.put("Η", "Η"); 25 | htmlSignMap.put("Θ", "Θ"); 26 | htmlSignMap.put("Ι", "Ι"); 27 | htmlSignMap.put("Κ", "Κ"); 28 | htmlSignMap.put("Λ", "Λ"); 29 | htmlSignMap.put("Μ", "Μ"); 30 | htmlSignMap.put("Ν", "Ν"); 31 | htmlSignMap.put("Ξ", "Ξ"); 32 | htmlSignMap.put("Ο", "Ο"); 33 | htmlSignMap.put("Ρ", "Ρ"); 34 | htmlSignMap.put("Σ", "Σ"); 35 | htmlSignMap.put("Τ", "Τ"); 36 | htmlSignMap.put("Υ", "Υ"); 37 | htmlSignMap.put("Φ", "Φ"); 38 | htmlSignMap.put("Χ", "Χ"); 39 | htmlSignMap.put("Ψ", "Ψ"); 40 | htmlSignMap.put("Ω", "Ω"); 41 | htmlSignMap.put("α", "α"); 42 | htmlSignMap.put("β", "β"); 43 | htmlSignMap.put("γ", "γ"); 44 | htmlSignMap.put("δ", "δ"); 45 | htmlSignMap.put("ε", "ε"); 46 | htmlSignMap.put("ζ", "ζ"); 47 | htmlSignMap.put("η", "η"); 48 | htmlSignMap.put("θ", "θ"); 49 | htmlSignMap.put("ι", "ι"); 50 | htmlSignMap.put("κ", "κ"); 51 | htmlSignMap.put("λ", "λ"); 52 | htmlSignMap.put("μ", "μ"); 53 | htmlSignMap.put("ν", "ν"); 54 | htmlSignMap.put("ξ", "ξ"); 55 | htmlSignMap.put("ο", "ο"); 56 | htmlSignMap.put("ρ", "ρ"); 57 | htmlSignMap.put("ς", "ς"); 58 | htmlSignMap.put("σ", "σ"); 59 | htmlSignMap.put("τ", "τ"); 60 | htmlSignMap.put("υ", "υ"); 61 | htmlSignMap.put("χ", "φ"); 62 | htmlSignMap.put("ψ", "ψ"); 63 | htmlSignMap.put("ω", "ω"); 64 | htmlSignMap.put("ϑ", "ϑ"); 65 | htmlSignMap.put("ϒ", "ϒ"); 66 | htmlSignMap.put("ϖ", "ϖ"); 67 | htmlSignMap.put("•", "•"); 68 | htmlSignMap.put("…", "…"); 69 | htmlSignMap.put("′", "′"); 70 | htmlSignMap.put("″", "″"); 71 | htmlSignMap.put("‾", "‾"); 72 | htmlSignMap.put("⁄", "⁄"); 73 | htmlSignMap.put("℘", "℘"); 74 | htmlSignMap.put("ℑ", "ℑ"); 75 | htmlSignMap.put("ℜ", "ℜ"); 76 | htmlSignMap.put("™", "™"); 77 | htmlSignMap.put("ℵ", "ℵ"); 78 | htmlSignMap.put("←", "←"); 79 | htmlSignMap.put("↑", "↑"); 80 | htmlSignMap.put("→", "→"); 81 | htmlSignMap.put("↓", "↓"); 82 | htmlSignMap.put("↔", "↔"); 83 | htmlSignMap.put("↵", "↵"); 84 | htmlSignMap.put("⇐", "⇐"); 85 | htmlSignMap.put("⇑", "⇑"); 86 | htmlSignMap.put("⇒", "⇒"); 87 | htmlSignMap.put("⇓", "⇓"); 88 | htmlSignMap.put("⇔", "⇔"); 89 | htmlSignMap.put("∀", "∀"); 90 | htmlSignMap.put("∂", "∂"); 91 | htmlSignMap.put("∃", "∃"); 92 | htmlSignMap.put("∅", "∅"); 93 | htmlSignMap.put("∇", "∇"); 94 | htmlSignMap.put("∈", "∈"); 95 | htmlSignMap.put("∉", "∉"); 96 | htmlSignMap.put("∋", "∋"); 97 | htmlSignMap.put("∏", "∏"); 98 | htmlSignMap.put("∑", "∑"); 99 | htmlSignMap.put("−", "−"); 100 | htmlSignMap.put("∗", "∗"); 101 | htmlSignMap.put("√", "√"); 102 | htmlSignMap.put("∝", "∝"); 103 | htmlSignMap.put("∞", "∞"); 104 | htmlSignMap.put("∠", "∠"); 105 | htmlSignMap.put("∧", "∧"); 106 | htmlSignMap.put("∨", "∨"); 107 | htmlSignMap.put("∩", "∩"); 108 | htmlSignMap.put("∪", "∪"); 109 | htmlSignMap.put("∫", "∫"); 110 | htmlSignMap.put("∴", "∴"); 111 | htmlSignMap.put("∼", "∼"); 112 | htmlSignMap.put("≅", "≅"); 113 | htmlSignMap.put("≈", "≈"); 114 | htmlSignMap.put("≠", "≠"); 115 | htmlSignMap.put("≡", "≡"); 116 | htmlSignMap.put("≤", "≤"); 117 | htmlSignMap.put("≥", "≥"); 118 | htmlSignMap.put("⊂", "⊂"); 119 | htmlSignMap.put("⊃", "⊃"); 120 | htmlSignMap.put("⊄", "⊄"); 121 | htmlSignMap.put("⊆", "⊆"); 122 | htmlSignMap.put("⊇", "⊇"); 123 | htmlSignMap.put("⊕", "⊕"); 124 | htmlSignMap.put("⊗", "⊗"); 125 | htmlSignMap.put("⊥", "⊥"); 126 | htmlSignMap.put("⋅", "⋅"); 127 | htmlSignMap.put("⌈", "⌈"); 128 | htmlSignMap.put("⌉", "⌉"); 129 | htmlSignMap.put("⌊", "⌊"); 130 | htmlSignMap.put("⌋", "⌋"); 131 | htmlSignMap.put("◊", "◊"); 132 | htmlSignMap.put("♠", "♠"); 133 | htmlSignMap.put("♣", "♣"); 134 | htmlSignMap.put("♥", "♥"); 135 | htmlSignMap.put("♦", "♦"); 136 | htmlSignMap.put(" ", " "); 137 | htmlSignMap.put("¡", "¡"); 138 | htmlSignMap.put("¢", "¢"); 139 | htmlSignMap.put("£", "£"); 140 | htmlSignMap.put("¤", "¤"); 141 | htmlSignMap.put("¥", "¥"); 142 | htmlSignMap.put("¦", "¦"); 143 | htmlSignMap.put("§", "§"); 144 | htmlSignMap.put("¨", "¨"); 145 | htmlSignMap.put("©", "©"); 146 | htmlSignMap.put("ª", "ª"); 147 | 148 | htmlSignMap.put("«", "«"); 149 | htmlSignMap.put("¬", "¬"); 150 | htmlSignMap.put("®", "®"); 151 | htmlSignMap.put("µ", "µ"); 152 | htmlSignMap.put("¯", "¯"); 153 | htmlSignMap.put("°", "°"); 154 | htmlSignMap.put("±", "±"); 155 | htmlSignMap.put("²", "²"); 156 | htmlSignMap.put("³", "³"); 157 | htmlSignMap.put("´", "´"); 158 | 159 | htmlSignMap.put("&", "&"); 160 | htmlSignMap.put("'", "'"); 161 | htmlSignMap.put(""", "\""); 162 | 163 | for (Entry entry : htmlSignMap.entrySet()) { 164 | htmlSignMapReversed.put(entry.getValue(), entry.getKey()); 165 | } 166 | htmlSignMapReversed.remove("&"); 167 | } 168 | 169 | /** 170 | * 特殊字符转换为HTML使用字符 171 | * @param str 172 | * @return 173 | */ 174 | public static String textToHTML(String str){ 175 | if (str.contains("&")) { 176 | str.replace("&", "&"); 177 | }; 178 | for (Entry entry : htmlSignMapReversed.entrySet()) { 179 | if (str.contains(entry.getKey())) { 180 | str = str.replaceAll(entry.getKey(), entry.getValue()); 181 | } 182 | } 183 | return str; 184 | } 185 | 186 | /** 187 | * 特殊字符转换为Json使用字符 188 | * @param str 189 | * @return 190 | */ 191 | public static String textToJson(String str){ 192 | str = htmlToText(str); 193 | str = str.replace("'", "\""); 194 | if (str.endsWith("\\")) { 195 | str += " "; 196 | } 197 | return str; 198 | } 199 | 200 | /** 201 | * HTML特殊字符转换为正常字符 202 | * @param str 203 | * @return 204 | */ 205 | public static String htmlToText(String str){ 206 | for (Entry element : htmlSignMap.entrySet()) { 207 | if (str.contains(element.getKey())) { 208 | str = str.replace(element.getKey(), element.getValue()); 209 | }; 210 | } 211 | return str; 212 | } 213 | 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/model/Album.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import cn.blackgray.douban.album.download.service.download.DownloadProcessing; 8 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 9 | import cn.blackgray.douban.album.download.service.handler.PageAnalyzer; 10 | 11 | /** 12 | * 相册MODEL 13 | * @author BlackGray 14 | */ 15 | public class Album { 16 | 17 | //1.※图片 - 文件名(相对路径获取,不需要路径) 18 | //2.※备注 - 备注.txt 19 | //3.※相册名称 - 文件夹名称 20 | //4.※相册地址 - 备注.txt第一行 21 | //5.文档生成时间 - 程序执行时间 22 | 23 | // public static final int TYPE_NOMAL = 1; 24 | // public static final int TYPE_DOUBAN_ALBUM = 2; 25 | // public static final int TYPE_DOUBAN_ONLINE = 3; 26 | // public static final int TYPE_DOUBAN_SITE = 4; 27 | // public static final int TYPE_DOUBAN_CELEBRITY = 5; 28 | 29 | private String name; //相册名称 30 | private String url; //相册地址 31 | private Date date; //日期 32 | private String path; //储存路径 33 | private String charset; //字符集 34 | private String desc; //相册描述 35 | // private int type = TYPE_NOMAL; //相册类型 36 | 37 | private boolean update = false; //是否为更新 38 | 39 | private AlbumHandler albumHandler; //相册处理器 40 | 41 | private boolean isVisibleToSelf = false; //是否仅对自己可见 42 | private boolean isVisibleToFriend = false; //是否仅对朋友可见 43 | private boolean isPrivateAlbum = false; //是否私密相册 44 | private long privatePhotoURLAnalyzeTotal = 0; //私密相册照片URL分析获取总数,用于计数、界面提醒 45 | 46 | 47 | //照片集合 48 | private List photosList = new ArrayList(); 49 | //页面集合 - 每个页面下多张照片 50 | private List pageURLLsit = new ArrayList(); 51 | 52 | public Album() { 53 | super(); 54 | } 55 | 56 | // public Album(String url, List photosList, String path, String charset) { 57 | // super(); 58 | // this.name = path.substring(path.lastIndexOf("/") + 1); 59 | // this.date = new Date(); 60 | // this.path = path; 61 | // this.charset = charset; 62 | // this.photosList = photosList; 63 | // if (url.endsWith("/")) { 64 | // url = url.substring(0,url.lastIndexOf("/")); 65 | // } 66 | // this.url = url; 67 | // } 68 | 69 | // /** 70 | // * 检查相册类型 71 | // * @param url 72 | // * @return 73 | // */ 74 | // public static int checkType(String url) { 75 | // //豆瓣相册下载 76 | // if (url.matches(DownloadService.REGEX_DOUBAN_ALBUM)) { 77 | // return TYPE_DOUBAN_ALBUM; 78 | // } 79 | // //豆瓣活动下载 80 | // if (url.matches(DownloadService.REGEX_DOUBAN_ONLINE_ALBUM)) { 81 | // return TYPE_DOUBAN_ONLINE; 82 | // } 83 | // //豆瓣小站 84 | // if (url.matches(DownloadService.REGEX_DOUBAN_SITE) || url.matches(DownloadService.REGEX_DOUBAN_SITE_NEW)) { 85 | // return TYPE_DOUBAN_SITE; 86 | // } 87 | // //普通下载 88 | // return TYPE_NOMAL; 89 | // } 90 | 91 | public AlbumHandler getAlbumHandler() { 92 | return albumHandler; 93 | } 94 | 95 | 96 | 97 | public void setAlbumHandler(AlbumHandler albumHandler) { 98 | this.albumHandler = albumHandler; 99 | } 100 | 101 | public boolean isUpdate() { 102 | return update; 103 | } 104 | 105 | public void setUpdate(boolean update) { 106 | this.update = update; 107 | } 108 | 109 | 110 | // public int getType() { 111 | // return type; 112 | // } 113 | // 114 | // public void setType(int type) { 115 | // this.type = type; 116 | // } 117 | 118 | public String getDesc() { 119 | return desc; 120 | } 121 | 122 | public void setDesc(String desc) { 123 | this.desc = desc; 124 | } 125 | 126 | public String getCharset() { 127 | return charset; 128 | } 129 | 130 | 131 | public void setCharset(String charset) { 132 | this.charset = charset; 133 | } 134 | 135 | 136 | public String getPath() { 137 | return path; 138 | } 139 | 140 | 141 | public void setPath(String path) { 142 | this.path = path; 143 | } 144 | 145 | 146 | public String getName() { 147 | return name; 148 | } 149 | 150 | public String getUrl() { 151 | return url; 152 | } 153 | 154 | public Date getDate() { 155 | return date; 156 | } 157 | 158 | public List getPhotosList() { 159 | return photosList; 160 | } 161 | 162 | public void setName(String name) { 163 | this.name = name; 164 | } 165 | 166 | public void setUrl(String url) { 167 | this.url = url; 168 | } 169 | 170 | public void setDate(Date date) { 171 | this.date = date; 172 | } 173 | 174 | public void setPhotosList(List photosList) { 175 | this.photosList = photosList; 176 | } 177 | 178 | public List getPageURLLsit() { 179 | return pageURLLsit; 180 | } 181 | 182 | public void setPageURLLsit(List pageURLLsit) { 183 | this.pageURLLsit = pageURLLsit; 184 | } 185 | 186 | public boolean getIsVisibleToSelf() { 187 | return isVisibleToSelf; 188 | } 189 | 190 | public void setIsVisibleToSelf(boolean isVisibleToSelf) { 191 | this.isVisibleToSelf = isVisibleToSelf; 192 | } 193 | 194 | 195 | public boolean getIsVisibleToFriend() { 196 | return isVisibleToFriend; 197 | } 198 | 199 | public void setIsVisibleToFriend(boolean isVisibleToFriend) { 200 | this.isVisibleToFriend = isVisibleToFriend; 201 | } 202 | 203 | public boolean getIsPrivateAlbum() { 204 | return isPrivateAlbum; 205 | } 206 | 207 | public void setIsPrivateAlbum(boolean isPrivateAlbum) { 208 | this.isPrivateAlbum = isPrivateAlbum; 209 | } 210 | 211 | 212 | public long getPrivatePhotoURLAnalyzeTotal() { 213 | return privatePhotoURLAnalyzeTotal; 214 | } 215 | 216 | public void setPrivatePhotoURLAnalyzeTotal(long privatePhotoURLAnalyzeTotal) { 217 | this.privatePhotoURLAnalyzeTotal = privatePhotoURLAnalyzeTotal; 218 | } 219 | 220 | /** 221 | * 下载 222 | */ 223 | public void download(){ 224 | //创建相册模型 225 | //根据模型下载相册 226 | DownloadProcessing.downloadAlbum(this); 227 | 228 | }; 229 | 230 | /** 231 | * 生成描述文档 232 | * @param imageAndDescMap 233 | */ 234 | public void createDescDoc(){ 235 | this.albumHandler.createDescDoc(this); 236 | } 237 | 238 | /** 239 | * 初始化相册 240 | */ 241 | public void init(){ 242 | 243 | // this.name = path.substring(path.lastIndexOf("/") + 1); 244 | // this.date = new Date(); 245 | // this.path = path; 246 | // this.charset = charset; 247 | // this.photosList = photosList; 248 | // if (url.endsWith("/")) { 249 | // url = url.substring(0,url.lastIndexOf("/")); 250 | // } 251 | // this.url = url; 252 | 253 | //【初始化相册信息】 254 | //URL 255 | this.setUrl(this.albumHandler.getAlbumURL()); 256 | //所有页面 257 | List pageURLLsit = PageAnalyzer.findPageURL(albumHandler); 258 | this.setPageURLLsit(pageURLLsit); 259 | //相册名称 260 | String name = PageAnalyzer.findAlbumName().trim(); 261 | this.setName(albumHandler.albumNameProcess(name)); 262 | //相册描述 263 | String desc = PageAnalyzer.findAlbumDesc(albumHandler); 264 | if (desc != null) { 265 | this.setDesc(desc.trim()); 266 | } 267 | //下载日期 268 | this.setDate(new Date()); 269 | 270 | } 271 | 272 | @Override 273 | public String toString() { 274 | return "Album [name=" + name + ", url=" + url + ", date=" + date 275 | + ", path=" + path + ", charset=" + charset 276 | + ", update=" + update + ", photosList=" + photosList 277 | + ", pageURLLsit=" + pageURLLsit + ", isVisibleToSelf=" + isVisibleToSelf 278 | + ", isVisibleToFriend=" + isVisibleToFriend + ", isPrivateAlbum=" + isPrivateAlbum 279 | + ", privatePhotoURLAnalyzeTotal=" + privatePhotoURLAnalyzeTotal 280 | + "]"; 281 | } 282 | 283 | } 284 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/download/DownloadFailManager.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.download; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileWriter; 6 | import java.io.IOException; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Map.Entry; 10 | import java.util.TreeMap; 11 | 12 | import javax.swing.JProgressBar; 13 | 14 | import cn.blackgray.douban.album.download.common.Common; 15 | import cn.blackgray.douban.album.download.common.Console; 16 | import cn.blackgray.douban.album.download.common.utils.FileUtils; 17 | import cn.blackgray.douban.album.download.ui.MainFrame; 18 | 19 | /** 20 | * 下载失败后处理类 21 | * @author BlackGray 22 | * @createTime 2022-10-23 23:32:56 23 | */ 24 | public class DownloadFailManager { 25 | 26 | private static Map failFileMap = new TreeMap(); //下载失败的文件集合Map 27 | 28 | 29 | /** 30 | * 添加下载失败图片 31 | * @return 32 | */ 33 | public static void add(String url, String path) { 34 | synchronized (failFileMap) { 35 | if (!failFileMap.containsKey(url)) { 36 | //删除下载不完整图片文件 37 | FileUtils.deleteImageFile(url, path); 38 | failFileMap.put(url,path); 39 | } 40 | } 41 | } 42 | 43 | 44 | /** 45 | * 批量添加下载失败图片 46 | * @param failMap 47 | */ 48 | public static void add(Map failMap) { 49 | synchronized (failFileMap) { 50 | failFileMap.putAll(failMap); 51 | } 52 | } 53 | 54 | 55 | /** 56 | * 获取下载失败文件集合 57 | * @return 58 | */ 59 | public static Map getFailFileMap() { 60 | return failFileMap; 61 | } 62 | 63 | /** 64 | * 获取下载失败的图片数量 65 | * @return 66 | */ 67 | public static int getFailSize() { 68 | return failFileMap.size(); 69 | } 70 | 71 | /** 72 | * 清除集合中所有记录 73 | * @return 74 | */ 75 | public static void clearAll() { 76 | failFileMap.clear(); 77 | } 78 | 79 | /** 80 | * 下载未成功下载的图片 81 | * @return true = 下载失败文件成功,false = 下载失败文件未成功(未完全下载)。 82 | */ 83 | public static boolean downloadFailFile(){ 84 | int num = 1; 85 | int size = DownloadFailManager.getFailSize(); 86 | //DownloadThread中下载异常时,将继续向公共Map中添加记录,为防止冲突,新建Map用于异常文件重新下载 87 | Map failFileMapForRetry = new TreeMap(); 88 | failFileMapForRetry.putAll(DownloadFailManager.getFailFileMap()); 89 | 90 | JProgressBar progressBar = MainFrame.getInstance().progressBar; 91 | progressBar.setMaximum(size); 92 | progressBar.setValue(0); 93 | Console.print("====================================="); 94 | Console.print("下载上次下载失败图片:" + size + "(张)"); 95 | Map newFailFileMap = new TreeMap(); 96 | for (Entry element : failFileMapForRetry.entrySet()) { 97 | String url = element.getKey(); 98 | String path = element.getValue(); 99 | try { 100 | //下载 101 | Console.print("下载图片(" + num + "/" + size + "):" + url); 102 | DownloadThread downloadThread = new DownloadThread(); 103 | int state = downloadThread.downloadImage(url, path); 104 | if (state == Common.IMAGE_DOWNLOAD_STATUS_DOWNLOAD_FAIL) { 105 | Console.print("失败重下 - 图片下载异常,已下载文件小于网络资源大小,等待再次重试:" + url); 106 | //加入下载异常集合,待重试 107 | FileUtils.deleteImageFile(url, path); 108 | newFailFileMap.put(url, path); 109 | } 110 | } catch (IOException e) { 111 | Console.print("图片下载失败:" + url); 112 | FileUtils.deleteImageFile(url, path); 113 | newFailFileMap.put(url, path); 114 | } 115 | progressBar.setValue(num); 116 | num++; 117 | } 118 | DownloadFailManager.clearAll(); 119 | if (newFailFileMap.size() > 0) { 120 | Console.print("【FINISH】成功:" + (size - newFailFileMap.size()) + ",失败" + newFailFileMap.size()); 121 | //批量添加下载失败图片 122 | DownloadFailManager.add(newFailFileMap); 123 | return false; 124 | } else { 125 | Console.print("【FINISH】成功:" + size + ",失败" + 0); 126 | return true; 127 | } 128 | } 129 | 130 | 131 | /** 132 | * 生成下载失败的图片记录文档 - 多个相册 133 | * 134 | * 图片下载失败、且重试后仍有失败,点击取消重试后将调用此方法生成记录文档 135 | * 用户可使用其他下载软件下载图片,或用于知晓下载失败内容 136 | * 137 | * 相册更新时将自动重新下载缺失图片,因此不再单独实现记录文档识别与重新下载功能 138 | * 139 | * @param albumPathList 相册目录 140 | */ 141 | public static void createAlbumFailFileDoc(List albumPathList, Map failFileMap) { 142 | 143 | Console.print("【正在生成下载失败图片记录文件(下载失败图片记录.txt),请稍等...】"); 144 | 145 | //按相册对下载异常文件做分组Map> 146 | Map> failFileMapGroupByAlbumPath = new TreeMap>(); 147 | for (String albumPath : albumPathList) { 148 | for (Entry entry : failFileMap.entrySet()) { 149 | String imageUrl = entry.getKey(); 150 | String imagePath = entry.getValue(); 151 | if(imagePath.startsWith(albumPath)) { 152 | Map tempMap = null; 153 | if(failFileMapGroupByAlbumPath.containsKey(albumPath)) { 154 | tempMap = failFileMapGroupByAlbumPath.get(albumPath); 155 | }else { 156 | tempMap = new TreeMap(); 157 | } 158 | tempMap.put(imageUrl, imagePath); 159 | failFileMapGroupByAlbumPath.put(albumPath, tempMap); 160 | } 161 | } 162 | 163 | } 164 | 165 | //生成单个相册文档 166 | for (String albumPath : albumPathList) { 167 | createAlbumFailFileDoc(albumPath, failFileMapGroupByAlbumPath.get(albumPath)); 168 | } 169 | 170 | Console.print("【下载失败图片记录文件生成成功】"); 171 | 172 | } 173 | 174 | 175 | /** 176 | * 生成下载失败的图片记录文档 - 单个相册 177 | */ 178 | private static void createAlbumFailFileDoc(String albumPath, Map failFileMap) { 179 | if(failFileMap != null && failFileMap.size() > 0) { 180 | try { 181 | String path = albumPath + File.separator + Common.DEFAULT_FAIL_FILE_DOC_NAME; 182 | Console.print("开始生成文档:" + path); 183 | BufferedWriter bw = new BufferedWriter(new FileWriter(path)); 184 | //输出记录 185 | bw.write("------------------------------------------------------------------"); 186 | bw.newLine(); 187 | bw.write("下载失败图片URL地址及保存目录"); 188 | bw.newLine(); 189 | bw.write("------------------------------------------------------------------"); 190 | bw.newLine(); 191 | for (Entry entry : failFileMap.entrySet()) { 192 | String imageUrl = entry.getKey(); 193 | String imagePath = entry.getValue(); 194 | bw.write(imageUrl + " → " + imagePath.replace(albumPath, Common.DEFAULT_ALBUM_ROOT_PATH_STR)); 195 | bw.newLine(); 196 | } 197 | //输出纯地址 198 | bw.newLine(); 199 | bw.newLine(); 200 | bw.newLine(); 201 | bw.write("------------------------------------------------------------------"); 202 | bw.newLine(); 203 | bw.write("下载失败图片URL地址,若希望使用其他工具下载缺失图片,可复制以下地址"); 204 | bw.newLine(); 205 | bw.write("------------------------------------------------------------------"); 206 | bw.newLine(); 207 | for (Entry entry : failFileMap.entrySet()) { 208 | String imageUrl = entry.getKey(); 209 | bw.write(imageUrl); 210 | bw.newLine(); 211 | } 212 | 213 | bw.flush(); 214 | bw.close(); 215 | Console.print("文档生成成功"); 216 | } catch (IOException e) { 217 | Console.print("文档生成失败"); 218 | e.printStackTrace(); 219 | } 220 | } 221 | } 222 | 223 | /** 224 | * 根据相册路径删除“下载失败图片记录.txt”文档,更新相册时调用 225 | */ 226 | public static void deleteAlbumFailFileDoc(String albumPath) { 227 | File doc = new File(albumPath + File.separator + Common.DEFAULT_FAIL_FILE_DOC_NAME); 228 | if (doc.exists()) { 229 | Console.print("更新相册,删除早期生成的“下载失败图片记录.txt”文档"); 230 | doc.delete(); 231 | } 232 | } 233 | 234 | 235 | 236 | } 237 | 238 | 239 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/download/DownloadThread.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.download; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.File; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.net.HttpURLConnection; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | import java.util.List; 14 | 15 | import javax.swing.JProgressBar; 16 | 17 | import cn.blackgray.douban.album.download.common.Common; 18 | import cn.blackgray.douban.album.download.common.Console; 19 | import cn.blackgray.douban.album.download.common.utils.LoginUtils; 20 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 21 | import cn.blackgray.douban.album.download.model.Album; 22 | 23 | /** 24 | * 下载线程 25 | * @author BlackGray 26 | */ 27 | public class DownloadThread extends Thread{ 28 | 29 | private Album album; 30 | private List imageURLList; 31 | private String path; //保存路径 32 | private String url; //正在处理中的图片url 33 | private int imageCount; 34 | private JProgressBar mainProgressBar; 35 | 36 | private BufferedInputStream inputStream; 37 | private BufferedOutputStream outputStream; 38 | 39 | public DownloadThread() { 40 | super(); 41 | } 42 | 43 | public DownloadThread(Album album, String threadName, List imageURLList,int imageCount , String path,JProgressBar mainProgressBar) { 44 | this.album = album; 45 | this.imageURLList = imageURLList; 46 | this.path = path.trim(); 47 | this.imageCount = imageCount; 48 | this.mainProgressBar = mainProgressBar; 49 | this.setName(threadName); 50 | } 51 | 52 | public void closeStream() throws IOException { 53 | if (inputStream != null) { 54 | inputStream.close(); 55 | inputStream = null; 56 | } 57 | if (outputStream != null) { 58 | outputStream.close(); 59 | outputStream = null; 60 | } 61 | } 62 | 63 | public String getPath() { 64 | return path; 65 | } 66 | 67 | public String getUrl() { 68 | return url; 69 | } 70 | 71 | public BufferedInputStream getInputStream() { 72 | return inputStream; 73 | } 74 | 75 | public BufferedOutputStream getOutputStream() { 76 | return outputStream; 77 | } 78 | 79 | public Album getAlbum() { 80 | return album; 81 | } 82 | 83 | @Override 84 | public void run() { 85 | while (true) { 86 | int listSize; 87 | synchronized (imageURLList) { 88 | //判断是否有图片需要下载 89 | if (imageURLList.size() != 0) { 90 | url = imageURLList.get(0); 91 | listSize = imageURLList.size() - 1; 92 | imageURLList.remove(url); 93 | }else{ 94 | break; 95 | } 96 | } 97 | try { 98 | int state = downloadImage(url, path, album.getIsPrivateAlbum(), LoginUtils.IS_LOGIN); 99 | if (state == Common.IMAGE_DOWNLOAD_STATUS_EXISTS) { 100 | Console.print(this.getName() + " - 图片已存在(" + (imageCount - listSize) + "/" + imageCount + "):" + url); 101 | } 102 | if(state == Common.IMAGE_DOWNLOAD_STATUS_FINISH){ 103 | Console.print(this.getName() + " - 图片下载完成(" + (imageCount - listSize) + "/" + imageCount + "):" + url); 104 | } 105 | if(state == Common.IMAGE_DOWNLOAD_STATUS_URL_NOT_EXISTS){ 106 | Console.print(this.getName() + " - 图片不存在(" + (imageCount - listSize) + "/" + imageCount + "):" + url); 107 | } 108 | if (state == Common.IMAGE_DOWNLOAD_STATUS_DOWNLOAD_FAIL) { 109 | Console.print(this.getName() + " - 图片下载异常,已下载文件小于网络资源大小(" + (imageCount - listSize) + "/" + imageCount + "):" + url); 110 | //加入下载异常集合,待重试 111 | DownloadFailManager.add(url, path); 112 | } 113 | DownloadManager.updateCount += 1; 114 | } catch (Exception e) { 115 | if (!e.getClass().equals(FileNotFoundException.class)) { 116 | Console.print("图片下载失败:" + url + " - " + e.getMessage()); 117 | //删除下载失败图片,并将图片信息加入失败文件集合 118 | DownloadFailManager.add(url, path); 119 | e.printStackTrace(); 120 | }else{ 121 | Console.print("图片不存在:" + url + " - " + e.getMessage()); 122 | e.printStackTrace(); 123 | } 124 | }finally{ 125 | synchronized (mainProgressBar) { 126 | mainProgressBar.setValue(mainProgressBar.getValue() + 1); 127 | } 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * 下载图片 - 是否私有相册、是否登陆默认值为false 134 | * @param url 135 | * @param filePath 136 | * @return 137 | * @throws MalformedURLException 138 | * @throws FileNotFoundException 139 | * @throws IOException 140 | */ 141 | public int downloadImage(String url, String filePath) throws MalformedURLException, FileNotFoundException, IOException{ 142 | return downloadImage(url, filePath, false, false); 143 | } 144 | 145 | 146 | /** 147 | * 下载图片 148 | * @param url 149 | * @param filePath 150 | * @throws MalformedURLException 151 | * @throws FileNotFoundException 152 | * @throws IOException 153 | */ 154 | public int downloadImage(String url, String filePath, boolean isPrivateAlbum, boolean isLogin) throws MalformedURLException, FileNotFoundException, IOException{ 155 | 156 | //私密相册,且已登录,生成请求所需的Cookie信息 157 | String cookieStr = null; 158 | if (isPrivateAlbum && isLogin) { 159 | cookieStr = LoginUtils.getCookiesStr(LoginUtils.CHROME_DRIVER); 160 | } 161 | 162 | //相册下载处理 163 | String fileName = url.substring(url.lastIndexOf('/')); 164 | File file = new File(filePath + File.separatorChar + fileName); 165 | if(file.exists()) { 166 | //如果本地文件已存在,不执行下载,返回已存在标识 167 | return Common.IMAGE_DOWNLOAD_STATUS_EXISTS; 168 | }else { 169 | //如果本地图不存在,执行下载 170 | //判断网络资源是否存在 171 | if (URLUtils.exists(url, cookieStr)) { 172 | //执行下载 173 | //配置网络资源 174 | URL image = new URL(url); 175 | HttpURLConnection conn = (HttpURLConnection) image.openConnection(); 176 | 177 | //2016-03-16 如不加referer信息,下载影人相册时,大图监测返回403异常 178 | conn.setRequestProperty("referer", "https://www.douban.com/"); 179 | //2024-06-23 新增参数,不加会报403 180 | conn.setRequestProperty("User-Agent", URLUtils.randomUserAgentStr()); 181 | //2024-10-02 设置通过selenium模拟登陆后获取的Cookie值 182 | if (cookieStr != null) { 183 | conn.setRequestProperty("Cookie", cookieStr); 184 | } 185 | 186 | conn.setConnectTimeout(10*1000); //设置连接超时 187 | conn.setReadTimeout(10*1000); //设置读取超时 188 | conn.setDoInput(true); //默认为true 189 | conn.connect(); 190 | //获取网络资源文件大小 191 | long contentLength = conn.getContentLengthLong(); 192 | 193 | InputStream in = conn.getInputStream(); 194 | inputStream = new BufferedInputStream(in); 195 | outputStream = new BufferedOutputStream(new FileOutputStream(file)); 196 | byte[] data = new byte[2048]; 197 | int n = 0; 198 | while ((n = inputStream.read(data)) != -1) { 199 | outputStream.write(data,0,n); 200 | } 201 | outputStream.flush(); 202 | in.close(); 203 | inputStream.close(); 204 | outputStream.close(); 205 | conn.disconnect(); 206 | 207 | //验证文件大小 208 | if(file.length() < contentLength) { 209 | // 图片下载异常,已下载文件小于网络资源大小 210 | return Common.IMAGE_DOWNLOAD_STATUS_DOWNLOAD_FAIL; 211 | }else { 212 | // 下载完成 213 | return Common.IMAGE_DOWNLOAD_STATUS_FINISH; 214 | } 215 | }else{ 216 | return Common.IMAGE_DOWNLOAD_STATUS_URL_NOT_EXISTS; 217 | } 218 | } 219 | 220 | } 221 | 222 | public static void main(String[] args) throws MalformedURLException, FileNotFoundException, IOException { 223 | //免登陆图片下载测试 224 | // WebDriver driver = LoginUtils.onlyInit(); 225 | // LoginUtils.CHROME_DRIVER = driver; 226 | // DownloadThread thread = new DownloadThread(); 227 | // 228 | // String url = "https://img9.doubanio.com/view/photo/l/public/p881707594.jpg"; 229 | // thread.downloadImage(url, "/Users/blackgray/Downloads/selenium/", true, true); 230 | 231 | 232 | //登陆后图片下载测试 233 | LoginUtils.login(); 234 | DownloadThread thread = new DownloadThread(); 235 | 236 | String url = "https://simg.douban.com/view/photo/m/tKu8-lZS3kZdc5E9Fgcrww/2745512/x2196275895.jpg"; 237 | thread.downloadImage(url, "/Users/blackgray/Downloads/selenium/", true, true); 238 | } 239 | 240 | } -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/OnlineAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.common.Console; 17 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 18 | import cn.blackgray.douban.album.download.model.Album; 19 | import cn.blackgray.douban.album.download.model.BGImage; 20 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 21 | import cn.blackgray.douban.album.download.service.image.ImageListComparator; 22 | 23 | /** 24 | * 活动相册处理器 25 | */ 26 | public class OnlineAlbumHandler extends AlbumHandler { 27 | 28 | public static final int PAGE_SIZE_IMAGES_ONLINE = 90; //活动照片分页大小(一页30张图) 29 | public static final String PAGE_TAG = "start"; 30 | public static final String IMAGE_NAME_REGEX = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 31 | public static final String ALBUM_URL_REGEX = "(http|https)://www.douban.com/online/\\d+/album/\\d+/"; 32 | 33 | @Override 34 | public String getURLRegex() { 35 | return ALBUM_URL_REGEX; 36 | } 37 | 38 | @Override 39 | public String getPageRegex() { 40 | return super.getAlbumURL() + "\\?\\w+=\\d+"; 41 | } 42 | 43 | @Override 44 | public boolean removeURLParameter() { 45 | return true; 46 | } 47 | 48 | @Override 49 | public Integer getPageSize() { 50 | return PAGE_SIZE_IMAGES_ONLINE; 51 | } 52 | 53 | @Override 54 | public String getPageTag() { 55 | return PAGE_TAG; 56 | } 57 | 58 | @Override 59 | public String getImageNameRegex() { 60 | return IMAGE_NAME_REGEX; 61 | } 62 | 63 | @Override 64 | public String getRawURL(String imageURL) { 65 | return null; 66 | } 67 | 68 | @Override 69 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 70 | 71 | //
    72 | // 73 | // 74 | // 75 | //
    76 | // 我的近照 77 | //
    78 | // 来自 similer猪猪 79 | // 80 | //
    81 | //
    82 | String imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 83 | String onlineId = pageURL.substring(pageURL.indexOf("online/") + 7, pageURL.indexOf("/album")); 84 | 85 | //【描述】 86 | String startIndexStr = "", ownerStartIndex)); 99 | String ownerURL = "@@@@"; 100 | try { 101 | ownerURL = ownerA.substring(0,ownerA.indexOf("/\">")); 102 | } catch (Exception e) { 103 | System.out.println("====================="); 104 | System.out.println(ownerA); 105 | System.out.println(ownerA.indexOf("")); 107 | System.out.println("====================="); 108 | e.printStackTrace(); 109 | } 110 | String ownerName = ownerA.substring(ownerA.indexOf(">") + 1); 111 | 112 | //【照片评论数】 113 | String commentTatolStartIndexStr = ""; 114 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 115 | Integer commentTatol = null; 116 | if (commentTatolStartIndex != -1) { 117 | //“3回应” 118 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 119 | commentTatol = Integer.valueOf(s.replace("回应", "")); 120 | } 121 | 122 | 123 | //【照片】 124 | imageURL = imageURL.replace("thumb", "photo").trim(); //thumb——>photo:缩略图——>大图 125 | desc = desc.replace("\\t\\n","").trim(); 126 | if (!map.containsKey(imageURL)) { 127 | BGImage bgImage = new BGImage(desc, imageURL, commentTatol); 128 | bgImage.setOwnerName(ownerName); 129 | bgImage.setOwnerURL(ownerURL); 130 | map.put(imageURL, bgImage); 131 | } 132 | } 133 | 134 | @Override 135 | public boolean checkBGImage(BGImage bgImage) { 136 | return bgImage.getUrl().indexOf("albumicon") < 0; 137 | } 138 | 139 | @Override 140 | public void createDescDoc(Album album) { 141 | List imageList = album.getPhotosList(); 142 | Map map = new HashMap(); 143 | for (BGImage bgImage : imageList) { 144 | map.put(bgImage.getUrl(), bgImage); 145 | } 146 | List keyList = new ArrayList(map.keySet()); 147 | //排序 148 | Collections.sort(keyList,new ImageListComparator()); 149 | try { 150 | BufferedWriter bw = new BufferedWriter(new FileWriter(album.getPath() + "/" + Common.DEFAULT_DOC_NAME)); 151 | //输出相册地址 152 | bw.write(URLUtils.charset + " " + album.getUrl() + " " +album.getDate().getTime() + " -"); 153 | bw.newLine(); 154 | //输出照片地址和描述 155 | for (int i = 0; i < keyList.size(); i++) { 156 | BGImage bgImage = map.get(keyList.get(i)); 157 | Integer commentTotal = bgImage.getCommentTotal(); 158 | String commentTotalStr = commentTotal==null?"-":String.valueOf(commentTotal); 159 | bw.write((i + 1) + " " + keyList.get(i) + " " + commentTotalStr + " " + bgImage.getDesc()); 160 | bw.newLine(); 161 | //活动相册,输出用户名&主页地址 162 | bw.write(bgImage.getOwnerURL() + " " + bgImage.getOwnerName()); 163 | bw.newLine(); 164 | } 165 | bw.flush(); 166 | bw.close(); 167 | Console.print("生成描述文档:成功"); 168 | } catch (IOException e) { 169 | Console.print("生成描述文档:失败"); 170 | e.printStackTrace(); 171 | } 172 | } 173 | 174 | 175 | @Override 176 | public List getBGImageFromDescDoc(File descFile) throws IOException { 177 | List list = new ArrayList(); 178 | BufferedReader reader = new BufferedReader(new FileReader(descFile)); 179 | BGImage tempBGImage = null; 180 | int line = 0; 181 | String str; 182 | while ((str = reader.readLine()) != null) { 183 | if (line == 0) { 184 | line++; 185 | }else{ 186 | //0行为相册&页面信息,奇数行照片信息,偶数行用户信息 187 | if (line%2 == 1) { 188 | String[] info = str.split(" ",4); 189 | //info[0],info[1],info[2],info[3]分别为照片编号、原始URL地址、评论数、照片描述 190 | tempBGImage = new BGImage(info[0],info[1],info[3]); 191 | //评论数 192 | if (!info[2].equals("-")) { 193 | tempBGImage.setCommentTotal(Integer.valueOf(info[2])); 194 | } 195 | line++; 196 | }else{ 197 | String[] info = str.split(" ",2); 198 | //info[0],info[1]分别为用户首页URL、用户名 199 | BGImage bgImage = tempBGImage; 200 | bgImage.setOwnerURL(info[0]); 201 | bgImage.setOwnerName(info[1]); 202 | list.add(bgImage); 203 | line++; 204 | } 205 | } 206 | } 207 | reader.close(); 208 | return list; 209 | } 210 | 211 | @Override 212 | public String getCommentURL(Album album, BGImage image) { 213 | //评论地址 214 | // http://www.douban.com/online/11127307/album/72416214/ 215 | // http://www.douban.com/online/11127307/photo/1573338563/ 216 | return album.getUrl().substring(0, album.getUrl().indexOf("/album/")) + "/photo/" + image.getId(); 217 | } 218 | 219 | @Override 220 | public String getAlbumDesc(String source) { 221 | return null; 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/LoginUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | import java.io.File; 4 | import java.util.Set; 5 | 6 | import javax.swing.ImageIcon; 7 | import javax.swing.JLabel; 8 | 9 | import org.openqa.selenium.By; 10 | import org.openqa.selenium.Cookie; 11 | import org.openqa.selenium.OutputType; 12 | import org.openqa.selenium.WebDriver; 13 | import org.openqa.selenium.WebElement; 14 | import org.openqa.selenium.chrome.ChromeDriver; 15 | import org.openqa.selenium.chrome.ChromeOptions; 16 | 17 | import cn.blackgray.douban.album.download.common.Common; 18 | import cn.blackgray.douban.album.download.common.Console; 19 | import cn.blackgray.douban.album.download.ui.LoginQrFrame; 20 | import cn.blackgray.douban.album.download.ui.MainFrame; 21 | 22 | /** 23 | * 登录工具类 24 | * 25 | * @author BlackGray 26 | * @createTime 2024-10-02 03:12:27 27 | */ 28 | public class LoginUtils { 29 | 30 | public static WebDriver CHROME_DRIVER = null; 31 | //是否已登录,默认为false 32 | public static boolean IS_LOGIN = false; 33 | //豆瓣登录首页 34 | private static String DOUBAN_LOGIN_URL = "https://accounts.douban.com/passport/login"; 35 | //是否正在执行登陆中 36 | public static boolean IS_LOGGING_IN = false; 37 | //chromedirver下载地址 38 | private static String CHROME_DRIVER_DOWNLOAD_URL = "https://googlechromelabs.github.io/chrome-for-testing/"; 39 | 40 | 41 | 42 | /** 43 | * 初始化ChromeDriver 44 | */ 45 | public static WebDriver initChromeDriver() { 46 | // 获取 chromedirver 的存放位置 47 | // String chromedriverPath = "/Users/blackgray/Downloads/chromedriver-mac-arm64/chromedriver"; 48 | String chromedriverPath = Common.PATH_APP + File.separator + "chromedriver"; 49 | if (CommonUtils.isWindows()) { 50 | chromedriverPath = chromedriverPath + ".exe"; 51 | } 52 | 53 | //检查chromedriver文件是否存在 54 | File chromedriverFile = new File(chromedriverPath); 55 | if (chromedriverFile.exists()) { 56 | //若存在,执行初始化 57 | Console.print("检测chromedriver文件是否存在 - 存在"); 58 | 59 | Console.print("加载chromedriver - " + chromedriverPath); 60 | System.getProperties().setProperty("webdriver.chrome.driver", chromedriverPath); 61 | 62 | ChromeOptions chromeOptions = new ChromeOptions(); 63 | 64 | chromeOptions.addArguments("--no-sandbox"); // 不使用沙箱 65 | chromeOptions.addArguments("--disable-dev-shm-usage"); 66 | // chromeOptions.addArguments("blink-settings=imagesEnabled=false"); //不加载图片 67 | chromeOptions.addArguments("--disable-gpu"); // 禁用GPU 68 | chromeOptions.addArguments("--remote-allow-origins=*"); 69 | 70 | // 使用后台打开chrome的方式 71 | chromeOptions.addArguments("--headless"); 72 | return new ChromeDriver(chromeOptions); 73 | }else { 74 | //若不存在,提醒下载、打开下载地址 75 | Console.print("检测chromedriver文件是否存在 - 不存在。"); 76 | Common.openURLWithBrowse(CHROME_DRIVER_DOWNLOAD_URL, MainFrame.getInstance()); 77 | Console.print("已使用默认浏览器打开chromedriver下载界面。"); 78 | Console.print("请下载与自己Chrome大版本一致的chromedriver。"); 79 | Console.print("下载后拷贝文件至下载器相同目录下,再重新执行登陆操作。"); 80 | return null; 81 | } 82 | } 83 | 84 | /** 85 | * 登陆执行 86 | */ 87 | public static void login() { 88 | 89 | Console.print("正在准备登陆,请稍等..."); 90 | 91 | try { 92 | CHROME_DRIVER = initChromeDriver(); 93 | if (CHROME_DRIVER != null) { 94 | // 1.模拟打开登陆页面 95 | String url = DOUBAN_LOGIN_URL; 96 | Console.print("打开登陆页面,URL:" + url); 97 | CHROME_DRIVER.get(url); 98 | 99 | // 2.等5秒钟响应后再操作,不然内容可能还没有返回 100 | Console.print("睡眠3s,等待页面加载完整。"); 101 | Thread.sleep(3 * 1000l); 102 | Console.print("打页面标题:" + CHROME_DRIVER.getTitle()); 103 | // System.out.println("========================"); 104 | // System.out.println(driver.getPageSource()); 105 | // System.out.println("========================"); 106 | // webDriver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS); 107 | 108 | // 3.获取二维码登录切换按钮 109 | WebElement loginTypeTab = CHROME_DRIVER.findElement(By.xpath("/html/body/div[1]/div[2]/div[2]/div/div[1]/div/div[1]/a[1]")); 110 | // 点击TAB,切换至密码登录表单 111 | loginTypeTab.click(); 112 | Console.print("睡眠1s。"); 113 | Thread.sleep(1 * 1000l); 114 | // Console.print("输出页面源码,检查二维码是否已生成。"); 115 | // System.out.println(driver.getPageSource()); 116 | 117 | // //4.获取二维码 - 保存到本地 118 | // //存储截图文件的文件夹位置 119 | // String dir = "/Users/blackgray/Downloads/selenium/"; 120 | // //文件名 121 | // String imageFileName = "qr.png"; 122 | // //文件完整路径 123 | // String imageFileFullName = dir + imageFileName; 124 | // //本地文件转为文件类型 125 | // File imageFile = new File(imageFileFullName); 126 | // 127 | // WebElement qrParent = CHROME_DRIVER.findElement(By.className("account-qr-scan")); 128 | // WebElement qr = qrParent.findElements(By.xpath("./*")).get(0); 129 | // 130 | // //将元素对应的部分截图并转为文件类型 131 | // File eleScreenShotFile = qr.getScreenshotAs(OutputType.FILE); 132 | // //将截图文件复制到本地文件 133 | // FileUtils.copyFile(eleScreenShotFile, imageFile); 134 | 135 | // 4.获取二维码 136 | // 本地文件转为文件类型 137 | WebElement qrParent = CHROME_DRIVER.findElement(By.className("account-qr-scan")); 138 | WebElement qr = qrParent.findElements(By.xpath("./*")).get(0); 139 | // 将元素对应的部分截图并转为文件类型 - selenium截图默认使用了Java临时文件,程序关闭,文件将自动删除 140 | File eleScreenShotFile = qr.getScreenshotAs(OutputType.FILE); 141 | Console.print("获取登陆二维码 - " + eleScreenShotFile.getAbsolutePath()); 142 | 143 | // 5.显示二维码 144 | LoginQrFrame frame = new LoginQrFrame(); 145 | ImageIcon qrImage = new ImageIcon(eleScreenShotFile.getAbsolutePath()); 146 | JLabel lable = new JLabel(qrImage); 147 | lable = new JLabel(qrImage); 148 | frame.setLayout(null); 149 | frame.add(lable); 150 | lable.setBounds(0, 0, frame.getWidth(), frame.getHeight() - 40); 151 | 152 | // 显示界面 153 | MainFrame mainFrame = MainFrame.getInstance(); 154 | frame.setLocationRelativeTo(mainFrame); 155 | frame.setLocation(mainFrame.getLocation().x + 400, mainFrame.getLocation().y + 80); 156 | frame.setVisible(true); 157 | frame.revalidate(); 158 | frame.setResizable(false); 159 | 160 | Console.print("等待使用豆瓣APP扫描二维码登录。"); 161 | //检查是否登陆成功 162 | //设置登录中标识 163 | LoginUtils.IS_LOGGING_IN = true; 164 | new Thread(new Runnable() { 165 | @Override 166 | public void run() { 167 | //定时检查页面,确认是否登陆成功后跳转首页 168 | while (true) { 169 | if (LoginUtils.IS_LOGGING_IN) { 170 | try { 171 | Console.print("检查是否已登录 - 未登录。"); 172 | if (!LoginUtils.CHROME_DRIVER.getCurrentUrl().equals(DOUBAN_LOGIN_URL)) { 173 | // 是否已登录标识更新 174 | LoginUtils.IS_LOGIN = true; 175 | Console.print("登陆成功,可开始执行下载。"); 176 | //取消登录中标识 177 | LoginUtils.IS_LOGGING_IN = false; 178 | frame.setVisible(false); 179 | //若有录入相册地址,自动开始下载 180 | MainFrame mainFrame = MainFrame.getInstance(); 181 | if (mainFrame.albumTextArea.getText().length() > 0) { 182 | mainFrame.downloadBtn.doClick(); 183 | } 184 | break; 185 | } 186 | Thread.sleep(1 * 1000); 187 | } catch (InterruptedException e) { 188 | e.printStackTrace(); 189 | } 190 | }else { 191 | Console.print("取消登录。"); 192 | break; 193 | } 194 | } 195 | } 196 | }).run(); 197 | } 198 | } catch (Exception e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | 203 | /** 204 | * 获取Cookie字符串 205 | * @param cookies 206 | * @return 207 | */ 208 | public static String getCookiesStr(WebDriver driver) { 209 | Set cookies = driver.manage().getCookies(); 210 | String cookieStr = convertCookiesToString(cookies); 211 | // System.out.println("【CookiesStr】" + cookieStr); 212 | return cookieStr; 213 | } 214 | 215 | /** 216 | * Cookie集合转为字符串 217 | * @param cookies 218 | * @return 219 | */ 220 | private static String convertCookiesToString(Set cookies) { 221 | StringBuilder cookieBuilder = new StringBuilder(); 222 | for (Cookie cookie : cookies) { 223 | if (cookieBuilder.length() > 0) { 224 | cookieBuilder.append("; "); 225 | } 226 | cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue()); 227 | } 228 | return cookieBuilder.toString(); 229 | } 230 | 231 | } 232 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/common/utils/URLUtils.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.common.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.UnsupportedEncodingException; 7 | import java.net.HttpURLConnection; 8 | import java.net.MalformedURLException; 9 | import java.net.ProtocolException; 10 | import java.net.URL; 11 | import java.util.Random; 12 | 13 | import org.openqa.selenium.WebDriver; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.common.Console; 17 | 18 | 19 | /** 20 | * 网络资源相关工具类 21 | * @author BlackGray 22 | */ 23 | public class URLUtils { 24 | 25 | 26 | public static String charset = Common.CHARTSET_UTF8; 27 | 28 | /** 29 | * 获取页面源码 30 | * @param url 31 | * @return 32 | */ 33 | public static String readSource(String url){ 34 | 35 | StringBuffer sb = new StringBuffer(); 36 | 37 | //判断是否已登录 38 | if (LoginUtils.IS_LOGIN) { 39 | //若已登录使用CHROME_DRIVER获取源码 40 | WebDriver driver = LoginUtils.CHROME_DRIVER; 41 | driver.get(url); 42 | Console.print("睡眠2s,等待页面加载完整。"); 43 | try { 44 | Thread.sleep(2*1000l); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | 49 | // System.out.println("获取页面源码。"); 50 | String pageSource = driver.getPageSource(); 51 | 52 | // System.out.println("输出页面源码。"); 53 | // System.out.println(pageSource); 54 | 55 | sb.append(pageSource.toString()); 56 | 57 | }else { 58 | //若未登录,使用HttpURLConnection获取源码 59 | //获取页面源码 60 | try { 61 | //代理 62 | // SocketAddress add = new InetSocketAddress("203.66.187.246", 81); 63 | // Proxy p = new Proxy(Proxy.Type.HTTP , add); 64 | // HttpURLConnection connection = (HttpURLConnection) u.openConnection(p); 65 | // String headerKey = "Proxy-Authorization"; 66 | // String headerValue = "Basic " + Base64.encode(user+":"+password); 67 | // conn.setRequestProperty(headerKey, headerValue); 68 | 69 | URL u = new URL(url); 70 | HttpURLConnection connection = (HttpURLConnection) u.openConnection(); 71 | connection.setRequestProperty("User-Agent", randomUserAgentStr()); 72 | 73 | //connection.setRequestProperty("referer", "https://www.douban.com/"); 74 | 75 | //默认UTF-8读取 76 | BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),charset)); 77 | String str; 78 | while ((str = reader.readLine()) != null) { 79 | sb.append(str); 80 | } 81 | reader.close(); 82 | } catch (MalformedURLException e) { 83 | e.printStackTrace(); 84 | }catch (UnsupportedEncodingException e) { 85 | e.printStackTrace(); 86 | } catch (IOException e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | 92 | String result = sb.toString(); 93 | if (result.trim().length() == 0) { 94 | Console.print("源码获取失败:" + url); 95 | return result; 96 | }else{ 97 | String charsetCheck = charset; 98 | //判断字符集 99 | if (result.indexOf(Common.CHARTSET_GBK) != -1) { 100 | charsetCheck = Common.CHARTSET_GBK; 101 | }else if(result.indexOf(Common.CHARTSET_GB2312) != -1){ 102 | //GB2312 103 | charsetCheck = Common.CHARTSET_GB2312; 104 | }else{ 105 | //utf-8 106 | charsetCheck = Common.CHARTSET_UTF8; 107 | } 108 | if (!charsetCheck.equals(charset)) { 109 | Console.print("字符集:" + charset + " -> " + charsetCheck); 110 | charset = charsetCheck; 111 | return readSource(url); 112 | }else{ 113 | return result; 114 | } 115 | } 116 | 117 | } 118 | 119 | 120 | /** 121 | * 随机生成user-agent字符串 122 | */ 123 | public static String randomUserAgentStr() { 124 | 125 | Random r = new Random(); 126 | Integer chromeVesionFirstNum = r.nextInt(14) + 100; 127 | Integer chromeVesionThirdNum = r.nextInt(3800); 128 | Integer chromeVesionFourthNum = r.nextInt(140); 129 | 130 | String[] osTypeArray = new String[] { 131 | "(Windows NT 6.1; WOW64) ", 132 | "(Windows NT 10.0; WOW64) ", 133 | "(X11; Linux x86_64) ", 134 | "(Macintosh; Intel Mac OS X 10_15_7) " 135 | }; 136 | 137 | String result = ""; 138 | result += "Mozilla/5.0 " ; 139 | result += osTypeArray[r.nextInt(3)] ; 140 | result += "AppleWebKit/537.36 (KHTML, like Gecko) "; 141 | result += "Chrome/" + chromeVesionFirstNum + ".0." + chromeVesionThirdNum + "." + chromeVesionFourthNum + " "; 142 | result += "Safari/537.36"; 143 | 144 | return result; 145 | } 146 | 147 | /** 148 | * 判断URL资源是否存在 149 | * @param url 150 | * @return 151 | * @throws MalformedURLException 152 | * @throws ProtocolException 153 | * @throws IOException 154 | */ 155 | public static boolean exists(String url) throws MalformedURLException, ProtocolException, IOException{ 156 | return exists(url); 157 | } 158 | 159 | /** 160 | * 判断URL资源是否存在 161 | * @param url 162 | * @param cookie字符串 163 | * @return 164 | * @throws MalformedURLException 165 | * @throws ProtocolException 166 | * @throws IOException 167 | */ 168 | public static boolean exists(String url, String cookieStr) throws MalformedURLException, ProtocolException, IOException{ 169 | URL u = new URL(url); 170 | HttpURLConnection conn = (HttpURLConnection) u.openConnection(); 171 | //允许重定向,否则部分图片无法下载 172 | HttpURLConnection.setFollowRedirects(true); 173 | conn.setInstanceFollowRedirects(true); 174 | /* 设置 URL 请求的方法, GET POST HEAD OPTIONS PUT DELETE TRACE 以上方法之一是合法的,具体取决于协议的限制。*/ 175 | conn.setRequestMethod("GET"); 176 | conn.setRequestProperty("User-Agent", URLUtils.randomUserAgentStr()); 177 | 178 | //2016-03-16 如不加referer信息,下载影人相册时,大图监测返回403异常 179 | conn.setRequestProperty("referer", "https://www.douban.com/"); 180 | 181 | //2024-10-02 如果有Cookie,设置参数。用于登录后页面访问。 182 | if (cookieStr != null && cookieStr != "") { 183 | conn.setRequestProperty("Cookie", cookieStr); 184 | } 185 | 186 | //=======信息======= 187 | // Map> map = conn.getHeaderFields(); 188 | // for (Entry> element : map.entrySet()) { 189 | // System.out.println(element.getKey() + " = " + element.getValue()); 190 | // } 191 | // System.out.println("getContentType = " + conn.getContentType()); 192 | // System.out.println("getContentLength = " + conn.getContentLength()); 193 | // System.out.println("getInstanceFollowRedirects = " + conn.getInstanceFollowRedirects()); 194 | // System.out.println("getDefaultUseCaches = " + conn.getDefaultUseCaches()); 195 | // System.out.println("getContentEncoding = " + conn.getContentEncoding()); 196 | // System.out.println("getExpiration = " + conn.getExpiration()); 197 | // System.out.println("getResponseCode = " + conn.getResponseCode()); 198 | // System.out.println("getResponseMessage = " + conn.getResponseMessage()); 199 | //=======/信息======= 200 | System.out.println("ResponseCode:" + conn.getResponseCode()); 201 | if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { 202 | return true; 203 | }else{ 204 | return false; 205 | } 206 | } 207 | 208 | 209 | public static void main(String[] args) throws MalformedURLException, ProtocolException, IOException { 210 | // //资源验证 211 | // System.out.println(URLUtils.exists("http://img5.douban.com/view/photo/photo/public/p814563030.jpg")); 212 | // System.out.println("=========="); 213 | // System.out.println(URLUtils.exists("http://img5.douban.com/view/photo/large/public/p814563030.jpg")); 214 | // System.out.println("=========="); 215 | // System.out.println(URLUtils.exists("http://img5.douban.com/view/photo/large/public/p814563030.jpgx")); 216 | 217 | //获取页面源码 218 | // System.out.println(readSource("http://www.douban.com/photos/album/67952443/")); 219 | // System.out.println(readSource("https://www.douban.com/photos/album/120012756/")); 220 | // System.out.println(readSource("https://movie.douban.com/celebrity/1138320/photos/?start=0")); 221 | //2023-07-05:因豆瓣改版“用户相册首页”只支持登录后访问,程序无权限,无法获取相册清单。 222 | System.out.println(readSource("https://www.douban.com/people/BlackGray/photos")); 223 | 224 | // try { 225 | // System.out.println(URLUtils.exists("https://img1.doubanio.com/view/photo/raw/public/p2321685527.jpg")); 226 | // } catch (MalformedURLException e) { 227 | // // TODO Auto-generated catch block 228 | // e.printStackTrace(); 229 | // } catch (ProtocolException e) { 230 | // // TODO Auto-generated catch block 231 | // e.printStackTrace(); 232 | // } catch (IOException e) { 233 | // // TODO Auto-generated catch block 234 | // e.printStackTrace(); 235 | // } 236 | 237 | //此前默认user-agent 238 | // System.out.println("Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36"); 239 | //随机生成user-agent 240 | // System.out.println(randomUserAgentStr()); 241 | 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/UserAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.util.Map; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import cn.blackgray.douban.album.download.common.Common; 8 | import cn.blackgray.douban.album.download.common.Console; 9 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 10 | import cn.blackgray.douban.album.download.model.Album; 11 | import cn.blackgray.douban.album.download.model.BGImage; 12 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 13 | 14 | /** 15 | * 个人相册处理器 16 | */ 17 | public class UserAlbumHandler extends AlbumHandler { 18 | 19 | //照片分页大小(一页18张图) 20 | public static final int PAGE_SIZE_IMAGES = 18; 21 | public static final String PAGE_TAG = "m_start"; 22 | 23 | //若为私密相册,照片前缀为x,公共相册照片前缀为p 24 | public static final String IMAGE_NAME_REGEX = "(x|p)\\d+.(" + Common.IMAGE_TYPE + ")"; 25 | public static final String ALBUM_URL_REGEX = "(http|https)://www.douban.com/photos/album/\\d+/"; 26 | 27 | @Override 28 | public String getURLRegex() { 29 | return ALBUM_URL_REGEX; 30 | } 31 | 32 | @Override 33 | public String getPageRegex() { 34 | return super.getAlbumURL() + "\\?\\w+=\\d+"; 35 | } 36 | 37 | @Override 38 | public boolean removeURLParameter() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public Integer getPageSize() { 44 | return PAGE_SIZE_IMAGES; 45 | } 46 | 47 | @Override 48 | public String getPageTag() { 49 | return PAGE_TAG; 50 | } 51 | 52 | @Override 53 | public String getImageNameRegex() { 54 | return IMAGE_NAME_REGEX; 55 | } 56 | 57 | @Override 58 | public boolean hasRaw(Album album) { 59 | if (album.getIsPrivateAlbum()) { 60 | //私密相册没有原始大图,不做判断下载 61 | return false; 62 | }else { 63 | return true; 64 | } 65 | } 66 | 67 | @Override 68 | public String getRawURL(String imageURL) { 69 | //最初 - 没有原始图 - 已失效 70 | //http://img3.douban.com/view/photo/photo/public/p1932887553.jpg - 小图 71 | //http://img3.douban.com/view/photo/large/public/p1932887553.jpg - 大图 72 | 73 | //2017-11-06之前 74 | //小图 75 | //https://img1.doubanio.com/view/photo/m/public/p2504126549.webp 76 | //大图 77 | //https://img3.doubanio.com/view/photo/l/public/p2504126600.webp 78 | //原始图 79 | //https://img3.doubanio.com/view/photo/large/public/p2504126600.jpg 80 | 81 | //2017-11-06 82 | //原始图地址有变,地址中photo/large变为photo/raw 83 | 84 | //2024-10-02 85 | //检查豆瓣,发现页面中已经无法访问原始图,点击“查看大图”,显示的图与此前"l"类型图片大小一致。 86 | //但通过程序仍能下载原始大图,地址中photo/l,改为photo/raw。 87 | //若在界面中访问原始大图,需手动调整地址,地址中photo/l,改为photo/large。 88 | //很迷惑的操作,不理解。豆瓣内部有问题? 89 | 90 | return imageURL.replace("photo/l", "photo/raw").trim(); 91 | } 92 | 93 | @Override 94 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 95 | 96 | //【1】若未做过判断,判断是否为私密相册 97 | if (!album.getIsPrivateAlbum()) { 98 | //通过图片前缀判断,x为私密,p为公开 99 | String imageNameRegexToPrivate = "x\\d+.(" + Common.IMAGE_TYPE + ")"; 100 | String imageName = imageURL.substring(imageURL.lastIndexOf("/") + 1); 101 | if (imageName.matches(imageNameRegexToPrivate)) { 102 | //私密相册 103 | album.setIsPrivateAlbum(true); 104 | } 105 | } 106 | 107 | 108 | 109 | //【2】获取照片描述、评论信息 110 | //获取照片ID 111 | String imageId; 112 | if (album.getIsPrivateAlbum()) { 113 | //私密相册处理实现 114 | imageId = imageURL.substring(imageURL.lastIndexOf("/x") + 2,imageURL.lastIndexOf(".")); 115 | }else { 116 | //公共相册处理实现 117 | imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 118 | } 119 | //照片描述 120 | String descStartIndexStr = "",descStartIndex)); 125 | }else{ 126 | desc = ""; 127 | } 128 | //照片评论 129 | String commentTatolStartIndexStr = ""; 130 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 131 | Integer commentTatol = null; 132 | if (commentTatolStartIndex != -1) { 133 | //“3回应” 134 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 135 | commentTatol = Integer.valueOf(s.replace("回应", "")); 136 | } 137 | 138 | 139 | //【3】获取照片地址 140 | if (album.getIsPrivateAlbum()) { 141 | //私密相册处理实现 142 | //如果是私密相册,涉及每张照片地址单独获取,时间较长,输出日志明细 143 | Console.print("---------------------------------------"); 144 | album.setPrivatePhotoURLAnalyzeTotal(album.getPrivatePhotoURLAnalyzeTotal() + 1); 145 | Console.print("获取私密相册照片地址开始 - " + album.getPrivatePhotoURLAnalyzeTotal() + " - " + pageURL); 146 | 147 | //根据照片ID生成照片单独页面URL 148 | String photoPageURL = "https://www.douban.com/photos/photo/" + imageId; 149 | Console.print("获取私密相册照片页面源码 - " + photoPageURL); 150 | String privatePhotoPageSource = URLUtils.readSource(photoPageURL); 151 | 152 | //获取照片URL 153 | imageURL = getPrivatePhotoURL(privatePhotoPageSource); 154 | if (imageURL != null) { 155 | Console.print("获取私密照片URL成功 - " + imageURL); 156 | }else{ 157 | Console.print("获取私密照片URL失败 - 无有效照片URL"); 158 | } 159 | 160 | }else { 161 | //公共相册处理实现 162 | /** 163 | * 2024-10-02 增加备注 164 | * 个人相册分公开相册、私有相册 165 | * 公开相册缩略图地址示例:https://img3.doubanio.com/view/photo/m/public/p881668782.jpg 166 | * 私有相册缩略图地址示例:https://simg.douban.com/view/photo/m/-urE8H6nKPAUXOxX_PVPWA/2745512/x2196276230.jpg 167 | * 168 | * 图片名称公开的前缀为p,私有的为x。 169 | * 170 | * 私有照片大图地址示例:https://simg.douban.com/view/photo/l/6NrUwD0fG4RjxvP7FjkzLA/2745512/x2196276230.jpg 171 | * 地址中包含随机生成字符串"6fph28J_c7MrPu9_LQT0Pw",每张图片值不同。 172 | * 且字符串只在明细界面可见,在清单界面中无信息。 173 | * 导致无法通过简单修改固定字符串方式推测大图地址。 174 | * 175 | * 私有照片原始大图示例:https://simg.douban.com/view/photo/l/6NrUwD0fG4RjxvP7FjkzLA/2745512/x2196276230.jpg 176 | * 和大图地址相同?与老图片没有RAW原图有关?待检查其他图片示例 177 | * 检查豆瓣,发现页面中已经无法访问原始图,点击“查看大图”,显示的图与此前"l"类型图片大小一致。 178 | * 但通过程序仍能下载原始大图。 179 | */ 180 | //推测大图地址,加入集合。若为私密相册,无法通过此方式推测地址。 181 | //m——>l:缩略图——>大图 182 | imageURL = imageURL.replace("photo/m", "photo/l").trim(); 183 | } 184 | 185 | //【4】照片加入集合 186 | if (imageURL != null) { 187 | if (!map.containsKey(imageURL)) { 188 | desc = desc.replace("\\t\\n","").trim(); 189 | map.put(imageURL, new BGImage(desc, imageURL, commentTatol)); 190 | }else{ 191 | //标注相册首页 192 | BGImage bgImage = map.get(imageURL); 193 | if (bgImage.getCommentTotal()!=null && commentTatol==null) { 194 | commentTatol = bgImage.getCommentTotal(); 195 | } 196 | map.put(imageURL, new BGImage("※" + bgImage.getDesc(), imageURL, commentTatol)); 197 | } 198 | } 199 | 200 | } 201 | 202 | 203 | @Override 204 | public boolean checkBGImage(BGImage bgImage) { 205 | return bgImage.getUrl().indexOf("albumicon") < 0; 206 | } 207 | 208 | @Override 209 | public String getCommentURL(Album album, BGImage image) { 210 | //http://www.douban.com/photos/album/67952443/ 211 | //http://www.douban.com/photos/photo/1560777504/ 212 | return "http://www.douban.com/photos/photo/" + image.getId(); 213 | } 214 | 215 | @Override 216 | public String getAlbumDesc(String source) { 217 | String startTag = "data-desc=\""; 218 | if (source.indexOf(startTag) != -1) { 219 | int startIndex = source.indexOf(startTag) + startTag.length(); 220 | String desc = source.substring(startIndex,source.indexOf("\"", startIndex)).replace("\\t\\n","").trim(); 221 | if (desc.indexOf("【") != -1) { 222 | desc = desc.substring(desc.lastIndexOf("】") + 1); 223 | } 224 | return desc; 225 | }else{ 226 | return null; 227 | } 228 | } 229 | 230 | 231 | /** 232 | * 获取私密相册照片地址 233 | */ 234 | private String getPrivatePhotoURL(String source) { 235 | 236 | //示例源码 237 | //view-source:https://www.douban.com/photos/photo/2196276230/ 238 | //需获取的照片地址 239 | //https://simg.douban.com/view/photo/l/6NrUwD0fG4RjxvP7FjkzLA/2745512/x2196276230.jpg 240 | 241 | //截取出包含照片地址的代码段 242 | int beginIndex = source.indexOf("image-show-inner"); 243 | int endIndex = source.indexOf("", beginIndex); 244 | source = source.substring(beginIndex, endIndex); 245 | 246 | //获取照片地址 247 | String regex = "(http|https)://(\\w|\\s|\\.|-|_|/)+[\\.](" + Common.IMAGE_TYPE + ")"; 248 | Pattern p = Pattern.compile(regex); 249 | Matcher m = p.matcher(source); 250 | while (m.find()) { 251 | return m.group().trim(); 252 | } 253 | 254 | return null; 255 | } 256 | 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/download/DownloadProcessing.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.download; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.Set; 13 | 14 | import javax.swing.JLabel; 15 | import javax.swing.JProgressBar; 16 | 17 | import cn.blackgray.douban.album.download.common.Common; 18 | import cn.blackgray.douban.album.download.common.Console; 19 | import cn.blackgray.douban.album.download.common.utils.FileUtils; 20 | import cn.blackgray.douban.album.download.model.Album; 21 | import cn.blackgray.douban.album.download.model.BGImage; 22 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 23 | import cn.blackgray.douban.album.download.service.handler.PageAnalyzer; 24 | import cn.blackgray.douban.album.download.ui.MainFrame; 25 | 26 | /** 27 | * 下载处理工具类 28 | * @author BlackGray 29 | */ 30 | public class DownloadProcessing { 31 | 32 | private static MainFrame mainFrame = MainFrame.getInstance(); 33 | 34 | private static JProgressBar processUnitProgressBar = mainFrame.processUnitProgressBar; //处理单元进度条 35 | private static JLabel processUnitCountLabel = mainFrame.processUnitCountLabel; //处理单元进度条 36 | 37 | 38 | 39 | /** 40 | * 根据相册模型下载相册 41 | * @throws FileNotFoundException 42 | * @throws IOException 43 | */ 44 | public static void downloadAlbum(Album album){ 45 | 46 | //单相册下载时间 47 | long albumDownloadTime = System.currentTimeMillis(); 48 | //更新统计 49 | int updateCount = 0; 50 | 51 | Map imageMap = new HashMap(); //【单相册全照片集合】 52 | 53 | //【启动处理单元】 54 | //处理单元总数 55 | int processUnitMax = new Double(Math.ceil((double)album.getPageURLLsit().size()/Common.PROCESS_UNIT_SIZE)).intValue(); 56 | int processUnitNumber = 0; //处理单元计数 57 | processUnitProgressBar.setMaximum(processUnitMax); 58 | processUnitProgressBar.setValue(0); 59 | processUnitCountLabel.setText("0/" + processUnitMax + " "); 60 | for (int j = 0; j < processUnitMax; j++) { 61 | //处理单元耗时 62 | long processUnitTime = System.currentTimeMillis(); 63 | //取出规定条数记录,执行处理单元 64 | List pageURLList = new ArrayList(); 65 | int start = processUnitNumber * Common.PROCESS_UNIT_SIZE; 66 | int end = start + Common.PROCESS_UNIT_SIZE; 67 | if (end > album.getPageURLLsit().size()) { 68 | end = album.getPageURLLsit().size(); 69 | } 70 | for (int k = start; k < end; k++) { 71 | pageURLList.add(album.getPageURLLsit().get(k)); 72 | } 73 | //处理 - 返回更新信息 74 | updateCount += processUnit(processUnitNumber, album, imageMap, pageURLList); 75 | 76 | //处理单元计数+1 77 | processUnitNumber++; 78 | processUnitProgressBar.setValue(j + 1); 79 | processUnitCountLabel.setText((j + 1) + "/" + processUnitMax + " "); 80 | //【判断启动休眠】 81 | //处理单元数大于1,并且不是最后一次处理才执行休眠判断 82 | if (processUnitMax > 1 && (j + 1) != processUnitMax) { 83 | long t = System.currentTimeMillis() - processUnitTime; 84 | Console.print("处理单元耗时:" + (t/1000) + "s"); 85 | if (t < Common.TIME_PROCESS_MIN) { 86 | Console.print("短时间访问页面次数过多,启动休眠~"); 87 | Console.print("( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///)"); 88 | long c = Common.TIME_PROCESS_SLEEP; 89 | while (true) { 90 | if (c <= 0) { 91 | Console.print("[]~( ̄▽ ̄)~* "); 92 | break; 93 | } 94 | try { 95 | Thread.sleep(1000); 96 | Console.print("休眠倒计时:" + c + "\t (#°Д°)\""); 97 | } catch (InterruptedException e) { 98 | e.printStackTrace(); 99 | } 100 | c--; 101 | } 102 | } 103 | } 104 | } 105 | //单相册处理完成,map赋值,提供后续操作使用 106 | album.setPhotosList(new ArrayList(imageMap.values())); 107 | 108 | //后续操作 109 | if (album.getPhotosList().size() != 0) { 110 | //【生成描述文件】 111 | album.createDescDoc(); 112 | //【输出统计信息】 113 | Console.print("相册下载完成 - " + album.getName()); 114 | Console.print(" 数量:" + album.getPhotosList().size()); 115 | if (album.isUpdate()) { 116 | Console.print(" 新增:" + updateCount + "(张)"); 117 | } 118 | Console.print(" 单相册耗时:" + (System.currentTimeMillis() - albumDownloadTime)/1000 + "s"); 119 | }else{ 120 | if (album.getIsVisibleToSelf()) { 121 | Console.print("提示:此相册已被设为“仅自己可见”,抱歉无法访问,无法下载。"); 122 | Console.print("提示:若是自身相册,或自己账号具有访问权限,可尝试登陆后下载。"); 123 | }else if(album.getIsVisibleToFriend()){ 124 | Console.print("提示:此相册设置为仅朋友可见。你还不是他/她的朋友,所以无法访问。"); 125 | Console.print("提示:若已是朋友,可尝试登陆后下载。"); 126 | }else { 127 | Console.print("提示:失败或页面无图片,取消下载。"); 128 | Console.print("提示:可能触发豆瓣反爬虫机制被禁IP,数小时后或第二天可恢复。"); 129 | Console.print("提示:切勿打开多个软件同时下载,勿短时间内下载大量图片,容易被禁。"); 130 | } 131 | } 132 | 133 | } 134 | 135 | 136 | /** 137 | * 处理单元 138 | * @param album 139 | * @param imageMap 140 | * @param pageURLList 141 | * @param processUnitMax 142 | * @param processUnitNumber 143 | * @return 图片更新数 144 | */ 145 | private static int processUnit(int processUnitNumber, Album album, Map imageMap,List pageURLList){ 146 | int update = 0; 147 | //【信息获取】 148 | Console.print("处理单元[" + processUnitNumber + "]:启动信息获取"); 149 | Set imageURLSet = infoProcess(album, imageMap, pageURLList); 150 | //【下载图片】 151 | if(imageURLSet.size() == 0) { 152 | Console.print("处理单元[" + processUnitNumber + "]:页面未检测到图片"); 153 | }else { 154 | //【创建目录】 155 | if(processUnitNumber == 0) { 156 | FileUtils.createDir(album); 157 | } 158 | 159 | Console.print("处理单元[" + processUnitNumber + "]:开始下载:" + album.getName() + "(" + imageURLSet.size() + "张)"); 160 | update = DownloadManager.downloadImage(album, new ArrayList(imageURLSet),album.getPath()); 161 | 162 | //【下载大图】 163 | AlbumHandler albumHandler = album.getAlbumHandler(); 164 | if (Common.IS_DOWNLOAD_RAW && albumHandler.hasRaw(album)) { 165 | Console.print("处理单元[" + processUnitNumber + "]:检测并下载大图"); 166 | //创建目录 167 | String path = album.getPath() + File.separatorChar + "raw"; 168 | File file = new File(path); 169 | if (!file.exists()) { 170 | file.mkdir(); 171 | } 172 | //【获取地址】 173 | //小站大图 174 | List list = new ArrayList(); 175 | for (String url : imageURLSet) { 176 | list.add(albumHandler.getRawURL(url)); 177 | } 178 | //执行下载 179 | update += DownloadManager.downloadImage(album, list,path); 180 | } 181 | } 182 | return update; 183 | } 184 | 185 | 186 | /** 187 | * 照片信息处理 188 | * @param imageMap 189 | * @param pageURLList 190 | * @param processUnitMax 191 | * @param processUnitNumber 192 | * @return 193 | */ 194 | private static Set infoProcess(Album album, Map imageMap,List pageURLList){ 195 | 196 | Set imageURLSet = new HashSet(); //需要下载的图片 - 单处理单元 197 | 198 | for (int i = 0; i < pageURLList.size(); i++) { 199 | Console.print("分析页面(" + (i + 1) + "/" + pageURLList.size() + "):" + pageURLList.get(i)); 200 | //查询单页所有照片地址和描述,可能会出现错误,如果出错,尝试重新分析一次 201 | Map map = new HashMap(); 202 | try { 203 | map = PageAnalyzer.findImageURLAndDesc(album, pageURLList.get(i)); 204 | } catch (Exception e) { 205 | try { 206 | map = PageAnalyzer.findImageURLAndDesc(album, pageURLList.get(i)); 207 | } catch (Exception e2) { 208 | Console.print("页面分析错误,下载失败:" + pageURLList.get(i)); 209 | } 210 | e.printStackTrace(); 211 | } 212 | 213 | //保存照片地址和描述信息 214 | for (Entry entry : map.entrySet()) { 215 | if (!imageMap.containsKey(entry.getKey())) { 216 | imageMap.put(entry.getKey(), entry.getValue()); 217 | imageURLSet.add(entry.getKey()); 218 | }else{ 219 | //※每个页面都有相册封面照片的链接,扫描照片地址时,有可能每次都对这张照片进行处理 220 | //封面照片可能在任意一页中,获取描述信息时,除了所在页可以得到描述外,其他页的描述都为空 221 | //这里判断每页图片是否存在交集,存在,说明该图是封面照片 222 | //根据图片是否有描述,可以确定图片是否在该页 223 | if (imageMap.get(entry.getKey()).getDesc().equals("")) { 224 | //如果之前添加的这张图没有描述,并且当前图有描述,设置描述信息,并添加首页图片标识,否则不执行任何操作 225 | if (entry.getValue().getDesc().equals("")) { 226 | //如果不加photo.getValue().equals("")判断,可能出现的情况: 227 | //1 false "" 228 | //2 false "" -> ※ 229 | //3 true "DESC" -> ※ 230 | BGImage image = imageMap.get(entry.getKey()); 231 | image.setDesc("※" + entry.getValue().getDesc()); 232 | imageMap.put(entry.getKey(), image); 233 | }else{ 234 | imageMap.put(entry.getKey(), entry.getValue()); 235 | } 236 | }else { 237 | //如果之前添加的图有描述,添加首页图片标识 238 | BGImage bgImage = imageMap.get(entry.getKey()); 239 | String desc = ("※" + bgImage.getDesc()).replaceAll("※+", "※"); 240 | if (desc.equals("※")) { 241 | bgImage = entry.getValue(); 242 | bgImage.setDesc("※" + bgImage.getDesc()); 243 | }else{ 244 | bgImage.setDesc(desc); 245 | } 246 | imageMap.put(entry.getKey(), bgImage); 247 | }; 248 | }; 249 | } 250 | 251 | try { 252 | //睡眠一定时间 253 | Thread.sleep(Common.TIME_ONE_PAGE_INFO_PROCESS_SLEEP); 254 | } catch (InterruptedException e) { 255 | e.printStackTrace(); 256 | } 257 | } 258 | return imageURLSet; 259 | } 260 | 261 | 262 | 263 | 264 | } 265 | -------------------------------------------------------------------------------- /src/main/java/cn/blackgray/douban/album/download/service/handler/handler/SitePublicAlbumHandler.java: -------------------------------------------------------------------------------- 1 | package cn.blackgray.douban.album.download.service.handler.handler; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.File; 6 | import java.io.FileReader; 7 | import java.io.FileWriter; 8 | import java.io.IOException; 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import cn.blackgray.douban.album.download.common.Common; 16 | import cn.blackgray.douban.album.download.common.Console; 17 | import cn.blackgray.douban.album.download.common.utils.URLUtils; 18 | import cn.blackgray.douban.album.download.model.Album; 19 | import cn.blackgray.douban.album.download.model.BGImage; 20 | import cn.blackgray.douban.album.download.service.handler.AlbumHandler; 21 | import cn.blackgray.douban.album.download.service.image.ImageListComparator; 22 | 23 | /** 24 | * 小站公共相册处理器 25 | */ 26 | public class SitePublicAlbumHandler extends AlbumHandler { 27 | 28 | // http://site.douban.com/127530/widget/public_album/7152314/ 29 | // http://site.douban.com/127530/widget/public_album/7152314/?start=30 30 | 31 | public static final int PAGE_SIZE_IMAGES_SITE = 30; //小站公共相册照片分页大小(一页30张图) 32 | public static final String PAGE_TAG = "start"; 33 | public static final String IMAGE_NAME_REGEX = "p\\d+.(" + Common.IMAGE_TYPE + ")"; 34 | public static final String ALBUM_URL_REGEX = "(http|https)://site.douban.com/\\d+/widget/public_album/\\d+/"; 35 | 36 | @Override 37 | public String getURLRegex() { 38 | return ALBUM_URL_REGEX; 39 | } 40 | 41 | @Override 42 | public String getPageRegex() { 43 | // /127530/widget/public_album/7152314/?start=30 44 | return "/widget/public_album/\\d+/\\?\\w+=\\d+"; 45 | } 46 | 47 | @Override 48 | public boolean removeURLParameter() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public Integer getPageSize() { 54 | return PAGE_SIZE_IMAGES_SITE; 55 | } 56 | 57 | @Override 58 | public String getPageTag() { 59 | return PAGE_TAG; 60 | } 61 | 62 | @Override 63 | public String getImageNameRegex() { 64 | return IMAGE_NAME_REGEX; 65 | } 66 | 67 | 68 | @Override 69 | public boolean hasRaw(Album album) { 70 | return false; 71 | } 72 | 73 | @Override 74 | public String getRawURL(String imageURL) { 75 | return null; 76 | } 77 | 78 | @Override 79 | public void createBGImage(Album album, String source, String pageURL, String imageURL, Map map) { 80 | 81 | //
  • 82 | //
    83 | // 84 | //
    85 | // 86 | //

    周庄

    87 | // 来自 劝君莫打鸟 88 | // 1回应 89 | //
    90 | //
    91 | //
  • 92 | 93 | String imageId = imageURL.substring(imageURL.lastIndexOf("/p") + 2,imageURL.lastIndexOf(".")); 94 | String siteAlbumId = pageURL.substring(pageURL.indexOf("public_album/") + 13, pageURL.lastIndexOf("/")); 95 | //【描述】 96 | // 1回应 108 | String commentTatolStartIndexStr = pageURL.substring(0,pageURL.indexOf(siteAlbumId)) + siteAlbumId + "/photo/" + imageId + "/#comments\">"; 109 | int commentTatolStartIndex = source.indexOf(commentTatolStartIndexStr); 110 | Integer commentTatol = null; 111 | if (commentTatolStartIndex != -1) { 112 | //“3回应” 113 | String s = source.substring(commentTatolStartIndex + commentTatolStartIndexStr.length(), source.indexOf("",commentTatolStartIndex)); 114 | commentTatol = Integer.valueOf(s.replace("回应", "")); 115 | } 116 | //【所有者ID&主页】 117 | String ownerURL = null; 118 | String ownerName = null; 119 | if (descStartIndex != -1) { 120 | String ownerStartStr = "来自 ", ownerStartIndex)); 123 | try { 124 | ownerURL = ownerA.substring(0,ownerA.indexOf("/\">")); 125 | } catch (Exception e) { 126 | System.out.println("====================="); 127 | System.out.println(ownerA); 128 | System.out.println(ownerA.indexOf("")); 130 | System.out.println("====================="); 131 | e.printStackTrace(); 132 | } 133 | ownerName = ownerA.substring(ownerA.indexOf(">") + 1); 134 | } 135 | //【照片】 136 | //http://img3.douban.com/view/photo/thumb/public/p1748477871.jpg 137 | imageURL = imageURL.replace("thumb", "photo").trim(); //thumb——>photo:缩略图——>大图 138 | desc = desc.replace("\\t\\n","").trim(); 139 | if (!map.containsKey(imageURL) || (map.containsKey(imageURL) && (map.get(imageURL).getOwnerURL()==null && ownerURL!=null))) { 140 | BGImage bgImage = new BGImage(desc, imageURL, commentTatol); 141 | bgImage.setOwnerURL(ownerURL); 142 | bgImage.setOwnerName(ownerName); 143 | map.put(imageURL, bgImage); 144 | } 145 | 146 | } 147 | 148 | 149 | @Override 150 | public boolean checkBGImage(BGImage bgImage) { 151 | return bgImage.getUrl().indexOf("albumicon") < 0; 152 | } 153 | 154 | @Override 155 | public String getCommentURL(Album album, BGImage image) { 156 | // http://site.douban.com/127530/widget/public_album/7152314/ 157 | // http://site.douban.com/127530/widget/public_album/7152314/photo/1781015215/ 158 | return album.getUrl() + "photo/" + image.getId(); 159 | } 160 | 161 | @Override 162 | public String getAlbumDesc(String source) { 163 | String startTag = "data-desc=\""; 164 | if (source.indexOf(startTag) != -1) { 165 | int startIndex = source.indexOf(startTag) + startTag.length(); 166 | String desc = source.substring(startIndex,source.indexOf("\"", startIndex)).replace("\\t\\n","").trim(); 167 | if (desc.indexOf("【") != -1) { 168 | desc = desc.substring(desc.lastIndexOf("】") + 1); 169 | } 170 | return desc; 171 | }else{ 172 | return null; 173 | } 174 | } 175 | 176 | @Override 177 | public void createDescDoc(Album album) { 178 | List imageList = album.getPhotosList(); 179 | Map map = new HashMap(); 180 | for (BGImage bgImage : imageList) { 181 | map.put(bgImage.getUrl(), bgImage); 182 | } 183 | List keyList = new ArrayList(map.keySet()); 184 | //排序 185 | Collections.sort(keyList,new ImageListComparator()); 186 | try { 187 | BufferedWriter bw = new BufferedWriter(new FileWriter(album.getPath() + "/" + Common.DEFAULT_DOC_NAME)); 188 | //输出相册地址 189 | bw.write(URLUtils.charset + " " + album.getUrl() + " " +album.getDate().getTime() + " -"); 190 | bw.newLine(); 191 | //输出照片地址和描述 192 | for (int i = 0; i < keyList.size(); i++) { 193 | BGImage bgImage = map.get(keyList.get(i)); 194 | Integer commentTotal = bgImage.getCommentTotal(); 195 | String commentTotalStr = commentTotal==null?"-":String.valueOf(commentTotal); 196 | bw.write((i + 1) + " " + keyList.get(i) + " " + commentTotalStr + " " + bgImage.getDesc()); 197 | bw.newLine(); 198 | //活动相册,输出用户名&主页地址 199 | bw.write(bgImage.getOwnerURL() + " " + bgImage.getOwnerName()); 200 | bw.newLine(); 201 | } 202 | bw.flush(); 203 | bw.close(); 204 | Console.print("生成描述文档:成功"); 205 | } catch (IOException e) { 206 | Console.print("生成描述文档:失败"); 207 | e.printStackTrace(); 208 | } 209 | } 210 | 211 | 212 | @Override 213 | public List getBGImageFromDescDoc(File descFile) throws IOException { 214 | List list = new ArrayList(); 215 | BufferedReader reader = new BufferedReader(new FileReader(descFile)); 216 | BGImage tempBGImage = null; 217 | int line = 0; 218 | String str; 219 | while ((str = reader.readLine()) != null) { 220 | if (line == 0) { 221 | line++; 222 | }else{ 223 | //0行为相册&页面信息,奇数行照片信息,偶数行用户信息 224 | if (line%2 == 1) { 225 | String[] info = str.split(" ",4); 226 | //info[0],info[1],info[2],info[3]分别为照片编号、原始URL地址、评论数、照片描述 227 | tempBGImage = new BGImage(info[0],info[1],info[3]); 228 | //评论数 229 | if (!info[2].equals("-")) { 230 | tempBGImage.setCommentTotal(Integer.valueOf(info[2])); 231 | } 232 | line++; 233 | }else{ 234 | String[] info = str.split(" ",2); 235 | //info[0],info[1]分别为用户首页URL、用户名 236 | BGImage bgImage = tempBGImage; 237 | bgImage.setOwnerURL(info[0]); 238 | bgImage.setOwnerName(info[1]); 239 | list.add(bgImage); 240 | line++; 241 | } 242 | } 243 | } 244 | reader.close(); 245 | return list; 246 | } 247 | 248 | } 249 | 250 | --------------------------------------------------------------------------------