English 中文
一个用于获取 PicaComic (哔咔漫画) 数据的 Java API 库
请注意: 本项目目前正处于积极的开发和测试阶段。API 可能会在未来的版本中发生不兼容的变更。
Please note: This project is currently in an active development and testing phase. The API may undergo incompatible changes in future versions.
| 额外功能 | 实现情况 |
|---|---|
| API/image 网络边界 | ✅ 双 OkHttp client、API host 健康探测与重试 |
| 图片并发边界 | ✅ 每 client 公平 semaphore,由配置驱动 |
| 原子图片落盘 | ✅ 同目录临时文件 + ATOMIC_MOVE |
获取并解析哔咔漫画 (PicaComic) 的基础实体信息。
- 获取本子详情 (
getAlbum):获取漫画的标题、作者、标签、简介等详细信息。 - 获取章节详情 (
getPhoto):获取指定章节下的所有图片列表。 - 获取图片数据 (
fetchImageBytes):根据图片信息直接拉取二进制数据。 - 漫画搜索 (
search):支持按关键字和指定条件(通过SearchQuery)进行分页搜索。 - 获取收藏夹 (
getFavorites):获取当前登录用户收藏的漫画列表。 - 获取分类数据 (
getCategories):按分类获取漫画列表内容。 - 获取排行榜 (
getLeaderboard):支持根据不同时间范围(TimeOption)获取漫画排行榜。 - 获取骑士榜 (
getKnightLeaderboard):获取社区贡献排名(骑士榜)。 - 获取随机本子 (
getRandomAlbums):获取一组随机推荐的漫画。 - 获取用户信息 (
getUserInfo):获取当前登录账户的详细信息。
管理 PicaComic 客户端的身份认证与会话状态。
- 用户登录 (
login):在一个原子请求中完成 sign-in 与 profile,只有拿到完整稳定用户 ID 才进入已登录状态。
内置高度灵活且支持并发的下载工具。
- 图片下载 (
downloadImage):- 支持直接下载单张图片到默认路径。
- 支持通过传入 URL 或图片实体进行精确下载。
- 支持利用
IImagePathGenerator策略接口自定义图片存储路径。
- 章节下载 (
downloadPhoto):- 支持并发下载整个章节内的所有图片。
- 提供默认路径或自定义路径生成器 (
IPhotoPathGenerator)。 - 允许外部传入自定义的
ExecutorService线程池以管理并发资源。 - 返回统一的
DownloadResult分析报告。
- 全本下载 (
downloadAlbum):- 支持全自动、高并发下载整本漫画的所有章节。
- 提供默认路径或自定义路径生成器 (
IAlbumPathGenerator)。 - 允许外部传入自定义的
ExecutorService线程池。 - 返回统一的
DownloadResult记录成功和失败的任务细节。
本项目采用模块化设计,将 公共接口(API) 与 核心实现(Core) 分离。
此模块定义了库的公共契约,不包含第三方网络库依赖,可独立集成。
- 领域模型: 提供 record 数据对象,用于描述
PicaAlbum(本子),PicaPhoto(章节),PicaImage(图片),PicaContentPage(分页内容),PicaUserInfo(用户信息) 等核心实体;公开集合保持可修改,client 内部会递归复制以隔离缓存和会话。 - 客户端接口 (
IPicaClient): 抽象并统一了所有业务操作,包括实体获取 (getAlbum,getPhoto)、列表查询 (search,getCategories,getFavorites)、排行榜查询 (getLeaderboard,getKnightLeaderboard)、用户会话 (login) 和下载 (downloadAlbum,downloadPhoto等)。 - 策略接口: 定义了如
IAlbumPathGenerator,IPhotoPathGenerator等策略接口,允许调用者注入自定义逻辑来控制文件存储等外部交互行为。
此模块包含了所有功能的具体实现逻辑,处理与 PicaComic 服务端的直接交互。
- 客户端实现:
- API 请求: 通过封装 OkHttp 调用 PicaComic 移动端 API 进行数据交互。
- 会话管理: 管理
SIGNED_OUT、AUTHENTICATING、SIGNED_IN与EXPIRED状态;凭据仅存在于当前 client 进程,不保存密码,也不自动恢复。 - 结构化错误: API 失败统一提供
PicaApiException.Reason、最终 HTTP status、provider code 和Retry-After,异常 message 与 cause 不包含响应体、URL、headers 或凭据。
- 网络处理:
- API/image 分离: 每个
IPicaClient独占 API 与图片两只 OkHttp client。图片 client 不带 Cookie、Token、签名或 API interceptor。 - 安全重试: API 请求在配置的 host 池内按健康分数选择目标,并在 I/O、
403或任意5xx时按retryTimes有界重试;GET 与 POST 使用相同规则。API 使用 OkHttp 默认 redirect 行为。 - 图片来源: 图片 URL 由模型或调用方提供并交给 OkHttp 解析;图片 client 不带 API 凭据,使用默认 redirect,接受成功的
2xxbody 和标准 gzip。
- API/image 分离: 每个
- 并发下载:
- 提供了
downloadAlbum和downloadPhoto等高级方法,叶子图片任务使用调用者提供或 client 自有的ExecutorService。 - 批量下载操作返回
DownloadResult对象,其中包含了成功与失败任务的详细报告。 - 图片单次请求通过
PicaImageRequest.execute/cancel/close保持同步 API,同时允许页面级取消。图片读取不设置库级字节或聚合内存上限;默认图片并发为 20,只接受正数配置。 - 下载文件先完整写入同目录
.part文件,再尝试ATOMIC_MOVE;文件系统不支持时使用REPLACE_EXISTING,失败或取消都会清理临时文件。
- 提供了
本项目的核心是一个数据获取与管理工具,而非一个功能固化的下载应用。其设计基于以下原则:
- 控制权移交: 库本身不负责具体的线程调度和文件 I/O 的硬编码。调用者可以通过注入
ExecutorService和自定义PathGenerator等策略接口,完全掌控并发行为和文件存储逻辑。 - 过程透明化: API 调用链 (
Album→Photo→Image) 中的每一个中间数据模型都是可访问的。这允许开发者在下载过程的任何阶段进行检查、过滤或自定义处理。 - 为集成而设计: 库的目标是作为一个可靠的底层模块,被轻松地集成到其他大型应用中,如 Android App、桌面工具或后端服务。
本项目尚未发布到 Maven 中央仓库。您可以通过以下方式在本地使用:
-
克隆本仓库:
git clone https://github.com/JUKOMU/PicaComic-Api-Java.git cd PicaComic-Api-Java -
在本地 Maven 仓库中安装:
mvn clean install
-
在您的
pom.xml文件中添加依赖:<dependency> <groupId>io.github.jukomu</groupId> <artifactId>picacomic-core</artifactId> <version>0.0.1</version> </dependency>
注意:与部分漫画平台不同,获取 PicaComic 的大部分数据必须先进行用户登录。
以下是一个基础下载器的完整示例:
package io.github.jukomu.picacomic.sample;
import io.github.jukomu.picacomic.api.client.DownloadResult;
import io.github.jukomu.picacomic.api.client.IPicaClient;
import io.github.jukomu.picacomic.api.client.PicaRequest;
import io.github.jukomu.picacomic.api.exception.PicaApiException;
import io.github.jukomu.picacomic.api.model.PicaAlbum;
import io.github.jukomu.picacomic.api.model.PicaUserInfo;
import io.github.jukomu.picacomic.core.PicaComic;
import io.github.jukomu.picacomic.core.config.PicaConfiguration;
/**
* @author JUKOMU
* @Description: 下载器示例
*/
public class DownloaderSample {
public static void main(String[] args) {
// 1. 配置并构建 Client
PicaConfiguration config = new PicaConfiguration.Builder().build();
try (IPicaClient client = PicaComic.newApiClient(config)) {
// 2. 登录账户;成功结果一定包含完整 profile 和稳定 ID
System.out.println("Logging in...");
PicaUserInfo user = client.login("your_username_or_email", "your_password");
System.out.println("Signed in as: " + user.getId());
// 3. 下载指定的本子
downloadAlbumWithAllPhotos(client, "album_id_here");
} catch (PicaApiException e) {
System.err.println("Pica API failed: " + e.getReason());
}
}
private static void downloadAlbumWithAllPhotos(IPicaClient client, String albumId) {
// 获取本子的信息
PicaAlbum album = client.getAlbum(albumId);
System.out.println("Downloading album: " + album.getTitle() + " ...");
// 开始并发下载
DownloadResult result = client.downloadAlbum(album);
// 处理下载结果
if (result.isAllSuccess()) {
System.out.println("Download complete! All " + result.getSuccessfulFiles().size() + " images saved.");
} else {
System.out.println("Download partially complete.");
System.out.println("Success: " + result.getSuccessfulFiles().size());
System.out.println("Failed: " + result.getFailedTasks().size());
result.getFailedTasks().forEach((image, error) ->
System.err.println(" - Failed to download " + image.getOriginalName() + ": " + error.getMessage())
);
}
}
}通过 PicaConfiguration.Builder,你可以精细化地控制网络请求、并发模型及图片质量等参数。
import java.time.Duration;
import io.github.jukomu.picacomic.core.config.PicaConfiguration;
// import io.github.jukomu.picacomic.api.enums.ImageQuality;
PicaConfiguration config = new PicaConfiguration.Builder()
.proxy("127.0.0.1", 7890) // 可选:调用者显式提供的 HTTP 代理
.timeout(Duration.ofSeconds(60)) // 设置网络超时为60秒
.retryTimes(5) // 设置最大重试次数
.imageTimeout(Duration.ofSeconds(60)) // 图片读取超时
.downloadThreadPoolSize(12) // 设置内部下载线程池大小
.concurrentPhotoDownloads(3) // 设置同时下载的章节数
.concurrentImageDownloads(20) // 设置同时读取图片的数量(必须为正数)
// .imageQuality(ImageQuality.ORIGINAL) // 设置下载的图片质量
.build();downloadThreadPoolSize 默认值为 12,concurrentPhotoDownloads 默认值为 3,concurrentImageDownloads 默认值为 20;三者都必须为正数,且没有库级硬上限。imageTimeout 默认值为 60 秒,只用于图片读取。使用 loadFromProperties 时并发键为 download.thread.pool.size、concurrent.photo.downloads 和 concurrent.image.downloads,图片超时键为 image.timeout.seconds。
默认 API host 池包含 picacomic.com、picaapi.go2778.com、picaapi.acbbb.com 和 picaapi.picacomic.com;通过 domains(...) 提供的 host 会替代默认池,并在首次 API 请求前探测。配置的 host 被视为等价 mirror,库不从网络响应发现新的 host。
代理只作用于该配置创建的 client,可能观察网络元数据;库不会因失败自动启用或切换代理。API host 会在首次 API 请求前进行 HEAD 探测,并按固定周期重新探测。图片请求是同步的空白 GET,不会继承 API 的 Cookie、Token、签名或请求体;配置的 closeTimeoutMs 控制 client 关闭时等待自有下载任务的时间。
fetchImageBytes 是阻塞便利方法;需要取消时使用 PicaImageRequest request = client.newImageRequest(image),在自己的 executor 中调用 request.execute(),并在页面销毁时调用 request.cancel()/request.close()。返回的 byte[] 在便利方法返回后归调用者所有,库不限制调用者长期保留它。
API 也提供相同的单次同步句柄。需要取消带分页或多步骤的操作时,创建 PicaRequest<T> 并在自己的 executor 中调用 execute();cancel() 会停止当前及后续 page、mirror 或登录 profile 请求。同步便利方法仍可直接使用:
PicaRequest<PicaAlbum> request = client.newAlbumRequest("album_id_here");
try {
PicaAlbum album = request.execute();
} finally {
request.close();
}登录后可通过 client.getSession() 观察不含 token/cookie/password 的进程会话快照;client.logout() 是本地幂等操作,之后可以重新登录。最终 401 会把会话置为 EXPIRED 并清除凭据,403 不会被猜测为过期。
章节缓存使用稳定 chapterId,不使用 (albumId, order) 作为 full photo 身份。需要权威数据时使用 refreshAlbum 或 refreshPhoto;章节插入、重排和删除会分别返回新的稳定身份、STALE_RESOURCE 或 NOT_FOUND,不会将旧章节的 pages 混入新章节。
图片失败统一抛出 ImageFetchException,可通过 getReason() 判断 INVALID_SOURCE、HTTP_STATUS、INVALID_CONTENT、TRUNCATED_BODY、TIMEOUT、CANCELLED 或 CLIENT_CLOSED 等稳定原因。README 中的示例凭据仅为占位文本;本项目的测试只访问本地 TLS fixture,不访问真实服务。
如果不希望使用默认的文件保存路径,您可以自定义实现路径生成策略:
import io.github.jukomu.picacomic.api.strategy.IAlbumPathGenerator;
import java.nio.file.Path;
IAlbumPathGenerator generator = new IAlbumPathGenerator() {
@Override
public Path generatePath(PicaAlbum album) {
// 示例:将本子存储到 "作者\标题_ID" 的特定目录
return Path.of("MyDownloads",
cleanFileName(album.getAuthor()),
cleanFileName(album.getTitle()) + "_" + album.getId());
}
private String cleanFileName(String name) {
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
}
};
// 传入策略进行下载
client.downloadAlbum(album, generator);如果您在更复杂的应用中(例如 Spring Boot 或 Android)已经拥有全局的线程池,可以直接注入以复用资源:
// 创建并管理你自己的线程池
ExecutorService myExecutor = Executors.newFixedThreadPool(16);
try {
// 将线程池注入到下载方法中
DownloadResult result = client.downloadAlbum(album, pathGenerator, myExecutor);
} finally {
// 在应用退出时,由您自己负责关闭线程池
myExecutor.shutdown();
}欢迎任何形式的贡献!如果您发现了 BUG 或有新的功能建议,请随时提交 Issues。
如果您想贡献代码,请先 Fork 本项目,在您的分支上进行修改,然后提交 Pull Request。
本项目基于 MIT License 开源。