initial commit

This commit is contained in:
2020-03-26 14:39:59 +08:00
commit 532d7371d8
31 changed files with 4224 additions and 0 deletions

View File

@ -0,0 +1,69 @@
package net.lamgc.cgj.pixiv;
/**
* 插图(集)的信息
*/
public class IllustInfo{
/**
* 插图(集)ID
*/
public final int illustID;
/**
* 第几页
*/
public final int page;
/**
* 插图标题
*/
public final String title;
/**
* 插图说明
*/
public final String description;
/**
* 插图标签
*/
public final String[] tags;
/**
* 插图图片长度
*/
public final int width;
/**
* 插图图片高度
*/
public final int height;
/**
* 作者名
*/
public final String authorName;
/**
* 作者用户ID
*/
public final int authorUserID;
public IllustInfo(int illustID, String title, String description, String[] tags, int width, int height, String authorName, int authorUserID){
this(illustID, 0, title, description, tags, width, height, authorName, authorUserID);
}
public IllustInfo(int illustID, int p, String title, String description, String[] tags, int width, int height, String authorName, int authorUserID){
this.illustID = illustID;
this.page = p;
this.title = title;
this.description = description;
this.tags = tags;
this.width = width;
this.height = height;
this.authorName = authorName;
this.authorUserID = authorUserID;
}
}

View File

@ -0,0 +1,267 @@
package net.lamgc.cgj.pixiv;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class Pixiv {
/**
* illust Link
*/
public final static String ATTR_LINK = "link";
/**
* illust Id
*/
public final static String ATTR_ILLUST_ID = "illustId";
/**
* illust Title
*/
public final static String ATTR_TITLE = "title";
/**
* illust Author Name
*/
public final static String ATTR_AUTHOR_NAME = "authorName";
/**
* illust Author UserID
*/
public final static String ATTR_AUTHOR_ID = "authorId";
private final HttpClient httpClient;
public Pixiv(HttpClient client){
this.httpClient = client;
}
/**
* 使用帐号密码登录Pixiv
* @param PixivID Pixiv账户登录名
* @param Password Pixiv帐号密码
* @throws IOException 当登录连接出现异常时抛出
* @deprecated {@link PixivSession#Login(String, String)} 已经废除, 故本方法不可用
*/
public Pixiv(String PixivID, String Password) throws IOException {
this(PixivID, Password, null);
}
/**
* 使用帐号密码登录Pixiv
* @param PixivID Pixiv账户登录名
* @param Password Pixiv帐号密码
* @param proxy 代理设置
* @throws IOException 当登录连接出现异常时抛出
* @deprecated {@link PixivSession#Login(String, String)} 已经废除, 故本方法不可用
*/
public Pixiv(String PixivID, String Password, HttpHost proxy) throws IOException {
PixivSession pixivSession = new PixivSession(proxy, null);
if(pixivSession.Login(PixivID, Password)){
System.out.println("P站登录成功!");
}else{
System.out.println("P站登录失败!错误信息: " + pixivSession.getErrMsg());
throw new RuntimeException(pixivSession.getErrMsg());
}
//httpClient = pixivSession.getHttpClient();
httpClient = HttpClientBuilder.create()
.setDefaultCookieStore(pixivSession.getCookieStore())
.build();
}
/**
* 获取首页推荐列表
* @return 首页推荐列表, 一个Map对应一个推荐项, 使用<code>ATTR_</code>开头常量访问即可
* @throws IOException
*/
public List<Map<String, String>> getRecommend() throws IOException {
HttpGet getRequest = new HttpGet(PixivURL.PIXIV_INDEX_URL);
HttpResponse response = httpClient.execute(getRequest);
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
//获取推荐图列表(li)
//System.out.println(pageAsXML);
Document document = Jsoup.parse(pageAsXML);
List<String> links = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-thumbnail-link").eachAttr("href");
List<String> illustId = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-thumbnail-link").eachAttr("data-gtm-recommend-illust-id");
List<String> title = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-title>h1").eachAttr("title");
List<String> authorName = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-user-name").eachText();
List<String> authorId = document.select("._image-items.gtm-illust-recommend-zone>li>.gtm-illust-recommend-user-name").eachAttr("data-user_id");
List<Map<String, String>> recommendList = new ArrayList<>();
for(int i = 0; i < links.size(); i++){
//System.out.println(links.get(i));
Map<String, String> info = new HashMap<>();
info.put(ATTR_LINK, PixivURL.PIXIV_INDEX_URL + links.get(i));
info.put(ATTR_ILLUST_ID, illustId.get(i));
info.put(ATTR_TITLE, title.get(i));
info.put(ATTR_AUTHOR_NAME, authorName.get(i));
info.put(ATTR_AUTHOR_ID, authorId.get(i));
recommendList.add(info);
}
return recommendList;
}
public String[] getAllDownloadLink(int illustID) throws IOException {
HttpGet illustPage = new HttpGet(PixivURL.PIXIV_ILLUST_API_URL.replaceAll("\\{illustId}", String.valueOf(illustID)));
HttpResponse response = httpClient.execute(illustPage);
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
//System.out.println(pageAsXML);
JsonObject resultObj = (JsonObject) new JsonParser().parse(pageAsXML);
if(!resultObj.get("error").getAsBoolean()){
JsonArray bodyArray = resultObj.get("body").getAsJsonArray();
int length = bodyArray.size();
String[] result = new String[length];
for(int i = 0; i < length; i++){
JsonObject childObj = bodyArray.get(i).getAsJsonObject();
result[i] = childObj.get("urls").getAsJsonObject().get("original").getAsString();
}
return result;
}else{
return null;
}
}
/**
* 下载P站图片
* @param illustID 插图ID
* @return 成功返回图片输入流,失败或为多图则返回null
*/
public InputStream[] downloadIllustImage(int illustID) throws IOException {
String[] links = getAllDownloadLink(illustID);
List<InputStream> inputStreamList = new ArrayList<>();
int count = 1;
boolean retry = false;
for(int i = 0; i < links.length; i++){
try {
long sleepTime = (new Random().nextInt(4) + 2) * 1000;
System.out.println("nextTime: " + (float)(sleepTime / 1000));
Thread.sleep(sleepTime);
} catch (InterruptedException ignored) {}
String link = links[i];
System.out.print("page:" + count++ + "/" + links.length + " ...");
HttpGet imgGet = new HttpGet(link);
//关键!如果不加上Referer的话,会返回403
imgGet.setHeader("Referer", PixivURL.PIXIV_ILLUST_MEDIUM_URL.replaceAll("\\{illustId}", String.valueOf(illustID)));
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(20 * 1000)
.setConnectionRequestTimeout(20 * 1000)
.setSocketTimeout(30 * 1000)
.build();
imgGet.setConfig(config);
HttpResponse response;
try {
response = httpClient.execute(imgGet);
}catch(ConnectionPoolTimeoutException e){
if(retry){
retry = false;
System.out.println("获取失败,跳过...");
continue;
}
System.out.println("连接超时,重新获取...");
retry = true;
i--;
continue;
}
retry = false;
ByteArrayOutputStream cacheOutputStream = new ByteArrayOutputStream((int)response.getEntity().getContentLength());
InputStream content = response.getEntity().getContent();
int readLength;
byte[] cache = new byte[4096];
while((readLength = content.read(cache)) != -1){
cacheOutputStream.write(cache, 0, readLength);
}
byte[] data = cacheOutputStream.toByteArray();
//System.out.println("读到数据: " + data.length);
inputStreamList.add(new ByteArrayInputStream(data));
System.out.println("done!length: " + response.getEntity().getContentLength() + ")");
}
return inputStreamList.toArray(new InputStream[0]);
}
/**
* 下载P站图片
* @param illustID 插图ID
* @return 成功返回图片输入流,失败或为多图则返回null
*/
public InputStream downloadIllustImages(int illustID){
throw new UnsupportedOperationException();
}
/**
* 通过解析插图详情页获取
* - 插图标题
* - 插图作者(及其UserId)
* - 插图上传时间
* - 插图标签(原始标签)
* ...
* @return 成功返回IllustInfo对象,失败返回null
*/
public IllustInfo[] getIllustInfo(int[] illustIDs) throws IOException {
//获取Api
HttpGet apiRequest = new HttpGet(PixivURL.getPixivIllustInfoAPI(illustIDs));
HttpResponse response = httpClient.execute(apiRequest);
String resultText = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
System.out.println(resultText);
JsonObject resultObj = ((JsonObject) new JsonParser().parse(resultText));
if(resultObj.get("error").getAsBoolean()){
System.err.println("获取失败!");
return null;
}
List<IllustInfo> illustInfoList = new ArrayList<>();
JsonArray illustArray = resultObj.get("body").getAsJsonObject().get("illusts").getAsJsonArray();
illustArray.forEach(jsonElement -> {
JsonObject illustInfoObj = jsonElement.getAsJsonObject();
JsonArray tagsArray = illustInfoObj.get("tags").getAsJsonArray();
String[] tags = new String[tagsArray.size()];
for(int i = 0; i < tags.length; i++){
tags[i] = tagsArray.get(i).getAsString();
}
//TODO: 通过不需要作者id就能获取图片信息的api无法获取图片尺寸
IllustInfo illustInfo = new IllustInfo(
illustInfoObj.get("workId").getAsInt(),
illustInfoObj.get("title").getAsString(),
null,
tags,
-1,
-1,
illustInfoObj.get("userName").getAsString(),
illustInfoObj.get("userId").getAsInt()
);
});
return null;
}
/**
* 获取指定用户的所有插画
*/
public void getUserAllIllustTest() {
}
}

View File

@ -0,0 +1,533 @@
package net.lamgc.cgj.pixiv;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.netty.handler.codec.http.HttpHeaderNames;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
public class PixivDownload {
private final static Logger log = LoggerFactory.getLogger("PixivDownload");
private final HttpClient httpClient;
private final CookieStore cookieStore;
/**
* 构造一个PixivDownload对象
* @param cookieStore 存在已登录Pixiv的CookieStore对象
*/
public PixivDownload(CookieStore cookieStore) {
this(cookieStore, null);
}
/**
* 构造一个PixivDownload对象
* @param cookieStore 存在已登录Pixiv的CookieStore对象
* @param proxy 访问代理
*/
public PixivDownload(CookieStore cookieStore, HttpHost proxy) {
this.cookieStore = cookieStore;
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setDefaultCookieStore(cookieStore);
// UA: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
ArrayList<Header> defaultHeaders = new ArrayList<>(2);
defaultHeaders.add(new BasicHeader("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"));
builder.setDefaultHeaders(defaultHeaders);
builder.setProxy(proxy);
httpClient = builder.build();
}
public HttpClient getHttpClient() {
return httpClient;
}
public CookieStore getCookieStore() {
return cookieStore;
}
/**
* 获取帐号所有的收藏插画,并以输入流形式提供
* @param fn 回调函数函数传进的InputStream无需手动关闭
* @throws IOException 当获取时发生异常则直接抛出
*/
public void getCollectionAsInputStream(PageQuality quality, BiConsumer<String, InputStream> fn) throws IOException {
int pageIndex = 0;
HttpGet request;
Document document;
ArrayList<String> linkList = new ArrayList<>();
do {
request = new HttpGet(PixivURL.PIXIV_USER_COLLECTION_PAGE.replace("{pageIndex}", Integer.toString(++pageIndex)));
setCookieInRequest(request, cookieStore);
log.info("Request Link: " + request.getURI().toString());
HttpResponse response = httpClient.execute(request);
// 解析网页内容,获得所有的收藏信息
document = Jsoup.parse(EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8));
Elements items = document.select(".display_editable_works .image-item a.work");
List<String> hrefList = items.eachAttr("href");
log.info("第 {} 页获取到的图片项数量: {}", pageIndex, hrefList.size());
if(hrefList.size() == 0) {
break;
}
Gson gson = new Gson();
for (String href : hrefList) {
HttpGet linkApiRequest = createHttpGetRequest(PixivURL.PIXIV_ILLUST_API_URL.replace("{illustId}", href.substring(href.lastIndexOf("/") + 1)));
log.debug(linkApiRequest.getURI().toString());
HttpResponse httpResponse = httpClient.execute(linkApiRequest);
JsonObject linkResult = gson.fromJson(EntityUtils.toString(httpResponse.getEntity()), JsonObject.class);
if(linkResult.get("error").getAsBoolean()) {
log.error("接口返回错误信息: {}", linkResult.get("message").getAsString());
continue;
}
JsonArray linkArray = linkResult.get("body").getAsJsonArray();
for (int i = 0; i < linkArray.size(); i++) {
JsonObject linkObject = linkArray.get(i).getAsJsonObject().get("urls").getAsJsonObject();
linkList.add(linkObject.get((quality == null ? PageQuality.ORIGINAL : quality).toString().toLowerCase()).getAsString());
}
}
} while(!document.select(".pager-container>.next").isEmpty());
log.info("获取完成.");
AtomicInteger count = new AtomicInteger(1);
linkList.forEach(link -> {
log.info("Next Link [{}]: {}", count.getAndIncrement(), link);
InputStream imageInputStream = null;
int tryCount = 0;
do {
try {
imageInputStream = getImageAsInputStream(httpClient, link);
} catch (IOException e) {
log.error("获取图片数据时发生异常", e);
if(++tryCount < 5) {
log.warn("即将重试[{} / 5]", tryCount);
}
}
} while(imageInputStream == null);
try(InputStream imageInput = new BufferedInputStream(imageInputStream, 256 * 1024)) {
log.debug("调用回调方法...");
fn.accept(link, imageInput);
log.debug("回调方法调用完成.");
} catch (IOException e) {
log.error("图片获取失败", e);
}
});
}
/**
* 获取推荐插图
* @param quality 图片质量
* @param fn 回调函数
* @throws IOException 当获取时发生异常则直接抛出
*/
public void getRecommendAsInputStream(PageQuality quality, BiConsumer<String, InputStream> fn) throws IOException {
HttpResponse response = httpClient.execute(createHttpGetRequest(PixivURL.PIXIV_INDEX_URL));
Document document = Jsoup.parse(EntityUtils.toString(response.getEntity()));
HttpClient imageClient = HttpClientBuilder.create().build();
Elements elements = document.select(".gtm-illust-recommend-zone>.image-item>.gtm-illust-recommend-thumbnail-link");
for(int illustIndex = 0; illustIndex < elements.size(); illustIndex++){
String href = elements.get(illustIndex).attr("href");
int illustId = Integer.parseInt(href.substring(href.lastIndexOf("/") + 1));
log.info("({}/{}) Href: {}, IllustID: {}", illustIndex + 1, elements.size(), href, illustId);
List<String> pageLinkList = getIllustAllPageDownload(httpClient, this.cookieStore, illustId, quality);
for (int linkIndex = 0; linkIndex < pageLinkList.size(); linkIndex++) {
String link = pageLinkList.get(linkIndex);
String fileName = link.substring(link.lastIndexOf("/") + 1);
log.info("({}/{})正在处理 {}", linkIndex, pageLinkList.size(), fileName);
InputStream imageInputStream = null;
int tryCount = 0;
do {
try {
imageInputStream = getImageAsInputStream(imageClient, link);
} catch (IOException e) {
log.error("获取图片数据时发生异常", e);
if(++tryCount < 5) {
log.warn("即将重试[{} / 5]", tryCount);
}
}
} while(imageInputStream == null);
try(InputStream pageInputStream = new BufferedInputStream(imageInputStream, 256 * 1024)) {
fn.accept(fileName, pageInputStream);
}
log.info("Done!");
}
log.info("IllustId {} 处理完成.", illustId);
}
}
/**
* 获取排行榜
* @param contentType 内容类型
* @param mode 查询模式
* @param time 查询时间
* @param range 从第一名开始的范围
* @param quality 图片质量
* @param fn 回调函数
* @throws IOException 当请求发生异常时抛出
*/
public void getRankingAsInputStream(PixivURL.RankingContentType contentType, PixivURL.RankingMode mode,
Date time, int range, PageQuality quality, RankingDownloadFunction fn) throws IOException {
getRankingAsInputStream(contentType, mode, time, 1, range, quality, fn);
}
/**
* 获取排行榜
* @param contentType 内容类型
* @param mode 查询模式
* @param time 查询时间
* @param rankStart 开始排行位(包括)
* @param range 范围
* @param quality 图片质量
* @param fn 回调函数
* @throws IOException 当请求发生异常时抛出
*/
public void getRankingAsInputStream(PixivURL.RankingContentType contentType, PixivURL.RankingMode mode,
Date time, int rankStart, int range, PageQuality quality, RankingDownloadFunction fn) throws IOException {
getRanking(contentType, mode, time, rankStart, range).forEach(rankInfo -> {
int rank = rankInfo.get("rank").getAsInt();
int illustId = rankInfo.get("illust_id").getAsInt();
int authorId = rankInfo.get("user_id").getAsInt();
String authorName = rankInfo.get("user_name").getAsString();
String title = rankInfo.get("title").getAsString();
log.info("当前到第 {}/{} 名(总共 {} 名), IllustID: {}, Author: ({}) {}, Title: {}", rank, rankStart + range - 1, range, illustId, authorId, authorName, title);
log.info("正在获取PagesLink...");
List<String> linkList;
try {
linkList = getIllustAllPageDownload(httpClient, this.cookieStore, illustId, quality);
} catch (IOException e) {
if(e.getMessage().equals("該当作品は削除されたか、存在しない作品IDです。")) {
log.warn("作品 {} 不存在.", illustId);
} else {
e.printStackTrace();
}
return;
}
log.info("PagesLink 获取完成, 总数: {}", linkList.size());
for (int pageIndex = 0; pageIndex < linkList.size(); pageIndex++) {
String downloadLink = linkList.get(pageIndex);
log.info("当前Page: {}/{}", pageIndex + 1, linkList.size());
try(InputStream imageInputStream = new BufferedInputStream(getImageAsInputStream(HttpClientBuilder.create().build(), downloadLink), 256 * 1024)) {
fn.download(rank, downloadLink, rankInfo.deepCopy(), imageInputStream);
} catch(IOException e) {
log.error("下载插画时发生异常", e);
return;
}
log.info("完成.");
}
});
}
public List<JsonObject> getRanking(PixivURL.RankingContentType contentType, PixivURL.RankingMode mode,
Date time, int rankStart, int range) throws IOException {
if(rankStart <= 0) {
throw new IllegalArgumentException("rankStart cannot be less than or equal to zero");
}
if(range <= 0) {
throw new IllegalArgumentException("range cannot be less than or equal to zero");
}
int startPage = (int) Math.ceil(rankStart / 50F);
int requestFrequency = (int) Math.ceil((rankStart + (range - 1)) / 50F);
int surplusQuantity = range;
boolean firstRequest = true;
Gson gson = new Gson();
ArrayList<JsonObject> results = new ArrayList<>();
for (int requestCount = startPage; requestCount <= requestFrequency && requestCount <= 10; requestCount++) {
int rangeStart = (requestCount - 1) * 50 + 1;
log.info("正在请求第 {} 到 {} 位排名榜 (第{}次请求, 共 {} 次)", rangeStart, rangeStart + 49, requestCount - startPage + 1, requestFrequency - startPage);
HttpGet request = createHttpGetRequest(PixivURL.getRankingLink(contentType, mode, time, requestCount, true));
log.info("Request URL: {}", request.getURI());
HttpResponse response = httpClient.execute(request);
String content = EntityUtils.toString(response.getEntity());
log.debug("Content: " + content);
JsonObject contentObject = gson.fromJson(content, JsonObject.class);
if(contentObject.has("error")) {
log.warn("接口报错, 返回信息: {}", contentObject.get("error").getAsString());
break;
}
JsonArray rankingArray = contentObject.getAsJsonArray("contents");
log.info("正在解析数据...");
//需要添加一个总量, 否则会完整跑完一次.
//检查是否为最后一次请求,和剩余量有多少
int firstRequestStartIndex = (rankStart % 50) - 1;
for (int rankIndex = firstRequest ? firstRequestStartIndex : 0; rankIndex < rankingArray.size() && surplusQuantity > 0; rankIndex++, surplusQuantity--) {
JsonElement jsonElement = rankingArray.get(rankIndex);
JsonObject rankInfo = jsonElement.getAsJsonObject();
int rank = rankInfo.get("rank").getAsInt();
int illustId = rankInfo.get("illust_id").getAsInt();
int authorId = rankInfo.get("user_id").getAsInt();
String authorName = rankInfo.get("user_name").getAsString();
String title = rankInfo.get("title").getAsString();
log.info("当前到第 {}/{} 名(总共 {} 名), IllustID: {}, Author: ({}) {}, Title: {}", rank, rankStart + range - 1, range, illustId, authorId, authorName, title);
results.add(rankInfo);
}
firstRequest = false;
log.info("第 {} 到 {} 位排名榜完成. (第{}次请求)", rangeStart, rangeStart + 49, requestCount);
}
if(requestFrequency > 10) {
log.warn("请求的排名榜范围超出所支持的范围, 已终止请求.");
}
return results;
}
/**
* 获取作品的预加载数据
* @param illustId 作品id
* @return 如果请求成功返回JsonObject, 失败返回null
* @throws IOException 当请求响应非200或请求发生异常时抛出.
*/
public JsonObject getIllustPreLoadDataById(int illustId) throws IOException {
HttpGet request = createHttpGetRequest(PixivURL.getPixivRefererLink(illustId));
HttpResponse response = httpClient.execute(request);
if(response.getStatusLine().getStatusCode() != 200) {
throw new IOException("Http响应码非200: " + response.getStatusLine());
}
Document document = Jsoup.parse(EntityUtils.toString(response.getEntity()));
Elements selectElements = document.select("#meta-preload-data");
if(selectElements.size() == 0) {
return null;
}
return new Gson().fromJson(selectElements.attr("content"), JsonObject.class);
}
@FunctionalInterface
public interface RankingDownloadFunction {
/**
* 接收图片InputStream
* @param rank 当前作品排名
* @param link 作品下载链接
* @param inputStream 作品下载输入流, InputStream会自动关闭
*/
void download(int rank, String link, JsonObject rankInfo, InputStream inputStream);
}
public HttpGet createHttpGetRequest(String url) {
HttpGet request = new HttpGet(url);
setCookieInRequest(request, cookieStore);
return request;
}
/**
* 取Illust所有页的原图下载链接
* @param httpClient 用于发起请求的HttpClient对象
* @param illustId 插画ID
* @param quality 页质量, 见{@link PageQuality}
* @return 返回该illust所有Page的下载链接
* @throws IOException 当HttpClient在请求时发生异常, 或接口报错时抛出, 注意{@link IOException#getMessage()}
*/
public static List<String> getIllustAllPageDownload(HttpClient httpClient, CookieStore cookieStore, int illustId, PageQuality quality) throws IOException {
HttpGet linkApiRequest = new HttpGet(PixivURL.PIXIV_ILLUST_API_URL.replace("{illustId}", Integer.toString(illustId)));
setCookieInRequest(linkApiRequest, cookieStore);
HttpResponse response = httpClient.execute(linkApiRequest);
JsonObject resultObject = new Gson().fromJson(EntityUtils.toString(response.getEntity()), JsonObject.class);
if(resultObject.get("error").getAsBoolean()) {
String message = resultObject.get("message").getAsString();
log.info("请求错误, 错误信息: {}", message);
throw new IOException(message);
}
JsonArray linkArray = resultObject.getAsJsonArray("body");
ArrayList<String> resultList = new ArrayList<>();
String qualityType = quality == null ? "original" : quality.toString().toLowerCase();
log.info("已选择插画类型: {}", qualityType);
linkArray.forEach(el -> {
JsonObject urlObj = el.getAsJsonObject().getAsJsonObject("urls");
resultList.add(urlObj.get(qualityType).getAsString());
});
return resultList;
}
/**
* 插图质量
*/
public enum PageQuality{
/**
* 原图画质
*/
ORIGINAL,
/**
* 常规画质
*/
REGULAR,
/**
* 小图画质
*/
SMALL,
/**
* 迷你画质
*/
THUMB_MINI
}
/**
* 获取帐号所有的收藏插画,并以输入流形式提供
* @return 获取所有链接的InputStream, 请注意关闭InputStream
* @throws IOException 当获取时发生异常则直接抛出
*/
public Set<Map.Entry<String, InputStream>> getCollectionAsInputStream(PageQuality quality) throws IOException {
HashSet<Map.Entry<String, InputStream>> illustInputStreamSet = new HashSet<>();
getCollectionAsInputStream(quality, (link, inputStream) -> illustInputStreamSet.add(new AbstractMap.SimpleEntry<>(link, inputStream)));
return illustInputStreamSet;
}
/**
* 获取Pixiv图片
* @param httpClient HttpClient对象
* @param link Pixiv图片链接
* @return 返回图片InputStream注意关闭InputStream
* @throws IOException 获取失败时抛出
* @throws IllegalArgumentException 当链接无法处理时抛出
*/
public static InputStream getImageAsInputStream(HttpClient httpClient, String link) throws IOException {
HttpGet request = new HttpGet(link);
int startIndex = link.lastIndexOf("/");
int endIndex = link.lastIndexOf("_");
if(startIndex == -1 || endIndex == -1) {
throw new IllegalArgumentException("无法从链接获取illustID: " + link);
}
String referer = PixivURL.getPixivRefererLink(link.substring(startIndex + 1, endIndex));
request.addHeader(HttpHeaderNames.REFERER.toString(), referer);
HttpResponse response = httpClient.execute(request);
log.debug("response: {}", response);
log.info("Content Length: {}KB", Float.parseFloat(response.getFirstHeader(HttpHeaderNames.CONTENT_LENGTH.toString()).getValue()) / 1024F);
log.info("{}", response.getFirstHeader(HttpHeaderNames.CONTENT_TYPE.toString()));
return response.getEntity().getContent();
}
/**
* 登出当前会话.<br/>
* 登出成功后, 该Cookies作废.
* @return 返回是否成功登出
* @throws IOException 当登出请求异常时抛出
*/
public boolean logOut() throws IOException {
HttpGet request = new HttpGet(PixivURL.PIXIV_LOGOUT_URL);
request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build());
setCookieInRequest(request, cookieStore);
HttpResponse response = httpClient.execute(request);
if (response.getStatusLine().getStatusCode() == 302) {
cookieStore.clear();
return true;
} else {
return false;
}
}
/**
* 获取作品信息
* @param illustId 作品ID
* @return 成功获取返回JsonObject, 失败返回null, <br/>
* Json示例: <br/>
* <pre>
* {
* "illustId": "79584670",
* "illustTitle": "このヤンキーはウブすぎる",
* "id": "79584670",
* "title": "このヤンキーはウブすぎる",
* "illustType": 1,
* "xRestrict": 0,
* "restrict": 0,
* "sl": 2,
* "url": "https://i.pximg.net/c/360x360_70/img-master/img/2020/02/19/00/38/23/79584670_p0_square1200.jpg",
* "description": "",
* "tags": [
* "漫画",
* "オリジナル",
* "創作",
* "創作男女",
* "コロさん、ポリさん此方です!",
* "恋の予感",
* "あまずっぺー",
* "交換日記",
* "続編希望!!",
* "オリジナル10000users入り"
* ],
* "userId": "4778293",
* "userName": "隈浪さえ",
* "width": 3288,
* "height": 4564,
* "pageCount": 4,
* "isBookmarkable": true,
* "bookmarkData": null,
* "alt": "#オリジナル このヤンキーはウブすぎる - 隈浪さえ的漫画",
* "isAdContainer": false,
* "profileImageUrl": "https://i.pximg.net/user-profile/img/2019/12/04/18/56/19/16639046_fea29ce38ea89b0cb2313b40b3a72f9a_50.jpg",
* "type": "illust"
* }
* </pre>
* @throws IOException 当请求发生异常, 或接口返回错误信息时抛出.
*/
public JsonObject getIllustInfoByIllustId(int illustId) throws IOException {
HttpGet request = createHttpGetRequest(PixivURL.getPixivIllustInfoAPI(new int[] {illustId}));;
HttpResponse response = httpClient.execute(request);
String responseStr = EntityUtils.toString(response.getEntity());
log.debug("Response Content: {}", responseStr);
JsonObject responseObj = new Gson().fromJson(responseStr, JsonObject.class);
if(responseObj.get("error").getAsBoolean()) {
throw new IOException(responseObj.get("message").getAsString());
}
JsonArray illustsArray = responseObj.getAsJsonObject("body").getAsJsonArray("illusts");
if(illustsArray.size() == 1) {
return illustsArray.get(0).getAsJsonObject();
} else {
return null;
}
}
public static void setCookieInRequest(HttpRequest request, CookieStore cookieStore) {
StringBuilder builder = new StringBuilder();
cookieStore.getCookies().forEach(cookie -> builder.append(cookie.getName()).append("=").append(cookie.getValue()).append("; "));
request.setHeader(HttpHeaderNames.COOKIE.toString(), builder.toString());
}
}

View File

@ -0,0 +1,340 @@
package net.lamgc.cgj.pixiv;
import com.google.common.base.Strings;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Objects;
public class PixivSearchBuilder {
private final String content;
private SearchArea searchArea = SearchArea.ARTWORKS;
private SearchMode searchMode = SearchMode.TAG_FULL;
private SearchType searchType = SearchType.ILLUST_AND_UGOIRA;
private SearchOrder searchOrder = SearchOrder.DATE_D;
private SearchContentOption searchContentOption = SearchContentOption.ALL;
private HashSet<String> includeKeywords = new HashSet<>(0);
private HashSet<String> excludeKeywords = new HashSet<>(0);
private int page = 1;
private int wgt = 0;
private int hgt = 0;
private int wlt = 0;
private int hlt = 0;
private RatioOption ratioOption = null;
private Date startDate = null;
private Date endDate = null;
public PixivSearchBuilder(String searchContent) {
this.content = Objects.requireNonNull(searchContent);
}
public String buildURL() {
StringBuilder builder;
try {
builder = new StringBuilder(PixivURL.PIXIV_SEARCH_CONTENT_URL.replaceAll("\\{area}", searchArea.name().toLowerCase())
.replaceAll("\\{content}",
URLEncoder.encode(getSearchCondition(), "UTF-8").replaceAll("\\+", "%20")
)
);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
if(searchArea.equals(SearchArea.TOP)) {
return builder.toString();
}
builder.append("&s_mode=").append(searchMode.name().toLowerCase());
builder.append("&type=").append(searchType.name().toLowerCase());
builder.append("&p=").append(page);
builder.append("&order=").append(searchOrder.name().toLowerCase());
builder.append("&mode=").append(searchContentOption.name().toLowerCase());
//可选参数
if(wgt > 0 && hgt > 0) {
builder.append("&wgt=").append(wgt);
builder.append("&hgt").append(hgt);
}
//可选参数
if(wlt > 0 && hlt > 0) {
builder.append("&wlt=").append(wlt);
builder.append("&hlt").append(hlt);
}
if (ratioOption != null) {
builder.append("&ratio=").append(ratioOption.value);
}
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
if (startDate != null) {
builder.append("&scd=").append(format.format(startDate));
}
if (endDate != null) {
builder.append("&ecd=").append(format.format(endDate));
}
return builder.toString();
}
public PixivSearchBuilder setSearchArea(SearchArea searchArea) {
this.searchArea = searchArea;
return this;
}
/**
* 获取搜索区域
* @return 返回搜索区域对象
*/
public SearchArea getSearchArea() {
return searchArea;
}
/**
* 获取搜索条件.
* @return 搜索条件内容
*/
public String getSearchCondition() {
StringBuilder searchContent = new StringBuilder(Strings.nullToEmpty(this.content));
if(searchArea.equals(SearchArea.TOP)) {
return searchContent.toString();
}
excludeKeywords.forEach(keyword -> searchContent.append(" -").append(keyword));
if(!includeKeywords.isEmpty()) {
if(!Strings.isNullOrEmpty(searchContent.toString())) {
searchContent.append(" (");
}
includeKeywords.forEach(keyword -> searchContent.append(keyword).append(" OR "));
int deleteStart = searchContent.lastIndexOf(" OR ");
if(searchContent.length() >= 4 && deleteStart != -1) {
searchContent.delete(deleteStart, searchContent.length());
}
if(!Strings.isNullOrEmpty(searchContent.toString())) {
searchContent.append(")");
}
}
return searchContent.toString();
}
public PixivSearchBuilder setSearchMode(SearchMode searchMode) {
this.searchMode = Objects.requireNonNull(searchMode);
return this;
}
public PixivSearchBuilder setSearchType(SearchType searchType) {
this.searchType = Objects.requireNonNull(searchType);
return this;
}
public PixivSearchBuilder setSearchOrder(SearchOrder searchOrder) {
this.searchOrder = Objects.requireNonNull(searchOrder);
return this;
}
public PixivSearchBuilder setSearchContentOption(SearchContentOption searchContentOption) {
this.searchContentOption = Objects.requireNonNull(searchContentOption);
return this;
}
public PixivSearchBuilder setRatioOption(RatioOption ratioOption) {
this.ratioOption = Objects.requireNonNull(ratioOption);
return this;
}
public PixivSearchBuilder setDateRange(Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
return this;
}
public PixivSearchBuilder setMaxSize(int width, int height) {
this.wgt = width;
this.hgt = height;
return this;
}
public PixivSearchBuilder setMinSize(int width, int height) {
this.wlt = width;
this.hlt = height;
return this;
}
public PixivSearchBuilder setPage(int pageIndex) {
if (pageIndex <= 0) {
throw new IllegalArgumentException("Invalid pageIndex: " + pageIndex);
}
this.page = pageIndex;
return this;
}
public PixivSearchBuilder addExcludeKeyword(String keyword) {
excludeKeywords.add(keyword);
return this;
}
public PixivSearchBuilder removeExcludeKeyword(String keyword) {
excludeKeywords.remove(keyword);
return this;
}
public PixivSearchBuilder addIncludeKeyword(String keyword) {
includeKeywords.add(keyword);
return this;
}
public PixivSearchBuilder removeIncludeKeyword(String keyword) {
includeKeywords.remove(keyword);
return this;
}
/**
* 搜索区域
*/
public enum SearchArea {
/**
* 所有(可能是 插画 + 漫画)
*/
ARTWORKS("illustManga"),
/**
* 顶部(所有内容)
* 同时包含了:
* {@link #ILLUSTRATIONS}
* {@link #MANGA}
* {@link #NOVELS}
* 选择此项后, 将直接显示所有与content相关内容, 而忽略所有附加搜索条件.
* 因为无法指定pageIndex, 数据只有24项
*/
TOP(null),
/**
* 插画
*/
ILLUSTRATIONS("illust"),
/**
* 漫画
*/
MANGA("manga"),
/**
* 小说
*/
NOVELS("novel");
public final String jsonKey;
SearchArea(String jsonKey) {
this.jsonKey = jsonKey;
}
}
/**
* 搜索模式
*/
public enum SearchMode {
/**
* 按标签搜索, 部分一致
*/
TAG,
/**
* 按标签搜索, 完全一致
*/
TAG_FULL,
/**
* 按标题和说明文字搜索
*/
TC
}
/**
* 搜索内容类型
*/
public enum SearchType {
/**
* 全部内容(插画、漫画、动图)
*/
ALL,
/**
* 插画和动图(不包括漫画)
*/
ILLUST_AND_UGOIRA,
/**
* 插图
*/
ILLUST,
/**
* 漫画
*/
MANGA,
/**
* 动图
*/
UGOIRA
}
public enum SearchOrder {
/**
* 按旧排序
*/
DATE,
/**
* 按新排序
*/
DATE_D
}
/**
* 搜索内容选项
*/
public enum SearchContentOption {
/**
* 所有内容
*/
ALL,
/**
* 全年龄
*/
SAFE,
/**
* R18
*/
R18
}
public enum RatioOption {
/**
* 横向
*/
TRANSVERSE(0.5F),
/**
* 纵向
*/
PORTRAIT(-0.5F),
/**
* 正方形
*/
SQUARE(0F)
;
public final float value;
RatioOption(float ratio) {
this.value = ratio;
}
}
}

View File

@ -0,0 +1,171 @@
package net.lamgc.cgj.pixiv;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class PixivSession {
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36";
/**
* 全登陆过程的关键,
* 保存会话用的cookieStore!
*/
private CookieStore cookieStore = new BasicCookieStore();
/**
* 可以直接使用的HttpClient对象
*/
private HttpClient httpClient;
/**
* 最后一次登录的错误信息
*/
private String errMsg;
public PixivSession(){
this(null);
}
public PixivSession(CookieStore cookieStore){
this(null, cookieStore);
}
/**
* 创建一个Pixiv登录会话
*/
public PixivSession(HttpHost proxy, CookieStore cookieStore) {
if(cookieStore != null){
this.cookieStore = cookieStore;
}
List<Header> defaultHeader = new ArrayList<>();
defaultHeader.add(new BasicHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"));
defaultHeader.add(new BasicHeader("user-agent", PixivSession.USER_AGENT));
defaultHeader.add(new BasicHeader("accept-encoding", "gzip, deflate, br"));
defaultHeader.add(new BasicHeader("accept-language", "zh-CN,zh;q=0.9"));
/*defaultHeader.add(new BasicHeader("sec-fetch-mode", "navigate"));
defaultHeader.add(new BasicHeader("sec-fetch-site", "same-origin"));
defaultHeader.add(new BasicHeader("upgrade-insecure-requests", "1"));*/
//创建一个Http访问器
httpClient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.setDefaultHeaders(defaultHeader)
.setProxy(proxy)
.build();
}
/**
* 程序自行通过帐号密码登录Pixiv.
* @param PixivID Pixiv帐号
* @param Password Pixiv密码
* @return 登录成功返回true
* @throws IOException 当登录抛出异常时返回
* @deprecated Pixiv已经新增Google人机验证, 程序已无法自行登录Pixiv
*/
public boolean Login(String PixivID, String Password) throws IOException {
// 获取登录接口所需的PostKey
String post_key = getPostKey();
HttpPost postRequest = new HttpPost(PixivURL.PIXIV_LOGIN_URL); //https://accounts.pixiv.net/api/login?lang=zh
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("pixiv_id", PixivID));
params.add(new BasicNameValuePair("password", Password));
params.add(new BasicNameValuePair("post_key", post_key));
//Form编码表单,作为Post的数据
postRequest.setEntity(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
//启动访问
HttpResponse response = httpClient.execute(postRequest);
//获取接口返回数据
String httpXML = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
System.out.println(httpXML);
JsonObject responseJson = (JsonObject) new JsonParser().parse(httpXML);
if(!responseJson.get("error").getAsBoolean() && !responseJson.get("body").getAsJsonObject().has("validation_errors")){
errMsg = null;
return true;
}else{
errMsg = responseJson.get("body").getAsJsonObject().get("validation_errors").toString();
//System.err.println("登录失败MSG: " + errMsg);
return false;
}
}
/**
* 登录前准备, 获取PostKey
* @return Post_Key
*/
private String getPostKey() throws IOException {
//创建请求,获取PostKey
HttpGet getRequest = new HttpGet(PixivURL.PIXIV_LOGIN_PAGE_URL);
//设置请求
//getRequest.setConfig(config);
getRequest.setHeader("User-Agent", USER_AGENT);
//启动访问
HttpResponse response = httpClient.execute(getRequest);
//获取网页内容
String pageAsXML = EntityUtils.toString(response.getEntity(),"utf-8");
//创建Http解析器
Document document = Jsoup.parse(pageAsXML);
//获取init-config内容
String init_config = document.getElementById("init-config").val();
//System.out.println(init_config);
//创建Json解析器解析init-config
JsonObject initConfigObj = (JsonObject) new JsonParser().parse(init_config);
//检查是否有postKey
if(!initConfigObj.has("pixivAccount.postKey")){
throw new RuntimeException("postKey获取失败!可能是Pixiv修改了登录过程!");
}
//获取postKey
return initConfigObj.get("pixivAccount.postKey").getAsString();
}
/**
* 获取CookieStore
* @return CookieStore
*/
public CookieStore getCookieStore(){
return cookieStore;
}
/**
* 获取可直接使用的HttpClient对象
* @return 已配置好的HttpClient对象
*/
public HttpClient getHttpClient(){
return this.httpClient;
}
public boolean hasError(){
return errMsg == null;
}
/**
* 获取错误信息
* @return 返回登录错误信息
* @deprecated {@link #Login(String, String)}已经废除, 故本接口废除
*/
public String getErrMsg(){
return errMsg;
}
}

View File

@ -0,0 +1,16 @@
package net.lamgc.cgj.pixiv;
public class PixivTag {
public static PixivTag create(String tagName) {
return null;
}
private PixivTag() {
}
}

View File

@ -0,0 +1,290 @@
package net.lamgc.cgj.pixiv;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* 目前已整理的一些Pixiv接口列表
*/
public class PixivURL {
public static final String PIXIV_INDEX_URL = "https://www.pixiv.net";
/**
* P站预登陆url
*/
public static final String PIXIV_LOGIN_PAGE_URL = "https://accounts.pixiv.net/login?lang=zh&source=pc&view_type=page&ref=wwwtop_accounts_index";
/**
* P站登录请求url
*/
public static final String PIXIV_LOGIN_URL = "https://accounts.pixiv.net/api/login?lang=zh";
/**
* P站搜索请求url
*/
private static final String PIXIV_SEARCH_URL = "https://www.pixiv.net/search.php";
/**
* P站搜索用户url
* 需要替换的参数:
* {nick} - 用户昵称、部分名称
*/
public static final String PIXIV_SEARCH_USER_URL = PIXIV_SEARCH_URL + "?s_mode=s_usr&nick={nick}";
/**
* P站搜索插画url
* 需要替换的参数:
* {word} - 插画相关文本
*/
public static final String PIXIV_SEARCH_TAG_URL = PIXIV_SEARCH_URL + "?s_mode=s_tag&word={word}";
/**
* P站插图下载链接获取url
* 需要替换的文本:
* {illustId} - 插画ID
*/
public static final String PIXIV_ILLUST_API_URL = "https://www.pixiv.net/ajax/illust/{illustId}/pages";
/*
* P站用户插图列表获取API
* 需要替换的文本:
* {userId} - 用户ID
*/
//TODO: 所需数据在 body属性内的 illusts(属性名,属性值不重要), manga(多图) pickup(精选)
//{"error":false,"message":"","body":{"illusts":{"74369837":null,"70990542":null,"70608653":null,"69755191":null,"69729450":null,"69729416":null,"69503608":null,"69288766":null,"69083882":null,"69051458":null,"68484200":null,"68216927":null,"68216866":null,"68192333":null,"67915106":null,"67914932":null,"67854803":null,"67854745":null,"67854670":null,"67787211":null,"67772199":null,"67770637":null,"67754861":null,"67754804":null,"67754726":null,"67740486":null,"67740480":null,"67740450":null,"67740434":null,"67726337":null,"67499196":null,"67499163":null,"67499145":null,"67499111":null,"67499085":null,"67499038":null,"67498987":null,"67473178":null,"66271465":null,"63682753":null,"63682697":null,"59385148":null,"59383265":null,"59383240":null,"59383227":null,"59383173":null},"manga":[],"novels":[],"mangaSeries":[],"novelSeries":[],"pickup":[],"bookmarkCount":{"public":{"illust":1,"novel":0},"private":{"illust":0,"novel":0}}}}
//public static final String PIXIV_USER_ILLUST_LIST_URL = "https://www.pixiv.net/ajax/user/{userId}/profile/all";
/**
* 能够同时获取插图信息的用户插图列表获取API
*/
public static final String PIXIV_USER_ILLUSTINFO_LIST_URL = "https://www.pixiv.net/ajax/user/{userId}/profile/top";
/**
* P站单图详情页url
* 需要替换的文本:
* {illustId} - 插画ID
*/
public static final String PIXIV_ILLUST_MEDIUM_URL = "https://www.pixiv.net/member_illust.php?mode=medium&illust_id={illustId}";
/**
* P站多图详情页url
* 需要替换的文本:
* {illustId} - 插画ID
*/
public static final String PIXIV_ILLUST_MANGA_URL = "https://www.pixiv.net/member_illust.php?mode=manga&illust_id={illustId}";
/**
* P站用户页面url
* 需要替换的文本:
* {userId} - 用户ID
*/
public static final String PIXIV_USER_URL = "https://www.pixiv.net/member.php?id={userId}";
/**
* P站插图信息获取API
* 这个API能获取插图基本信息但不能获取大小
* 请使用{@link #getPixivIllustInfoAPI(int[])}获取URL
*/
private static final String PIXIV_GET_ILLUST_INFO_URL = "https://www.pixiv.net/ajax/illust/recommend/illusts?";
/**
* P站获取用户所有插图ID的Api
* 这个API只能获取该用户的插图ID不能获取图片信息(图片信息要另外获取)
* 需要替换的文本:
* {userId} - 用户ID
*/
public static final String PIXIV_GET_USER_ALL_ILLUST_ID_URL = "https://www.pixiv.net/ajax/user/{userId}/profile/all";
/**
* P站标签搜索URL
* 可以将Tag的大概内容搜索成P站精确的Tag以搜索其他接口返回的Tags数组;
* 需要替换的文本:
* {content} - 大致tag内容
*/
public static final String PIXIV_TAG_SEARCH_URL = "https://www.pixiv.net/ajax/search/tags/{content}";
/**
* 请求时带上需要退出的Cookies
* 无论成功与否都会返回302重定向到{@linkplain #PIXIV_LOGIN_PAGE_URL 登录页面}
*/
public static final String PIXIV_LOGOUT_URL = "https://www.pixiv.net/logout.php";
/**
* 构造P站获取插图信息的Api Url
* @param illustIds 要查询的插图ID数组
* @return 对应查询的API Url
*/
public static String getPixivIllustInfoAPI(int[] illustIds){
StringBuilder strBuilder = new StringBuilder().append(PIXIV_GET_ILLUST_INFO_URL);
for(int illustId : illustIds){
strBuilder.append("illust_ids[]=").append(illustId).append("&");
}
return strBuilder.toString();
}
/**
* 获取用于下载图片时防盗链所需Referer的链接
* @param illustId 欲下载图片所属illustId
* @return 返回Referer链接, 也可以作为作品链接使用
*/
public static String getPixivRefererLink(int illustId){
return "https://www.pixiv.net/artworks/" + illustId;
}
/**
* 获取用于下载图片时防盗链所需Referer的链接
* @param illustId 欲下载图片所属illustId
* @return 返回Referer链接, 也可以作为作品链接使用
*/
public static String getPixivRefererLink(String illustId){
return "https://www.pixiv.net/artworks/" + illustId;
}
/**
* 排行榜接口, 需加入"&format=json"
*/
private final static String PIXIV_RANKING_LINK = "https://www.pixiv.net/ranking.php?";
/**
* 查询用户收藏.<br/>
* 该URL返回HTML页面需要进行解析.<br/>
* 需要替换的文本:<br/>
* {pageIndex} - 页数, 超出了则结果为空<br/>
*/
public final static String PIXIV_USER_COLLECTION_PAGE = "https://www.pixiv.net/bookmark.php?rest=show&p={pageIndex}";
/**
* 获取排名榜
* @param mode 查询类型, 详细信息看{@link RankingMode}, 如本参数为null, 则为每天
* @param contentType 排名榜类型, 如为null则为综合
* @param time 欲查询的时间, 最新只能查询昨天, 根据mode不同:
* 每天 - 查询指定日期的排名榜
* 每周 - 查询指定时间结束(含)到七天前一段时间内的排名榜
* 每月 - 查询指定日期结束(含)到28天时间内的排名榜
* 新人 - 与每周相同
* 受男性欢迎 - 与每天相同
* 受女性欢迎 - 与每天相同
* 默认值为昨天
* @param pageIndex 页数一页50位总共10页
* @return 返回构建好的链接
*/
public static String getRankingLink(RankingContentType contentType, RankingMode mode, Date time, int pageIndex, boolean json){
StringBuilder linkBuilder = new StringBuilder(PIXIV_RANKING_LINK);
linkBuilder.append("mode=").append(mode == null ? RankingMode.MODE_DAILY.modeParam : mode.modeParam);
if(contentType != null){
linkBuilder.append("&content=").append(contentType.typeName);
}
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date queryDate;
if(time == null){
GregorianCalendar gregorianCalendar = new GregorianCalendar();
gregorianCalendar.setTime(new Date());
gregorianCalendar.add(Calendar.DATE, -1);
queryDate = gregorianCalendar.getTime();
} else {
queryDate = time;
}
linkBuilder.append("&date=").append(format.format(queryDate));
if(pageIndex > 0 && pageIndex <= 10) {
linkBuilder.append("&p=").append(pageIndex);
}
if(json) {
linkBuilder.append("&format=").append("json");
}
return linkBuilder.toString();
}
/**
* 排名榜模式
*/
public enum RankingMode{
/**
* 每天
*/
MODE_DAILY("daily"),
/**
* 每周
*/
MODE_WEEKLY("weekly"),
/**
* 每月
*/
MODE_MONTHLY("monthly"),
/**
* 新人
*/
MODE_ROOKIE("rookie"),
/**
* 受男性喜欢
*/
MODE_MALE("male"),
/**
* 受女性喜欢
*/
MODE_FEMALE("female"),
/**
* 每天 - R18
*/
MODE_DAILY_R18("daily_r18"),
/**
* 每周 - R18
*/
MODE_WEEKLY_R18("weekly_r18"),
/**
* 受男性喜欢 - R18
*/
MODE_MALE_R18("male_r18"),
/**
* 受女性喜欢 - R18
*/
MODE_FEMALE_R18("female_r18"),
;
public String modeParam;
RankingMode(String modeParamName){
this.modeParam = modeParamName;
}
}
/**
* Pixiv搜索接口.<br/>
* 要使用该链接请使用{@link PixivSearchBuilder}构造链接.<br/>
* 需要替换的参数: <br/>
* content - 搜索内容
*/
final static String PIXIV_SEARCH_CONTENT_URL = "https://www.pixiv.net/ajax/search/{area}/{content}?word={content}";
/**
* 排名榜类型
*/
public enum RankingContentType{
/**
* 插画
* 支持的时间类型: 每天, 每周, 每月, 新人
*/
TYPE_ILLUST("illust"),
/**
* 动图
* 支持的时间类型:每天, 每周
*/
TYPE_UGOIRA("ugoira"),
/**
* 漫画
* 支持的时间类型: 每天, 每周, 每月, 新人
*/
TYPE_MANGA("manga")
;
String typeName;
RankingContentType(String typeName){
this.typeName = typeName;
}
}
}