YouTube 字幕抓取工具随机失败,并显示“无法找到视频字幕”

问题描述 投票:0回答:1

我正在尝试使用 youtube-captions-scraper 包获取 YouTube 视频字幕。大多数时候,它工作得很好,但有时会失败并出现以下错误:无法找到视频字幕:video-id

此错误似乎是随机发生的,即使对于手动检查时字幕可用的视频也是如此。

const { getSubtitles } = require('youtube-captions-scraper');

const videoId = 'some-video-id';

getSubtitles({
  videoID: videoId, // YouTube video ID
  lang: 'en' // language code
})
.then(captions => {
  console.log(captions);
})
.catch(err => {
  console.error('Error:', err.message);
});
  • 视频肯定有字幕(我在 YouTube 上验证过)。
  • 同一个视频ID并不一定会出现错误;有时,相同的视频在后续尝试中效果会很好。
  • 我在不同的网络和系统上尝试过此操作,但行为不一致。

有人遇到过这个问题或者知道为什么会发生这种情况吗?是否与 YouTube 的速率限制或 API 限制有关?任何有关如何更优雅地处理此问题或完全避免错误的建议将不胜感激。

javascript web-scraping youtube youtube-api
1个回答
0
投票

这个库包含一个小函数。您可以在此处查看代码。

产生该错误的部分是这样的:

  const data = await fetchData(
    `https://youtube.com/watch?v=${videoID}`
  );

  // * ensure we have access to captions data
  if (!data.includes('captionTracks'))
    throw new Error(`Could not find captions for video: ${videoID}`);

这意味着反应良好,但内容不符合预期。这可能是针对验证码测试等抓取工具的防御。如果您使用像

puppeteer
这样的无头浏览器来获取 HTML,您可能会得到更好的结果。还可以考虑使用不同的代理以获得更高的成功率。

一旦您使用 puppeteer 获取 html,或使用

fetch
和旋转代理,您就可以使用以下模块,该模块基于库的源代码,并带有附加参数:视频页面的 HTML。

import he from 'he';
import axios from 'axios';
import {
  find
} from 'lodash';
import striptags from 'striptags';

const fetchData =
  typeof fetch === 'function' ?
  async function fetchData(url) {
    const response = await fetch(url);
    return await response.text();
  } :
  async function fetchData(url) {
    const {
      data
    } = await axios.get(url);
    return data;
  };

export async function getSubtitles({
  videoID,
  lang = 'en',
}: {
  videoID: string,
  lang: 'en' | 'de' | 'fr' | void,
}, html) {
  const data = html || await fetchData(
    `https://youtube.com/watch?v=${videoID}`
  );

  // * ensure we have access to captions data
  if (!data.includes('captionTracks'))
    throw new Error(`Could not find captions for video: ${videoID}`);

  const regex = /"captionTracks":(\[.*?\])/;
  const [match] = regex.exec(data);

  const {
    captionTracks
  } = JSON.parse(`{${match}}`);
  const subtitle =
    find(captionTracks, {
      vssId: `.${lang}`,
    }) ||
    find(captionTracks, {
      vssId: `a.${lang}`,
    }) ||
    find(captionTracks, ({
      vssId
    }) => vssId && vssId.match(`.${lang}`));

  // * ensure we have found the correct subtitle lang
  if (!subtitle || (subtitle && !subtitle.baseUrl))
    throw new Error(`Could not find ${lang} captions for ${videoID}`);

  const transcript = await fetchData(subtitle.baseUrl);
  const lines = transcript
    .replace('<?xml version="1.0" encoding="utf-8" ?><transcript>', '')
    .replace('</transcript>', '')
    .split('</text>')
    .filter(line => line && line.trim())
    .map(line => {
      const startRegex = /start="([\d.]+)"/;
      const durRegex = /dur="([\d.]+)"/;

      const [, start] = startRegex.exec(line);
      const [, dur] = durRegex.exec(line);

      const htmlText = line
        .replace(/<text.+>/, '')
        .replace(/&amp;/gi, '&')
        .replace(/<\/?[^>]+(>|$)/g, '');

      const decodedText = he.decode(htmlText);
      const text = striptags(decodedText);

      return {
        start,
        dur,
        text,
      };
    });

  return lines;
}
© www.soinside.com 2019 - 2024. All rights reserved.