node爬虫脚本puppeteer
通过youtube链接获取视频信息
使用 YouTube Player API
首先,我们使用 YouTube Player API 来加载视频,这样我们就可以获取一些基本信息和控制视频播放。
js
<!DOCTYPE html>
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '360',
width: '640',
videoId: 'M7lc1UVf-VE',
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
getVideoInfo();
}
// 5. The API calls this function when the player's state changes.
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING) {
// Video is playing
}
}
function getVideoInfo() {
// 获取视频信息
var videoData = player.getVideoData();
console.log("Video Data:", videoData);
console.log("Title:", videoData.title);
console.log("Video ID:", videoData.video_id);
console.log("Author:", videoData.author);
// 获取视频时长(秒)
var duration = player.getDuration();
console.log("Duration:", duration);
// 获取缩略图
var videoId = player.getVideoData().video_id;
var thumbnailUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`;
console.log("Thumbnail URL:", thumbnailUrl);
}
</script>
</body>
</html>使用 oEmbed API 获取额外信息
YouTube Player API 无法提供所有你需要的信息,比如作者头像和视频简介。我们可以结合使用 oEmbed API 来获取一些额外信息。
js
async function getVideoInfo(url) {
const oembedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent("https://www.youtube.com/watch?v=Af4CR16VP2I&t=217s")}&format=json`;
try {
const response = await fetch(oembedUrl);
const data = await response.json();
console.log(data);
// 处理视频信息
} catch (error) {
console.error('Error:', error);
}
}使用Youtube Data API
Youtube Data API 可以获取更多的信息,但是需要获取API密钥,并且有每天10000的配额限制。
js
<script src="https://apis.google.com/js/api.js"></script>
// 替换为你的 API key
const API_KEY = '';
// 替换为你想获取信息的视频 ID
const VIDEO_ID = '';
// 加载 YouTube API 客户端库
const loadClient = () => {
return new Promise((resolve, reject) => {
gapi.load('client', () => {
gapi.client.setApiKey(API_KEY);
gapi.client.load('youtube', 'v3')
.then(() => resolve())
.catch(err => reject('Error loading YouTube API:', err));
});
});
}
// 执行 API 请求
const executeRequest = () => {
return gapi.client.youtube.videos.list({
"part": [
"snippet,contentDetails,statistics"
],
"id": VIDEO_ID
});
}
// 主函数
const getVideoInfo = async () => {
try {
await loadClient();
const response = await executeRequest();
console.log('Video info:', response.result.items[0]);
// 处理获取到的视频信息
const videoInfo = response.result.items[0];
// 例如,获取视频标题
console.log('Video title:', videoInfo.snippet.title);
// 获取缩略图 URL
console.log('Thumbnail URL:', videoInfo.snippet.thumbnails.default.url);
} catch (err) {
console.error('Error:', err);
}
}
// 调用主函数
getVideoInfo();node爬虫脚本puppeteer
使用脚本来爬取youtube视频链接的部分信息
结合node-fetch获取信息
js
const fetch = require('node-fetch');
const puppeteer = require('puppeteer');
const fs = require('fs');
function extractUsernameFromUrl(url) {
const match = url.match(/\/user\/([^/]+)$/);
return match ? match[1] : '';
}
async function scrapeData(urls) {
const browser = await puppeteer.launch({
headless: "new",
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const results = [];
try {
for (const url of urls) {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(url, { waitUntil: 'networkidle2' });
await page.waitForSelector('body');
const extractedData = await page.evaluate((url) => {
const ytInitialData = JSON.parse(document.body.innerHTML.match(/var ytInitialData = (.+?);<\/script>/)[1]);
const videoId = new URL(url).searchParams.get('v');
const videoTitle = ytInitialData.playerOverlays?.playerOverlayRenderer?.videoDetails?.playerOverlayVideoDetailsRenderer?.title?.simpleText;
let videoDescription = '';
let authorAvatar = '';
let authorName = '';
let authorEmail = '';
const contents = ytInitialData.contents?.twoColumnWatchNextResults?.results?.results?.contents;
if (contents) {
const data = contents.find(item => item.videoSecondaryInfoRenderer);
if (data) {
const videoSecondaryInfoRenderer = data.videoSecondaryInfoRenderer;
videoDescription = videoSecondaryInfoRenderer.attributedDescription?.content || '';
authorAvatar = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.thumbnail?.thumbnails?.[0]?.url || '';
authorName = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.text || '';
authorEmail = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.text?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url || '';
thumbnailUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`
}
}
return {
videoId,
videoTitle,
videoDescription,
authorAvatar,
authorName,
authorEmail,
thumbnailUrl
};
}, url);
if (extractedData) {
console.log(`Successfully extracted data for video ID: ${extractedData.videoId}`);
// 调用 oEmbed API
const oembedUrl = `https://www.youtube.com/oembed?url=${encodeURIComponent(url)}&format=json`;
try {
const response = await fetch(oembedUrl);
const oembedData = await response.json();
oembedData.id = extractedData.videoId;
const email = extractUsernameFromUrl(oembedData.author_url);
oembedData.email = "@" + email;
// 合并数据
const mergedData = {
...extractedData,
...oembedData,
thumbnail_url: oembedData.thumbnail_url,
title: oembedData.title,
author_url: oembedData.author_url
};
results.push(mergedData);
} catch (error) {
console.error('Error fetching oEmbed data:', error);
results.push(extractedData);
}
} else {
console.log(`Failed to extract data for URL: ${url}`);
}
await page.close();
}
// 将所有结果保存为 JSON 文件
fs.writeFileSync('extractedVideosData.json', JSON.stringify(results, null, 2));
console.log('All data saved to extractedVideosData.json');
} catch (error) {
console.error('Error scraping data:', error);
} finally {
await browser.close();
}
return results;
}
const urls = [
'https://www.youtube.com/watch?v=HpVOs5imUN0',
'https://www.youtube.com/watch?v=MFRbfY0cZFo',
'https://www.youtube.com/watch?v=wU9ouVuT7CQ',
'https://www.youtube.com/watch?v=l1PQ2d8DfHo',
'https://www.youtube.com/watch?v=YvB8wk_wJ6E',
'https://www.youtube.com/watch?v=tpFEainx6Ak',
'https://www.youtube.com/watch?v=h3J1os_YfiI',
'https://www.youtube.com/watch?v=2WPdvIl3yYg',
'https://www.youtube.com/watch?v=TZVioAWixJ4',
];
scrapeData(urls).then(results => {
console.log(`Scraped data for ${results.length} videos`);
});使用promise来处理并发的问题,提高爬取速度。
js
const puppeteer = require('puppeteer');
const fs = require('fs');
async function scrapeData(urls) {
const browser = await puppeteer.launch({
headless: "new",
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const results = [];
try {
const pages = await Promise.all(urls.map(async (url) => {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(url, { waitUntil: 'networkidle2' });
await page.waitForSelector('body');
return {
page,
url
};
}));
const extractedDataPromises = pages.map(async ({ page, url }) => {
const extractedData = await page.evaluate((url) => {
const ytInitialData = JSON.parse(document.body.innerHTML.match(/var ytInitialData = (.+?);<\/script>/)[1]);
const videoId = new URL(url).searchParams.get('v');
const videoTitle = ytInitialData.playerOverlays?.playerOverlayRenderer?.videoDetails?.playerOverlayVideoDetailsRenderer?.title?.simpleText;
let videoDescription = '';
let authorAvatar = '';
let authorName = '';
let authorEmail = '';
const contents = ytInitialData.contents?.twoColumnWatchNextResults?.results?.results?.contents;
if (contents) {
const data = contents.find(item => item.videoSecondaryInfoRenderer);
if (data) {
const videoSecondaryInfoRenderer = data.videoSecondaryInfoRenderer;
videoDescription = videoSecondaryInfoRenderer.attributedDescription?.content || '';
authorAvatar = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.thumbnail?.thumbnails?.[0]?.url || '';
authorName = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.text || '';
authorEmail = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url || '';
}
}
return {
videoId,
videoTitle,
videoDescription,
authorAvatar,
authorName,
authorEmail,
url
};
}, url);
return extractedData;
});
const extractedData = await Promise.all(extractedDataPromises);
// 将数据按原始 URL 顺序存储到 results 数组中
for (let i = 0; i < pages.length; i++) {
if (extractedData[i]) {
results.push(extractedData[i]);
} else {
console.log(`Failed to extract data for URL: ${pages[i].url}`);
}
await pages[i].page.close();
}
// 将所有结果保存为 JSON 文件
fs.writeFileSync('extractedVideosData.json', JSON.stringify(results, null, 2));
console.log('All data saved to extractedVideosData.json');
} catch (error) {
console.error('Error scraping data:', error);
} finally {
await browser.close();
}
return results;
}
const urls = [
'https://www.youtube.com/watch?v=HpVOs5imUN0',
'https://www.youtube.com/watch?v=MFRbfY0cZFo',
'https://www.youtube.com/watch?v=wU9ouVuT7CQ',
'https://www.youtube.com/watch?v=l1PQ2d8DfHo',
'https://www.youtube.com/watch?v=YvB8wk_wJ6E',
'https://www.youtube.com/watch?v=tpFEainx6Ak',
'https://www.youtube.com/watch?v=h3J1os_YfiI',
'https://www.youtube.com/watch?v=2WPdvIl3yYg',
'https://www.youtube.com/watch?v=TZVioAWixJ4',
];
scrapeData(urls).then(results => {
console.log(`Scraped data for ${results.length} videos`);
});最终实现:多平台爬取并排序(部分需要下载图片到本地)
js
const path = require('path');
const fs = require('fs');
const puppeteer = require('puppeteer');
const axios = require('axios');
const crypto = require('crypto');
const sharp = require('sharp');
const storyDataDirectory = path.join(__dirname, 'public', 'assets', 'storydata');
clearDirectory(storyDataDirectory);
async function scrapeData(urlList) {
const browser = await puppeteer.launch({
headless: "new",
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const results = [];
try {
const pages = await Promise.all(urlList.map(async (url) => {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
try {
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60000
});
await page.waitForSelector('body', { timeout: 60000 });
} catch (error) {
console.error(`Error loading page ${url}: ${error.message}`);
return null; // 页面加载失败
}
return {
page,
url,
};
}));
const extractedDataPromises = pages
.filter(page => page !== null) // 过滤掉加载失败的页面
.map(async ({ page, url }) => {
try {
const type = determineType(url);
const extractedData = await page.evaluate(async (url, type) => {
if (type === 'youtube') {
const ytInitialData = JSON.parse(document.body.innerHTML.match(/var ytInitialData = (.+?);<\/script>/)[1]);
const videoId = new URL(url).searchParams.get('v');
const videoTitle = ytInitialData.playerOverlays?.playerOverlayRenderer?.videoDetails?.playerOverlayVideoDetailsRenderer?.title?.simpleText;
let videoDescription = '';
let authorAvatar = '';
let authorName = '';
let authorEmail = '';
let viewCount = '';
let date = ''
const contents = ytInitialData.contents?.twoColumnWatchNextResults?.results?.results?.contents;
if (contents) {
const primaryData = contents.find(item => item.videoPrimaryInfoRenderer);
const secondaryData = contents.find(item => item.videoSecondaryInfoRenderer);
if (primaryData) {
viewCount = primaryData.videoPrimaryInfoRenderer.viewCount?.videoViewCountRenderer?.viewCount?.simpleText || '';
date = primaryData.videoPrimaryInfoRenderer.dateText?.simpleText || '';
}
if (secondaryData) {
const videoSecondaryInfoRenderer = secondaryData.videoSecondaryInfoRenderer;
videoDescription = videoSecondaryInfoRenderer.attributedDescription?.content || '';
authorAvatar = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.thumbnail?.thumbnails?.[0]?.url || '';
authorName = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.text || '';
authorEmail = videoSecondaryInfoRenderer.owner?.videoOwnerRenderer?.title?.runs?.[0]?.navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.slice(1) || '';
thumbnailUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`
}
}
return {
videoId,
videoTitle,
videoDescription,
authorAvatar,
authorName,
authorEmail,
thumbnailUrl,
viewCount,
date,
};
} else if (type === 'x') {
const match = url.match(/\/status\/(\d+)/);
const videoId = (match && match[1]) ? match[1] : null;
const date = document.querySelector('a time').textContent.trim();
return {
date,
videoId
};
} else if (type === 'bilibili') {
const videoTitle = document.querySelector('.video-title').textContent.trim();
const authorName = document.querySelector('.up-name').textContent.trim();
const date = document.querySelector('.pubdate-ip-text').textContent.trim();
const videoDescription = document.querySelector('.desc-info-text').textContent.trim();
let thumbnailUrl = document.querySelector('#wxwork-share-pic')?.getAttribute('src') || '';
let authorUrl = document.querySelector('.up-avatar')?.href || '';
let authorAvatar = document.querySelector('.bili-avatar-img')?.getAttribute('data-src') || '';
thumbnailUrl = thumbnailUrl.split('@')[0];
authorAvatar = authorAvatar.split('@')[0];
return {
videoTitle,
authorName,
authorAvatar,
thumbnailUrl,
videoDescription,
date,
authorUrl
};
} else if (type === 'xiaohongshu') {
// const ytInitialData = JSON.parse(document.body.innerHTML.match(/var ytInitialData = (.+?);<\/script>/)[1]);
const thumbnailUrl = document.querySelector('.note-slider-img')?.getAttribute('src') || ''
const element = document.querySelector('.xgplayer-poster');
let videolUrl
if (element) {
const style = window.getComputedStyle(element);
const backgroundImage = style.getPropertyValue('background-image');
const urlMatch = backgroundImage.match(/url\(["']?(.*?)["']?\)/);
if (urlMatch && urlMatch[1]) {
videolUrl = urlMatch[1];
}
}
// const thumbnailUrl = document.querySelector('.note-slider-img')?.getAttribute('src') || ''
const videoTitle = document.querySelector('#detail-title')?.textContent.trim() || ''
const authorName = document.querySelector('.username')?.textContent.trim() || ''
const date = document.querySelector('.date')?.textContent.trim() || '';
const authorAvatar = document.querySelector('.avatar-item')?.getAttribute('src') || ''
const videoDescription = document.querySelector('#detail-desc')?.children[0]?.textContent.trim() || ''
return {
thumbnailUrl,
videoTitle,
authorName,
date,
authorAvatar,
videoDescription,
videolUrl
}
} else if (type === 'facebook') {
const html = document.body.innerHTML
const endIndex = html.indexOf('<span>赞</span>');
const truncatedHtml = endIndex !== -1 ? html.substring(0, endIndex) : html;
const authorName = document.querySelector('strong span')?.textContent.trim() || ''
const regex = /<div dir="auto" style="text-align:start">(.+?)<\/div>/g;
const matches = [];
let match;
while ((match = regex.exec(truncatedHtml)) !== null) {
const text = match[1].trim()
if (!text.includes('<span')) {
matches.push(match[1].trim());
}
}
console.log(matches)
// const authorName = await page.$eval('strong span', el => el.textContent.trim())
return {
authorName,
matches
}
}
}, url, type);
if (type === 'bilibili') {
extractedData.thumbnailUrl = await downloadAndSaveImage(extractedData.thumbnailUrl, 'thumbnail', 434, 244);
extractedData.authorAvatar = await downloadAndSaveImage(extractedData.authorAvatar, 'authorAvatar', 100, 100);
}
return {
...extractedData,
type,
url
};
} catch (error) {
console.error(`Error extracting data from ${url}: ${error.message}`);
return null;
}
});
const extractedData = await Promise.all(extractedDataPromises);
for (let i = 0; i < pages.length; i++) {
if (pages[i] && extractedData[i]) {
results.push(extractedData[i]);
} else {
console.log(`Failed to extract data for URL: ${pages[i]?.url || 'Unknown URL'}`);
}
if (pages[i]) {
await pages[i].page.close();
}
}
return results;
} catch (error) {
console.error('Error scraping data:', error);
} finally {
await browser.close();
}
return results;
}
function determineType(url) {
const typeMap = {
'youtube.com': 'youtube',
'twitter.com': 'x',
'x.com': 'x',
'bilibili.com': 'bilibili',
'xiaohongshu.com': 'xiaohongshu',
"facebook.com": 'facebook'
};
for (const domain in typeMap) {
if (url.includes(domain)) {
return typeMap[domain];
}
}
return 'unknown';
}
//bilibili图片直接引用会跨域,保存到本地 evaluate是浏览器环境,无法使用path
async function downloadAndSaveImage(url, prefix, width = 240, height = 240) {
if (!url) return null;
const cleanUrl = url.split('@')[0]; // 移除尺寸参数
const hash = crypto.createHash('md5').update(cleanUrl).digest('hex');
const filename = `${prefix}_${hash}.jpg`;
const filepath = path.join(__dirname, 'public', 'assets', 'storydata', 'bilibili', filename);
try {
// 下载原始图片
const response = await axios({
url: cleanUrl,
method: 'GET',
responseType: 'stream'
});
// 使用 sharp 调整尺寸
await new Promise((resolve, reject) => {
response.data
.pipe(sharp())
.resize(width, height) // 调整尺寸
.toFile(filepath, (err, info) => {
if (err) {
reject(err);
} else {
resolve(info);
}
});
});
return `/assets/storydata/bilibili/${filename}`;
} catch (error) {
console.error(`Error downloading and resizing image from ${url}:`, error);
return null;
}
}
function parseDate(dateString) {
const formats = [
/(\d{4})年(\d{1,2})月(\d{1,2})日/, // "2024年6月12日"
/(\d{4})-(\d{1,2})-(\d{1,2})/, // "2024-06-18"
/(\d{1,2}):(\d{1,2}) · (\d{4})年(\d{1,2})月(\d{1,2})日/, // "上午10:59 · 2024年8月20日"
/(\d{2})-(\d{2})/, // "07-29"
];
for (const format of formats) {
console.log(dateString, 'dateString');
if (dateString) {
const match = dateString.match(format);
if (match) {
let year, month, day
if (match.length === 3) {
year = new Date().getFullYear()
month = parseInt(match[1], 10) - 1;
day = parseInt(match[2], 10);
} else {
year = parseInt(match[1], 10);
month = parseInt(match[2], 10) - 1; // JavaScript 月份是从 0 开始的
day = parseInt(match[3], 10);
}
return new Date(year, month, day);
}
}
}
return ''
}
//格式化时间并排序
function sortByDate(arr, descending = true) {
return arr.map(item => {
const parsedDate = parseDate(item.date);
if (parsedDate) {
item.formattedDate = parsedDate?.toISOString()?.substring(0, 10);
}
return item;
}).sort((a, b) => {
const dateA = new Date(a.formattedDate);
const dateB = new Date(b.formattedDate);
if (descending) {
return dateB - dateA; // 降序
} else {
return dateA - dateB; // 升序
}
});
}
function clearDirectory(directoryPath) {
fs.readdir(directoryPath, (err, files) => {
if (err) {
console.error(`Error reading directory: ${directoryPath}`, err);
return;
}
files.forEach(file => {
fs.unlink(path.join(directoryPath, file), err => {
if (err) {
console.error(`Error deleting file: ${file}`, err);
}
});
});
});
}
async function crawlXHS (){
}
const URL_LIST = [
"https://www.xiaohongshu.com/explore/66a786500000000009015489?xsec_token=ABnIt-lH6fhQX9QtRxZ26efsL5Rev0lciTN03JYhURzP0=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/66c05ab2000000000901580d?xsec_token=ABy3jJHBMU9kaAYMOVsxNuzVZxBO2o4kvkGq9wrC2yxe0=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/65a351dd0000000011018408?xsec_token=AB2ianaz-rA6X_XjMhN6MKLIvrNOtQBqOVguOpB5fS428=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/658a974e000000001d025c16?xsec_token=ABc9YG3s0uBPFN_37JyuYaA2W4awVyZtEB4JuEp6mJXB0=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/6588fb0b0000000034034823?xsec_token=ABYyhmQaV6NqmnuR9Bf9hNoMfsq-Rm4yofCZoG7f8w1cs=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/66601df600000000060046a2?xsec_token=ABCnM0d4p41wI5QpoSnzuyQiHVr4cCz1jnlj5jXIQjOTo=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/665b39e60000000005006c49?xsec_token=ABWS8Bf4uJrTFPSHyu1WxVAh9DhfsSfs1ZwkYfUelGZ18=&xsec_source=pc_search",
"https://www.xiaohongshu.com/explore/666c4e23000000000e032bbc?xsec_token=ABsugUDWchlnDbuj99QHNXtEgvcy-teJ10CNAFLoJJ9sU=&xsec_source=pc_search",
"https://www.facebook.com/share/p/hLyXkajHg9AMd4n1/",
"https://www.facebook.com/share/p/WAzpRTrrYAh6QWUB/",
"https://www.facebook.com/share/p/n1u7NA2Btzcg5cw3/",
"https://www.facebook.com/share/p/fzVXBkgAwxfMtqAB/",
"https://www.facebook.com/share/v/PDftY4EMLzH6jdui/",
"https://www.facebook.com/share/p/cu27hN8uUFJKm2CN/",
"https://twitter.com/DeemosTech/status/1825729382016745811",
"https://twitter.com/Gorden_Sun/status/1796578685161783527",
"https://twitter.com/ntamotsu_/status/1782273273843294611",
"https://twitter.com/satori_sz9/status/1796015122021765505",
"https://twitter.com/DeemosTech/status/1812909941105303604",
"https://twitter.com/grmchn4ai/status/1798119923098804242",
"https://twitter.com/kuronagirai/status/1782257115022925983",
"https://twitter.com/DeemosTech/status/1825810156262928478",
"https://twitter.com/DeemosTech/status/1799112017309217082",
"https://twitter.com/akinoriosamura/status/1760124253398237202",
"https://twitter.com/DeemosTech/status/1823375756220838364",
"https://twitter.com/akinoriosamura/status/1755428565053771925",
"https://twitter.com/WilliamLamkin/status/1792934921595469858",
"https://twitter.com/DeemosTech/status/1805236190066708493",
"https://twitter.com/DeemosTech/status/1802329656030633994",
"https://twitter.com/DeemosTech/status/1815026598695112841",
"https://twitter.com/DeemosTech/status/1809969250817454325",
"https://twitter.com/Koba_1975/status/1796712777802731984",
"https://twitter.com/labibjaffar/status/1818236302073925664",
"https://twitter.com/DeemosTech/status/1803253026708398228",
"https://twitter.com/Gorden_Sun/status/1796578685161783527",
"https://twitter.com/DeemosTech/status/1825729382016745811",
"https://twitter.com/DeemosTech/status/1827736182610153795",
"https://twitter.com/DeemosTech/status/1826692188077785112",
"https://www.youtube.com/watch?v=HpVOs5imUN0",
"https://www.youtube.com/watch?v=MFRbfY0cZFo",
"https://www.youtube.com/watch?v=tpFEainx6Ak",
"https://www.youtube.com/watch?v=h3J1os_YfiI",
"https://www.youtube.com/watch?v=2WPdvIl3yYg",
"https://www.youtube.com/watch?v=TZVioAWixJ4",
"https://www.youtube.com/watch?v=wU9ouVuT7CQ",
"https://www.youtube.com/watch?v=l1PQ2d8DfHo",
"https://www.youtube.com/watch?v=YvB8wk_wJ6E",
"https://www.bilibili.com/video/BV1L6421Z7ng/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1ky411b7ti/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV13142117Dx/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1bje8eVEdj/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1Dw4m1y72v/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV19WpoeAEJX/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1ii421Q7bQ/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1g4421Q7bp/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1d9TyeFEMY/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1pW421R7hd/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1fm421N7yu/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1jw4m1i7qT/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1xy41187bb/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1Wi421v7JR/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1Vb421n76D/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1Yi421U759/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1Hj42197bz/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1sT421v7LK/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1nw4m197wo/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
"https://www.bilibili.com/video/BV1SvVYeNEsC/?spm_id_from=333.337.search-card.all.click&vd_source=2cf827ae246d7e0c43844bc723259237",
];
scrapeData(URL_LIST).then(results => {
console.log(`Scraped data for ${results.length} videos`);
const sortedResults = sortByDate(results);
fs.writeFileSync('public/assets/storydata/extractedVideosData.json', JSON.stringify(sortedResults, null, 2));
console.log('All data saved to extractedVideosData.json');
});优化爬取脚本
js
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');
const axios = require('axios');
const fs = require('fs').promises;
const SITE_CONFIGS = {
XIAOHONGSHU: {
dataKey: 'note',
identifier: 'xiaohongshu.com',
parseStrategies: [
{
name: 'initialState',
pattern: /window\.__INITIAL_STATE__\s*=\s*({[^;]+})/,
extract: (match) => JSON.parse(match[1]).note
},
{
name: 'bracketMatching',
pattern: null,
extract: (content, config) => parseByBracketMatching(content, config)
}
]
},
BILIBILI: {
dataKey: 'videoData',
identifier: 'bilibili.com',
parseStrategies: [
{
name: 'initialState',
pattern: /window\.__INITIAL_STATE__\s*=\s*({[^;]+})/,
extract: (match) => JSON.parse(match[1]).videoData
},
{
name: 'bracketMatching',
pattern: null,
extract: (content, config) => parseByBracketMatching(content, config)
}
]
}
};
function parseByBracketMatching(scriptContent, siteConfig) {
const dataKeyIndex = scriptContent.indexOf(`,"${siteConfig.dataKey}":`);
if (dataKeyIndex === -1) return;
const dataStartIndex = scriptContent.indexOf('{', dataKeyIndex);
if (dataStartIndex === -1) return;
const stack = ['{'];
let dataEndIndex = dataStartIndex + 1;
while (dataEndIndex < scriptContent.length && stack.length > 0) {
const char = scriptContent[dataEndIndex];
if (char === '{') stack.push('{');
else if (char === '}') stack.pop();
dataEndIndex++;
}
if (stack.length === 0) {
const dataStr = scriptContent.substring(dataStartIndex, dataEndIndex);
return JSON.parse(dataStr);
}
}
async function crawlVideoByHtml(videoUrl) {
try {
const { data: html } = await axios.get(videoUrl);
const $ = cheerio.load(html);
const siteConfig = Object.values(SITE_CONFIGS).find(config =>
videoUrl.includes(config.identifier)
);
if (!siteConfig) {
throw new Error('不支持的网站');
}
let videoData = null;
$('script').each((index, element) => {
const scriptContent = $(element).html();
if (!scriptContent) return;
// 遍历所有解析策略
for (const strategy of siteConfig.parseStrategies) {
try {
if (strategy.pattern) {
const match = scriptContent.match(strategy.pattern);
if (match) {
videoData = strategy.extract(match, siteConfig);
if (videoData) {
console.log(`使用 ${strategy.name} 策略成功解析数据 --- `);
return false;
}
}
} else {
videoData = strategy.extract(scriptContent, siteConfig);
if (videoData) {
console.log(`使用 ${strategy.name} 策略成功解析数据`);
return false;
}
}
} catch (err) {
console.error(`${strategy.name} 策略解析失败:`, err);
continue;
}
}
});
return videoData;
} catch (error) {
console.error('爬取失败:', error);
throw error;
}
}
async function main() {
// 测试URL
const xhsUrl = 'https://www.xiaohongshu.com/explore/672c8dcf000000003c01c673?xsec_token=ABue8fMPYeoMxLuK-hG69Oi1gdn8lLd_clZlSlJhS0juU=&xsec_source=pc_feed';
const biliUrl = 'https://www.bilibili.com/video/BV1i8D6YGEcp/?spm_id_from=333.1007.tianma.1-2-2.click&vd_source=2cf827ae246d7e0c43844bc723259237';
try {
// 测试小红书
const xhsData = await crawlVideoByHtml(xhsUrl);
if (xhsData) {
await fs.writeFile(
'xhs_data.json',
JSON.stringify(xhsData, null, 2),
'utf8'
);
console.log('小红书数据已成功写入 xhs_data.json');
}
// 测试B站
const biliData = await crawlVideoByHtml(biliUrl);
if (biliData) {
await fs.writeFile(
'bili_data.json',
JSON.stringify(biliData, null, 2),
'utf8'
);
console.log('B站数据已成功写入 bili_data.json');
}
} catch (error) {
console.error('爬取失败:', error);
}
}
main()脚本数据反爬机制
html
<script>
window.__INIT_STATE__={}
(function() {
var s;
(s = document.currentScript || document.scripts[document.scripts.length - 1]).parentNode.removeChild(s);
}());
</script>