我正在尝试使用 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 的速率限制或 API 限制有关?任何有关如何更优雅地处理此问题或完全避免错误的建议将不胜感激。
这个库包含一个小函数。您可以在此处查看代码。
产生该错误的部分是这样的:
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(/&/gi, '&')
.replace(/<\/?[^>]+(>|$)/g, '');
const decodedText = he.decode(htmlText);
const text = striptags(decodedText);
return {
start,
dur,
text,
};
});
return lines;
}