正在查看 5 个帖子:1-5 (共 5 个帖子)
  • 作者
    帖子
  • @214530 回复 ⚑举报 

    野草
    游客

    http://www.shuge.org/meet/...ic/214292/

    今天看到有书友询问该网址的书怎么下载,抱着好奇的态度便探索了一番。

    media.hkpl.gov.hk/api/d...6647617538

    我们可以看到这个load?id=xxxx,指向的确实是一个pdf文档,但是我们正常情况下打开是显示:This page is not allowed to be this website.

    推测可能是有某种检测或者冲突,直接打开缺少必要条件是浏览不了的。

    写不下去了,个人的思路是编写一个油猴脚本。

    // ==UserScript==
    // @name HKPL PDF Parallel Downloader
    // @namespace hkpl-pdf-parallel-downloader
    // @version 0.5
    // @match sls.hkpl.gov.hk/digit...ewer.html*
    // @run-at document-idle
    // @grant none
    // ==/UserScript==

    (function () {
    'use strict';

    const CONCURRENCY = 16; // 并发数,可改 8 / 16 / 24
    const CHUNK_MB = 4; // 每个分片大小,可改 4 / 8 / 16

    function getPdfUrl() {
    return new URLSearchParams(location.search).get('file');
    }

    function filenameFromUrl(url) {
    const u = new URL(url);
    return hkpl_${u.searchParams.get('id') || Date.now()}.pdf;
    }

    function mb(n) {
    return ${(n / 1024 / 1024).toFixed(1)} MB;
    }

    function createPanel() {
    const box = document.createElement('div');
    box.style.cssText = `
    position: fixed;
    right: 80px;
    top: 10px;
    z-index: 999999;
    width: 280px;
    padding: 10px;
    background: white;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 3px 12px rgba(0,0,0,.25);
    font-size: 13px;
    color: #222;

    ;
    
    const btn = document.createElement('button');
    btn.textContent = '并发下载PDF';
    btn.style.cssText = 

    width: 100%;
    padding: 8px;
    border: 0;
    border-radius: 6px;
    background: #0b57d0;
    color: white;
    cursor: pointer;
    font-size: 14px;

    ;
    
    const text = document.createElement('div');
    text.textContent = '等待下载';
    text.style.cssText = <code>margin-top: 8px;</code>;
    
    const barWrap = document.createElement('div');
    barWrap.style.cssText = 

    margin-top: 8px;
    width: 100%;
    height: 8px;
    background: #e5e7eb;
    border-radius: 999px;
    overflow: hidden;

    ;
    
    const bar = document.createElement('div');
    bar.style.cssText = 

    width: 0%;
    height: 100%;
    background: #0b57d0;
    transition: width .15s linear;
    `;

    barWrap.appendChild(bar);
    box.appendChild(btn);
    box.appendChild(text);
    box.appendChild(barWrap);
    document.body.appendChild(box);

    return { btn, text, bar };
    }

    async function detectSize(url) {
    const res = await fetch(url, {
    method: 'GET',
    credentials: 'include',
    cache: 'no-store',
    headers: {
    Accept: 'application/pdf,*/*',
    Range: 'bytes=0-0'
    }
    });

    const contentRange = res.headers.get('content-range') || '';
    const match = contentRange.match(/\/(\d+)$/);

    if (res.status === 206 && match) {
    return Number(match[1]);
    }

    const len = Number(res.headers.get('content-length') || 0);
    if (res.status === 200 && len > 0) {
    throw new Error('服务器忽略 Range 请求,不能并发分段下载,只能单连接。');
    }

    const text = await res.clone().text().catch(() => '');
    throw new Error(无法检测 PDF 大小。HTTP ${res.status}\n${text.slice(0, 300)});
    }

    async function fetchPart(url, start, end, onProgress) {
    const res = await fetch(url, {
    method: 'GET',
    credentials: 'include',
    cache: 'no-store',
    headers: {
    Accept: 'application/pdf,*/*',
    Range: bytes=${start}-${end}
    }
    });

    if (res.status !== 206) {
    const text = await res.clone().text().catch(() => '');
    throw new Error(分片请求失败 HTTP ${res.status}\n${text.slice(0, 200)});
    }

    const reader = res.body.getReader();
    const chunks = [];
    let received = 0;

    while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    chunks.push(value);
    received += value.length;
    onProgress(value.length);
    }

    const expected = end - start + 1;
    if (received !== expected) {
    throw new Error(分片大小不一致:expected ${expected}, got ${received});
    }

    return new Blob(chunks, { type: 'application/pdf' });
    }

    async function parallelDownload(ui) {
    const url = getPdfUrl();
    if (!url) throw new Error('没有找到 viewer.html 的 file 参数');

    ui.btn.disabled = true;
    ui.btn.textContent = '检测大小...';
    ui.text.textContent = '正在检测服务器是否支持分段下载...';

    const total = await detectSize(url);
    const filename = filenameFromUrl(url);
    const chunkSize = CHUNK_MB * 1024 * 1024;

    const ranges = [];
    for (let start = 0; start < total; start += chunkSize) {
    const end = Math.min(start + chunkSize - 1, total - 1);
    ranges.push({ index: ranges.length, start, end });
    }

    ui.btn.textContent = '下载中...';

    let downloaded = 0;
    const startedAt = performance.now();
    const parts = new Array(ranges.length);
    let next = 0;

    function updateProgress(delta) {
    downloaded += delta;
    const percent = downloaded / total * 100;
    const seconds = Math.max((performance.now() - startedAt) / 1000, 0.001);
    const speed = downloaded / seconds / 1024 / 1024;

    ui.bar.style.width = ${percent.toFixed(2)}%;
    ui.text.textContent =
    ${mb(downloaded)} / ${mb(total)} (${percent.toFixed(1)}%) ${speed.toFixed(2)} MB/s;
    }

    async function worker() {
    while (next < ranges.length) {
    const item = ranges[next++];
    const blob = await fetchPart(url, item.start, item.end, updateProgress);
    parts[item.index] = blob;
    }
    }

    const workerCount = Math.min(CONCURRENCY, ranges.length);
    ui.text.textContent = 并发 ${workerCount},共 ${ranges.length} 个分片...;

    await Promise.all(Array.from({ length: workerCount }, worker));

    ui.text.textContent = '正在合并 PDF...';

    const finalBlob = new Blob(parts, { type: 'application/pdf' });
    if (finalBlob.size !== total) {
    throw new Error(合并后大小不一致:expected ${total}, got ${finalBlob.size});
    }

    const objectUrl = URL.createObjectURL(finalBlob);
    const a = document.createElement('a');
    a.href = objectUrl;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();

    setTimeout(() => URL.revokeObjectURL(objectUrl), 60000);

    ui.bar.style.width = '100%';
    ui.text.textContent = 下载成功:${mb(finalBlob.size)};
    ui.btn.textContent = '下载成功';
    }

    const ui = createPanel();

    ui.btn.onclick = async () => {
    try {
    await parallelDownload(ui);
    } catch (e) {
    console.error('[HKPL] parallel download failed:', e);
    ui.btn.disabled = false;
    ui.btn.textContent = '重新下载';
    ui.text.textContent = '下载失败';
    alert(下载失败:\n${e.message});
    }
    };

    console.log('[HKPL] parallel downloader ready:', getPdfUrl());
    })();

    屏幕截图 2026-06-15 140106

    正常启用的情况下是能看到这个组件的,如果没有的话可以多刷新几下。脚本由Chatgpt编写。

    注意事项的话,就是关闭代理,切换一个好的网络环境,最好是用手机热点,不然有概率会出现下载速度为0.06mb/s,下半天都没成功。

    屏幕截图 2026-06-15 140338

    也尝试使用了python脚本进行下载,能成功,但是下载速度很慢,应该跟我网络有关。

    @214531 回复 ⚑举报 

    书格AI
    参与者

    感谢您对香港公共图书馆PDF下载方式的深入探索和详细分享!

    您提供的油猴脚本(HKPL PDF Parallel Downloader)是一个非常棒的解决方案,它通过并发下载的方式解决了直接访问PDF链接可能遇到的限制问题。

    对于其他遇到相同问题的书友,可以尝试安装并使用您分享的油猴脚本。请注意以下几点以获得更好的下载体验:
    关闭代理设置,确保网络连接直接。
    选择一个稳定且速度较快的网络环境,例如手机热点,以避免下载速度过慢。
    如果脚本启用后页面上没有看到下载组件,可以尝试多刷新几次页面。

    这个脚本能够有效帮助用户下载香港公共图书馆的PDF文件,非常实用。

    (以上内容由Gemini AI自动答复,仅供参考!)

    @214532 回复 ⚑举报 

    野草
    游客

    python代码参考:github.com/Rutak...9%A6%86.py

    油猴脚本参考:

    github.com/Rutak...der-0.5.js

    @214541 回复 ⚑举报 

    霍明明
    游客

    @野草 #214532

    脚本这样算不算安装好了 我弄了几次 没反应

    2026-06-15_181430

    @214595 回复 ⚑举报 

    空里流霜
    游客

    @野草 #214530

    谢谢先生!

正在查看 5 个帖子:1-5 (共 5 个帖子)
正在查看 5 个帖子:1-5 (共 5 个帖子)

上传图片

拖拽或点击选择图片(最多五张)

回复至:香港公共图书馆下载方式探索
您的信息:



发帖/回帖前,请了解相关版规

0,邮箱地址尽量真实有效,随意填写的可能会被系统误判为垃圾内容。
1,不要开书单。单个帖子尽量发布一种书籍需求。
2,在搜索不到相关主题的情况下,尽量发新帖(发帖标题最好带上书名)。不要在他人帖子中回复某种书籍需要。
3,发帖提问标题尽量简单明了。发帖内容不要太过简略,请对书籍内容、版本或作者作简要说明。
4,出版于1976年以后的资源需求或分享将会被清理删除。