A Go-based service for crawling Sora videos from sora.chatgpt.com with REST API and MCP (Model Context Protocol) support.
- 🎥 Sora Video Crawler - Automatically scroll and download videos/thumbnails from Sora
- 🚀 REST API - Simple HTTP API for triggering crawls
- 🔧 Command-line interface with
runserverandversioncommands - 🛠️ Browser Automation - Uses headless Chrome for dynamic content scraping
- 🌐 CORS and middleware support out of the box
- 📝 MCP Integration - Optional MCP tools for AI agent integration
- 🔒 Anti-Detection - Built-in stealth mode to bypass Cloudflare and other anti-bot protections
- ♻️ Smart Deduplication - Automatically skips re-downloading existing videos/thumbnails, organized in unique folders
cd socrawler
go mod tidy# Start the server on default port 8080
go run . runserver
# Or specify a custom port
go run . runserver --port 3000
# Enable debug logging and disable headless mode (to see browser)
go run . runserver --debug --headless=false
# For production, use headless mode (default)
go run . runserver --headless=trueUse the included test script:
./test_sora_crawl.shOr use curl directly:
curl -X POST http://localhost:8080/api/sora/crawl \
-H "Content-Type: application/json" \
-d '{
"total_duration_seconds": 60,
"scroll_interval_seconds": 10,
"save_path": "./downloads/sora"
}'Use the MCP Inspector to test your server:
npx @modelcontextprotocol/inspectorThen connect to: http://localhost:8080/mcp
# Start the server
go run . runserver [--port 8080] [--debug] [--headless=true]
# Show version information
go run . version
# Show help
go run . --helpCrawls Sora videos and thumbnails from sora.chatgpt.com.
Request Body:
{
"total_duration_seconds": 300, // Total crawl duration (default: 300s)
"scroll_interval_seconds": 20, // Scroll interval (default: 20s)
"save_path": "./downloads/sora" // Save directory (default: ./downloads/sora)
}Response:
{
"success": true,
"data": {
"videos": ["path1.mp4", "path2.mp4"],
"thumbnails": ["thumb1.webp", "thumb2.webp"],
"total_videos": 2,
"total_thumbnails": 2,
"duration_seconds": 300
},
"message": "Crawl completed successfully"
}Health check endpoint.
The crawler includes smart deduplication to prevent re-downloading the same videos and thumbnails:
- URL-Based Hashing - Each URL is hashed (SHA256) to create a unique folder identifier
- Automatic Skip - Files are checked before download; existing files are skipped
- Organized Structure - Each video/thumbnail pair stored in its own folder
- Bandwidth Savings - Only download new content on subsequent crawls
downloads/sora/
├── 8bfb1a7bdd74/ # Hash from video URL
│ ├── video.mp4 # Video file
│ └── thumbnail.webp # Thumbnail file
├── 3892fb6b23d4/
│ ├── video.mp4
│ └── thumbnail.webp
└── debug_initial_page.png
Run the deduplication test script:
./test_deduplication.shFor more details, see DEDUPLICATION.md.
The crawler includes built-in anti-detection features to bypass Cloudflare and other anti-bot protections:
- Stealth Mode - Uses
go-rod/stealthlibrary to hide automation signatures - Custom User Agent - Mimics real Chrome browser on macOS
- JavaScript Overrides - Masks
navigator.webdriverand other detection points - Realistic Browser Behavior - Smooth scrolling and natural timing
The crawler automatically applies stealth techniques:
// In browser/browser.go
- Sets realistic User-Agent
- Injects stealth.js to hide automation
- Overrides navigator.webdriver
- Masks Chrome automation flags
- Simulates real browser propertiesTo verify the anti-detection is working:
- Run with visible browser (non-headless mode):
go run . runserver --headless=false --debug-
Watch the browser navigate to Sora - it should pass Cloudflare verification automatically
-
Check logs for:
level=info msg="Applying stealth mode to page..."
level=info msg="Page loaded, waiting for initial content..."
If you still see Cloudflare verification pages:
- Try non-headless mode first - Some sites detect headless browsers:
go run . runserver --headless=false- Add delays - Increase wait times to appear more human:
curl -X POST http://localhost:8080/api/sora/crawl \
-H "Content-Type: application/json" \
-d '{
"total_duration_seconds": 120,
"scroll_interval_seconds": 15,
"save_path": "./downloads/sora"
}'-
Check your IP - Some IPs may be rate-limited or blocked by Cloudflare
-
Use residential proxy - For production use, consider using a proxy service
The crawler now includes detailed logging to help diagnose issues:
- Network Request Statistics: Tracks total requests, OpenAI-related requests, and media requests
- Page Information: Logs page title, URL, and HTML length
- Login Detection: Warns if the page appears to require authentication
- Screenshot Capture: Saves initial page screenshot to
{save_path}/debug_initial_page.png - Real-time Status: Reports current video/thumbnail count and network statistics during crawling
time="..." level=info msg="Applying stealth mode to page..."
time="..." level=info msg="Page title: Sora, URL: https://sora.chatgpt.com/"
time="..." level=info msg="Saved initial page screenshot to: ./downloads/sora/debug_initial_page.png"
time="..." level=debug msg="Page HTML length: 45678 bytes"
time="..." level=debug msg="Detected media-related URL: https://videos.openai.com/..."
time="..." level=info msg="Found video: https://videos.openai.com/video.mp4"
time="..." level=info msg="Current status: videos=5, thumbnails=5, total_requests=234, openai_requests=45, media_requests=10"
Cloudflare Verification Page
If you see a Cloudflare "Verifying you are human" page:
- Try running in non-headless mode:
--headless=false - The stealth mode should handle this automatically
- Check the screenshot to confirm what page is being loaded
- See the "Anti-Detection & Cloudflare Bypass" section above
No videos found (videos=0, thumbnails=0)
Possible causes:
- Authentication Required: Sora may require login. Check the logs for "Page may require login" warning
- Changed URL Structure: The video URLs may have changed. Check debug logs for "Detected media-related URL" entries
- Network Issues: Check if
openai_requestsandmedia_requestsare > 0 - Insufficient Wait Time: Try increasing
scroll_interval_secondsortotal_duration_seconds
Debugging Steps:
- Enable debug logging:
go run . runserver --debug - Check the screenshot:
{save_path}/debug_initial_page.png - Review network statistics in the logs
- Look for "Detected media-related URL" entries to see what URLs are being captured
socrawler/
├── main.go # CLI entry point with cobra commands
├── app_server.go # Core server implementation
├── service.go # Sora service business logic
├── handlers.go # API request handlers
├── routes.go # HTTP routing setup
├── middleware.go # CORS and error handling middleware
├── types.go # Type definitions
├── streamable_http.go # MCP protocol handler (JSON-RPC over HTTP)
├── tools.go # MCP tool implementations
├── browser/ # Browser automation
│ └── browser.go # Clean browser initialization
├── configs/ # Configuration
│ └── browser.go # Headless mode config
├── sora/ # Sora crawler implementation
│ ├── crawler.go # Browser scrolling and URL interception
│ ├── downloader.go # Media file downloader
│ └── types.go # Internal types
├── go.mod # Go module definition
└── README.md # This file
- Define your tool in
tools.go:
func (s *AppServer) handleMyCustomTool(ctx context.Context, args map[string]interface{}) *MCPToolResult {
// Extract arguments
param1, _ := args["param1"].(string)
// Your tool logic here
result := fmt.Sprintf("Processing: %s", param1)
return &MCPToolResult{
Content: []MCPContent{{
Type: "text",
Text: result,
}},
IsError: false,
}
}- Register the tool in
streamable_http.go:
Add to processToolsList():
{
"name": "my_custom_tool",
"description": "Description of what your tool does",
"inputSchema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"param1": map[string]interface{}{
"type": "string",
"description": "Description of param1",
},
},
"required": []string{"param1"},
},
},Add to processToolCall():
case "my_custom_tool":
result = s.handleMyCustomTool(ctx, toolArgs)- Browser Automation: Uses go-rod to control a headless Chrome browser
- Dynamic Scrolling: Automatically scrolls the Sora page at configured intervals
- Network Interception: Captures video and thumbnail URLs from network requests to
videos.openai.com - Media Detection:
- Videos: URLs containing
.mp4 - Thumbnails: URLs containing
.webporthumbnail
- Videos: URLs containing
- Download: Downloads and saves media files with unique SHA256-based filenames
The server can be configured through command-line flags:
--port, -p: Server port (default: 8080)--debug, -d: Enable debug logging--headless: Run browser in headless mode (default: true)--help, -h: Show help information
feed uploadgoldcast command.
Easiest way - via command-line flags:
./socrawler feed uploadgoldcast \
--oss-access-key-id="your-aliyun-access-key-id" \
--oss-access-key-secret="your-aliyun-access-key-secret"Or via environment variables:
# Only these two are required (others have defaults)
export OSS_ACCESS_KEY_ID="your-aliyun-access-key-id"
export OSS_ACCESS_KEY_SECRET="your-aliyun-access-key-secret"
./socrawler feed uploadgoldcastOptional settings (have defaults):
OSS_BUCKET_NAME(default:dreammedias)OSS_ENDPOINT(default:oss-cn-beijing.aliyuncs.com)OSS_REGION(default:cn-beijing)
For detailed configuration instructions, see OSS_CONFIG_CHANGE.md.
curl -X POST http://localhost:8080/api/sora/crawl \
-H "Content-Type: application/json" \
-d '{
"total_duration_seconds": 60,
"scroll_interval_seconds": 10,
"save_path": "./downloads/sora"
}'curl -X POST http://localhost:8080/api/sora/crawl \
-H "Content-Type: application/json" \
-d '{
"total_duration_seconds": 300,
"scroll_interval_seconds": 20,
"save_path": "./downloads/sora"
}'curl -X POST http://localhost:8080/api/sora/crawl \
-H "Content-Type: application/json" \
-d '{
"total_duration_seconds": 1800,
"scroll_interval_seconds": 30,
"save_path": "./downloads/sora"
}'- Logging: Use
logrusfor structured logging - Context: Always pass context for cancellation support
- Error Handling: Proper error responses with status codes
- Browser Mode: Use
--headless=falsefor debugging - Testing: Start with short durations for testing
# Build binary
go build -o socrawler .
# Run the binary
./socrawler runserver --port 8080 --headless=true- Go 1.23.0 or higher
- Chrome/Chromium browser (automatically managed by go-rod)
- Internet connection (for accessing sora.chatgpt.com)
See DEPLOYMENT.md for detailed instructions on:
- Installing Chrome on Ubuntu servers
- Docker deployment
- Systemd service setup
- Production configurations
Quick Ubuntu Setup:
# Run the automated setup script
./setup_browser_ubuntu.sh- Make sure Chrome/Chromium is installed
- Try running with
--headless=falseto see browser activity - Check logs with
--debugflag
- Ensure Sora page is accessible
- Increase
total_duration_secondsto allow more time - Check network connectivity
- Verify save_path is writable
- The crawler keeps videos in memory before writing
- For long crawls, monitor system resources
- Consider shorter crawl durations or restart periodically
This project is provided as-is for educational and development purposes.
Built with ❤️ using Go, go-rod, and browser automation.
./run_service.sh help
./run_service.sh start
./run_service.sh start --port=9090
./run_service.sh start --headless=false
./run_service.sh status
./run_service.sh test
./run_service.sh test --duration=300 --interval=15
./run_service.sh logs
./run_service.sh stop
./run_service.sh restart
./run_service.sh cleanup