Created
July 20, 2025 14:30
-
-
Save acoyfellow/20e6d8d60bf9f3bcbbbd57192ba48877 to your computer and use it in GitHub Desktop.
GHL Course Video Scraper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Video URL Scraper for Course Platform | |
// WARNING: Only use this on content you have legal access to download | |
class VideoScraper { | |
constructor() { | |
this.videoUrls = []; | |
this.currentPage = 1; | |
this.isRunning = false; | |
this.delay = 3000; // 3 seconds delay between actions | |
} | |
// Wait function for delays | |
wait(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
// Find the next button (adjust selector as needed) | |
findNextButton() { | |
// Use the user's specific selector first | |
try { | |
const userButton = document.querySelectorAll(".max-w-8")[1]; | |
if (userButton && !userButton.disabled) { | |
return userButton; | |
} | |
} catch (e) { | |
console.log('User selector failed:', e.message); | |
} | |
// Try other possible selectors for the next button | |
const selectors = [ | |
'button[data-v-646dd304]', | |
'.plyr__control[data-plyr="next"]', | |
'.next-button', | |
'.navigation-next', | |
'button[aria-label*="next"]', | |
'button[aria-label*="Next"]' | |
]; | |
for (let selector of selectors) { | |
try { | |
const button = document.querySelector(selector); | |
if (button && !button.disabled) { | |
return button; | |
} | |
} catch (e) { | |
// Skip invalid selectors | |
continue; | |
} | |
} | |
// Look for buttons with arrow symbols or "next" text | |
const allButtons = document.querySelectorAll('button'); | |
for (let button of allButtons) { | |
const text = button.textContent.toLowerCase(); | |
if (text.includes('next') || text.includes('→') || text.includes('>') || text.includes('▶')) { | |
return button; | |
} | |
} | |
return null; | |
} | |
// Extract video URL from various sources | |
getVideoUrl() { | |
// Method 1: Check video element src | |
const videoElement = document.querySelector('video'); | |
if (videoElement && videoElement.src && !videoElement.src.startsWith('blob:')) { | |
return videoElement.src; | |
} | |
// Method 2: Check network requests for video files | |
// This requires the script to monitor network activity | |
return this.getVideoUrlFromNetwork(); | |
} | |
// Monitor network requests for video URLs | |
getVideoUrlFromNetwork() { | |
// This is a simplified version - in practice, you'd need to set up | |
// network monitoring before navigation | |
const performanceEntries = performance.getEntriesByType('resource'); | |
for (let entry of performanceEntries) { | |
if (entry.name.includes('.mp4') || | |
entry.name.includes('.m3u8') || | |
entry.name.includes('video') || | |
entry.name.includes('stream')) { | |
if (!entry.name.startsWith('blob:')) { | |
return entry.name; | |
} | |
} | |
} | |
return null; | |
} | |
// Get current lesson/video title | |
getCurrentTitle() { | |
const titleSelectors = [ | |
'h1', 'h2', '.lesson-title', '.video-title', | |
'[class*="title"]', '.course-title' | |
]; | |
for (let selector of titleSelectors) { | |
const element = document.querySelector(selector); | |
if (element) { | |
return element.textContent.trim(); | |
} | |
} | |
return `Video ${this.currentPage}`; | |
} | |
// Log video information | |
logVideoInfo(url, title) { | |
const info = { | |
page: this.currentPage, | |
title: title, | |
url: url, | |
timestamp: new Date().toISOString() | |
}; | |
this.videoUrls.push(info); | |
console.log(`Found video ${this.currentPage}: ${title}`); | |
console.log(`URL: ${url}`); | |
console.log('---'); | |
} | |
// Main scraping function | |
async scrapeVideos() { | |
if (this.isRunning) { | |
console.log('Scraper is already running!'); | |
return; | |
} | |
this.isRunning = true; | |
console.log('Starting video scraper...'); | |
console.log('WARNING: Make sure you have permission to download these videos!'); | |
try { | |
while (this.isRunning) { | |
console.log(`Processing page ${this.currentPage}...`); | |
// Wait for page to load | |
await this.wait(this.delay); | |
// Get current video info | |
const title = this.getCurrentTitle(); | |
const videoUrl = this.getVideoUrl(); | |
if (videoUrl) { | |
this.logVideoInfo(videoUrl, title); | |
} else { | |
console.log(`No video URL found on page ${this.currentPage}`); | |
} | |
// Find and click next button | |
const nextButton = this.findNextButton(); | |
if (!nextButton) { | |
console.log('No next button found. Scraping complete!'); | |
break; | |
} | |
console.log('Clicking next button...'); | |
nextButton.click(); | |
this.currentPage++; | |
// Wait for navigation | |
await this.wait(this.delay); | |
} | |
} catch (error) { | |
console.error('Error during scraping:', error); | |
} finally { | |
this.isRunning = false; | |
this.showResults(); | |
} | |
} | |
// Stop the scraper | |
stop() { | |
console.log('Stopping scraper...'); | |
this.isRunning = false; | |
} | |
// Show all collected URLs | |
showResults() { | |
console.log('\n=== SCRAPING COMPLETE ==='); | |
console.log(`Found ${this.videoUrls.length} videos:`); | |
console.log('\n'); | |
this.videoUrls.forEach((video, index) => { | |
console.log(`${index + 1}. ${video.title}`); | |
console.log(` URL: ${video.url}`); | |
console.log(` Page: ${video.page}`); | |
console.log(''); | |
}); | |
// Copy results to clipboard as JSON | |
const jsonResults = JSON.stringify(this.videoUrls, null, 2); | |
// Try to copy to clipboard | |
if (navigator.clipboard) { | |
navigator.clipboard.writeText(jsonResults).then(() => { | |
console.log('Results copied to clipboard!'); | |
}).catch(() => { | |
console.log('Could not copy to clipboard. Results logged above.'); | |
}); | |
} | |
return this.videoUrls; | |
} | |
// Export URLs as downloadable file | |
downloadResults() { | |
const jsonResults = JSON.stringify(this.videoUrls, null, 2); | |
const blob = new Blob([jsonResults], { type: 'application/json' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'video_urls.json'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
console.log('Results downloaded as video_urls.json'); | |
} | |
} | |
// Usage instructions | |
console.log('Video Scraper loaded! Usage:'); | |
console.log('const scraper = new VideoScraper();'); | |
console.log('scraper.scrapeVideos(); // Start scraping'); | |
console.log('scraper.stop(); // Stop scraping'); | |
console.log('scraper.showResults(); // Show collected URLs'); | |
console.log('scraper.downloadResults(); // Download as JSON file'); | |
console.log(''); | |
console.log('IMPORTANT: Only use on content you have legal rights to download!'); | |
// Auto-create instance | |
window.videoScraper = new VideoScraper(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment