返回 Skill 列表
extension
分类: 数据与分析无需 API Key

动态网页PDF文件下载

从动态加载文件的网页中下载 PDF/文档。当用户需要从政府、国企、企业等网站下载文件(如公告、招标书、中标通知书等),但页面附件链接为 javascript:void(0) 或找不到直接下载按钮时使用。通过浏览器自动化注入网络监听、捕获接口响应、逆向拼接下载地址,完成文件抓取。触发词:下载网页PDF、下载附件、页面里没有下载按钮、附件打不开、提取文档链接、抓取公示文件。

person作者: user_d77d75f9hubcommunity

动态网页文件下载技能(Web File Downloader)

适用于附件链接为 javascript:void(0) 的动态加载页面,通过浏览器自动化逆向工程提取真实下载地址。

快速开始

当用户说「帮我把这个页面的 PDF 下载下来」「这个附件打不开」等类似需求时:

  1. 加载 browser-agent 技能
  2. 按「五步法」流程执行
  3. 下载文件到本地并验证完整性
  4. 交付给用户

适用场景

✅ 典型场景

| 场景 | 示例 | |------|------| | 政府公告附件 | 招标公告、中标通知书、政策文件 | | 国企采购平台 | 国网电商平台、中石化采购网等 | | 企业内部系统 | OA 系统文档预览页、知识库附件 | | 招投标网站 | 公共资源交易中心、政府采购网 |

❌ 不适用场景

  • 页面上已有明确 .pdf / .doc 下载链接 → 直接用 curl 下载
  • 需要登录才能访问的文件 → 先引导用户完成登录
  • 文件需要付费购买 → 提示用户自行完成支付
  • 非公开/受版权保护的文件 → 拒绝并说明原因

核心方法:五步法

总体流程

注入钩子 → 触发请求 → 捕获响应 → 逆向源码 → 拼接下载

前提条件

⚠️ 以下约束来自大量实测经验,违反将导致任务失败。

| 约束项 | 要求 | 原因 | |--------|------|------| | 执行环境 | use_main_world: true | 隔离沙箱中钩子无法拦截页面真实请求 | | 截图 | 禁止 browser_take_screenshot | 截图数据量大会导致上下文 token 溢出熔断 | | 输出长度 | 每次 evaluate ≤ 2500 字符 | 超长返回会被截断,大 JSON 需分段读取 | | 重复执行 | 禁止连续执行相同 JS | 会触发重复操作熔断机制 | | 最大次数 | browser_evaluate ≤ 6 次 | 超过后服务端强制终止 |


第 1 步:注入响应监听钩子

在页面主执行环境注入 XHR 响应拦截器,记录所有接口的完整响应。

// browser_evaluate (use_main_world: true)
(function(){
  window.__resp = {};
  var os = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.send = function(){
    var s = this;
    s.addEventListener('load', function(){
      try {
        if (s.__u) {
          window.__resp[s.__u] = (s.responseText || '').slice(0, 8000);
        }
      } catch(e){}
    });
    return os.apply(this, arguments);
  };
  if (!XMLHttpRequest.prototype.__oP) {
    var oo = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(m, u) {
      this.__u = u;
      return oo.apply(this, arguments);
    };
    XMLHttpRequest.prototype.__oP = true;
  }
  return 'hooked';
})()

要点

  • 必须用 use_main_world: true,否则脚本在浏览器隔离沙箱运行,拦截不到页面真实请求
  • 钩子同时拦截 opensend,通过 __oP 标志防止重复注入
  • 响应截取前 8000 字符,覆盖绝大多数 JSON 接口响应

第 2 步:触发文档请求

通过页面内路由切换重新触发 API 请求,确保钩子能捕获到完整的接口调用链。

browser_navigate → 站点首页(如 #/)→ 等待 2 秒
browser_navigate → 原文档页 → 等待 6~8 秒

如果文档需要点击附件才加载,则在导航回文档页后:

  1. browser_snapshot 获取页面快照
  2. 在快照中找到含 .pdf 文字的附件链接
  3. browser_click 点击该链接
  4. 等待 4~5 秒让异步请求完成

第 3 步:读取捕获的接口响应

// browser_evaluate (use_main_world: true)
(function(){
  var keys = Object.keys(window.__resp || {});
  var out = [];
  for (var i = 0; i < keys.length; i++) {
    var v = window.__resp[keys[i]];
    if (v && v.length > 100) {
      out.push(keys[i] + ' => ' + v.slice(0, 2000));
    }
  }
  return out.join('\n---\n').slice(0, 2400) || 'EMPTY';
})()

如果返回 EMPTY

  • 检查钩子是否在导航时被清除(页面刷新会重置 JS 环境)
  • 尝试先注入钩子 → 再导航回文档页(调整第 1、2 步顺序)

如果响应过长被截断,分段读取:

// 读取中段
(function(){ var r = window.__resp; var k = Object.keys(r); return k.map(function(x){ return x + ' => ' + r[x].slice(2000, 4000); }).join('\n').slice(0, 2400); })()
// 读取后段
(function(){ var r = window.__resp; var k = Object.keys(r); return k.map(function(x){ return x + ' => ' + r[x].slice(4000, 6000); }).join('\n').slice(0, 2400); })()

从响应中识别关键信息

| 字段类型 | 常见字段名 | 说明 | |---------|-----------|------| | 文件路径 | FILE_PATH, filePath, url, attachUrl, ossUrl | 可能是加密值 | | 文件 ID | fileId, attachId, docId, id | 用于构造下载请求 | | 文件名 | FILE_NAME, fileName, name, title | 用于保存文件 |


第 4 步:在前端 JS 源码中找下载接口模板

// browser_evaluate (use_main_world: true)
(async function(){
  var out = [];
  var srcs = Array.prototype.slice.call(document.querySelectorAll('script[src]'))
    .map(function(s){ return s.src; })
    .filter(function(u){ return u.indexOf(location.hostname) > -1; });
  var kws = ['FILE_PATH', 'downloadFile', 'downLoad', 'getFile',
             'pdfjs', 'viewer.html', 'showPDF', 'attachUrl'];
  for (var k = 0; k < kws.length && out.length < 6; k++) {
    for (var i = 0; i < srcs.length && out.length < 6; i++) {
      try {
        var t = await (await fetch(srcs[i])).text();
        var idx = t.indexOf(kws[k]);
        while (idx > -1 && out.length < 6) {
          out.push('KW=' + kws[k] + '|' + srcs[i].split('/').pop() + '@' + idx
            + ':' + t.slice(Math.max(0, idx - 150), idx + 250).replace(/\s+/g, ' '));
          idx = t.indexOf(kws[k], idx + 1);
        }
      } catch(e){}
    }
  }
  return out.join('\n=====\n').slice(0, 2300) || 'NOT-FOUND';
})()

从源码片段中识别下载 URL 模板,常见模式:

| 模式 | 示例 | |------|------| | window.open 拼接 | window.open(adr + "/index/downLoadWinFile?filePath=" + l.FILE_PATH + "&fileName=" + l.FILE_NAME) | | PDF 预览器加载 | this.pdfv.load({fileurl: adr + "/index/showPDF?filePath=" + n}) | | fetch / xhr 请求 | fetch(baseUrl + "/api/attach/download?id=" + fileId) | | <a> 动态设置 href | link.href = baseUrl + "/files/" + item.path |


第 5 步:拼接 URL 并下载

根据模板拼出完整下载 URL,用 curl 下载:

# URL 编码文件路径参数(Python 方式)
ENC_PATH=$(python -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "$FILE_PATH")

# 下载文件
curl -sk --max-time 300 -L -o "文件名.pdf" \
  -H "Referer: https://目标域名/..." \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36" \
  "完整下载URL"

下载后验证(必须执行):

# 检查文件头(PDF 应以 %PDF- 开头)
head -c 16 "文件名.pdf" | od -c | head -2

# 检查文件尾(PDF 应以 %%EOF 结尾)
tail -c 64 "文件名.pdf" | od -c | tail -3

# 检查文件大小是否合理
ls -la "文件名.pdf"

已验证站点模板

以下模板经过实测验证,可直接使用。其他站点请按五步法动态分析。

国网电商平台 (ecp.sgcc.com.cn)

| 项目 | 值 | |------|-----| | 元数据接口 | /ecp2.0/ecpwcmcore/index/getWinFile | | 加密路径字段 | FILE_PATH(十六进制加密,含 \| 分隔符) | | 文件名段 | FILE_NAME | | 下载接口 | /ecp2.0/ecpwcmcore/index/downLoadWinFile?filePath={FILE_PATH}&fileName={FILE_NAME} | | 预览接口 | /ecp2.0/ecpwcmcore/index/showPDF?filePath={FILE_PATH} | | 注意事项 | filePath 需要 URL 编码;需携带 Referer 请求头 |

通用站点分析模板

对于未收录的站点,按以下检查清单逐步分析:

□ 1. 注入钩子后导航触发请求
□ 2. 从捕获的接口中找到返回文件信息的 API
□ 3. 提取文件路径/ID 字段
□ 4. 搜索前端源码找到 URL 拼接模板
□ 5. 构造完整 URL 并下载
□ 6. 验证文件完整性

故障排除

按现象查原因

| 现象 | 可能原因 | 解决方案 | |------|---------|---------| | Hook 捕获为空数组 [] | 脚本在隔离沙箱运行 | 所有 browser_evaluate 必须加 use_main_world: true | | 上下文超限熔断 | 截图或大 JSON 撑爆 token | 禁止 browser_take_screenshot;输出截断到 2500 字符 | | 重复操作熔断 | 连续执行相同 JS 脚本 | 每次 evaluate 脚本必须有差异 | | 接口响应被截断 | 单次返回长度限制 | 分段读取:slice(0,2400)slice(2400,4800)slice(4800,7200) | | 前端源码搜索无结果 | JS 文件过多或托管在 CDN | 缩小范围到同域名脚本;增加关键词变体 | | 下载得到 HTML 而非 PDF | URL 拼接错误或需要认证 | 检查 URL 参数;确认是否需要 Cookie/Token | | 文件大小为 0 或极小 | 接口返回错误信息 | 检查响应 content-type 和状态码 |

重试策略

  1. 钩子未捕获 → 调整注入和导航顺序(先钩子后导航)
  2. 源码搜索无果 → 扩大关键词范围,或直接在 Network 面板中手动检查
  3. 下载失败 → 增加 Cookie、Token 等认证头,或尝试预览接口

安全与合规

  • ✅ 仅用于下载公开可访问的文件(公示、公告、招标文件等)
  • ✅ 不绕过登录/权限验证;如需登录则先引导用户完成
  • ✅ 下载文件保存在本地,不外传到第三方服务
  • ✅ 遵守目标网站的 robots.txt 和使用条款
  • ❌ 不用于批量爬取或自动化数据采集
  • ❌ 不用于获取受版权保护的非公开内容

版本历史

| 版本 | 日期 | 变更 | |------|------|------| | 2.0.0 | 2026-08-08 | 根据 SkillHub 五维评测优化:增加场景分类、约束表格、分段读取、已验证模板、检查清单、重试策略 | | 1.0.0 | 2026-08-08 | 初版:五步法核心流程、国网平台模板、故障排除 |