You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何程序化点击元素并等待加载新元素,批量获取有效音频下载链接?

问题:如何程序化遍历点击元素列表,等待点击后加载预定义新元素?

场景示例:
我想从OTRR网站下载老广播节目《I Love a Mystery》,先通过以下代码构造了URL列表:

const urlStub = "https://otrr.org/OTRRLibrary/jukebox/";
copy(Array.from(
    document.getElementsByTagName('a')).
        map( a => `"${urlStub}${a.href.split('/').pop()}"` ).
        join(' ')
)

但用下面的curl命令下载时,大量返回404 Not Found错误:

for url in <copied-list>; do curl -k -O $u; done

后来发现必须点击节目标题,触发播放器预加载音频文件后,curl才能成功下载。手动点击数百个文件太麻烦,尝试过批量点击的代码:

const t = Array.from(document.querySelectorAll('td.colTitle'));

t.pop().click()
...
t.pop().click()

但这个方案不够理想。我自己写了基于MutationObserver的递归解决方案,但不确定有没有更简单、不会因数千条内容崩溃的方法。


解决方案

方案1:async/await + 固定延迟(简单易实现)

适合页面加载逻辑稳定的场景,给每个点击操作预留足够的预加载时间,避免请求失败。

async function batchClickElements() {
  // 反转数组,按页面显示顺序点击(原pop()是倒序)
  const elements = Array.from(document.querySelectorAll('td.colTitle')).reverse();
  for (const el of elements) {
    el.click();
    // 等待音频预加载,时间根据页面实际速度调整,比如2秒
    await new Promise(resolve => setTimeout(resolve, 2000));
    // 可选:提取当前可用的音频下载链接
    const validAudioUrl = document.querySelector('audio').src;
    console.log(validAudioUrl); // 可收集这些URL后批量下载
  }
}

// 执行批量点击
batchClickElements();
  • 优点:代码简洁,无需监听DOM变化,调试成本低
  • 缺点:固定延迟不够灵活,页面加载慢时可能失败,加载快时浪费时间

方案2:改进版MutationObserver(稳定可靠)

针对页面加载速度不稳定的情况,通过监听DOM变化确认音频预加载完成后,再执行下一次点击,避免无效操作或内存泄漏。

function processNextElement(elements) {
  if (elements.length === 0) {
    console.log('所有元素处理完成');
    return;
  }

  const currentEl = elements.shift();
  currentEl.click();

  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      // 监听音频元素的src属性变化,确认有效链接加载完成
      if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
        const audioSrc = mutation.target.src;
        if (audioSrc && !audioSrc.includes('placeholder')) { // 替换为页面实际的占位符特征
          console.log('可用下载链接:', audioSrc);
          observer.disconnect(); // 停止当前监听,避免内存占用
          // 留短缓冲时间,再处理下一个元素
          setTimeout(() => processNextElement(elements), 500);
        }
      }
      // 如果音频元素还未生成,监听播放器容器的DOM新增
      if (mutation.type === 'childList') {
        const audioEl = document.querySelector('audio');
        if (audioEl) {
          observer.disconnect();
          // 重新监听音频元素的属性变化
          observer.observe(audioEl, { attributes: true });
        }
      }
    }
  });

  // 优先监听已存在的音频元素,否则监听播放器容器
  const audioEl = document.querySelector('audio');
  if (audioEl) {
    observer.observe(audioEl, { attributes: true });
  } else {
    // 替换为页面实际的播放器容器选择器
    const playerContainer = document.querySelector('#jukebox-player');
    observer.observe(playerContainer, { childList: true, subtree: true });
  }
}

// 启动批量处理
const targetElements = Array.from(document.querySelectorAll('td.colTitle')).reverse();
processNextElement(targetElements);
  • 优点:基于实际DOM变化触发,可靠性高,无无效等待,适合大量元素处理
  • 优化点:每次处理后断开观察者,避免内存泄漏;按顺序处理元素,逻辑更清晰;增加缓冲时间防止页面卡顿

方案3:直接调用接口获取真实URL(最优解)

通过浏览器开发者工具分析页面点击时的请求,找到获取音频URL的接口,直接批量请求接口获取真实链接,无需模拟点击。

async function fetchAllAudioUrls() {
  // 获取所有节目标题对应的标识(假设ID存储在data-episode-id属性)
  const titleElements = Array.from(document.querySelectorAll('td.colTitle'));
  const episodeIds = titleElements.map(el => el.dataset.episodeId);

  const audioUrls = [];
  for (const id of episodeIds) {
    // 替换为页面实际的音频接口地址
    const response = await fetch(`https://otrr.org/api/episode/audio?id=${id}`, {
      headers: {
        // 复制浏览器请求头中的Cookie、Referer等信息,避免接口拦截
        'Cookie': document.cookie,
        'Referer': window.location.href
      }
    });
    const data = await response.json();
    audioUrls.push(data.audioUrl);
    console.log(data.audioUrl);
  }

  // 将所有URL复制到剪贴板,方便后续curl批量下载
  copy(audioUrls.join('\n'));
}

fetchAllAudioUrls();
  • 优点:无需模拟点击,效率最高,不会出现页面卡顿或崩溃问题
  • 注意:需要匹配接口的请求头(如Cookie、Referer),部分网站可能有反爬机制,需根据实际情况调整

内容的提问来源于stack exchange,提问作者toraritte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 06:35:55