Back to Home
Gistify Snippet
Public
Scraper wallpaper - unsplash.com (anti-bot)
Scraper wallpaper - unsplash.com (anti-bot)
#github-gist#javascript#gist:0efd29a97ad0740db33bda14777a215d#gistsync:2026-09-02T10:46:06Z
1
Rawunsplash.jsjavascript
121 lines| 1 | |
| 2 | |
| 3 | import fs from 'node:fs/promises' |
| 4 | import os from 'node:os' |
| 5 | import path from 'node:path' |
| 6 | import { execFile } from 'node:child_process' |
| 7 | import { promisify } from 'node:util' |
| 8 | import { fileURLToPath } from 'node:url' |
| 9 | |
| 10 | const execFileAsync = promisify(execFile) |
| 11 | |
| 12 | const BASE = 'https://unsplash.com' |
| 13 | const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36' |
| 14 | |
| 15 | const BRIDGE = fileURLToPath(new URL('./scrapling_fetch.py', import.meta.url)) |
| 16 | |
| 17 | const uniq = (arr) => [...new Set(arr)] |
| 18 | |
| 19 | async function fetchPageHtml(url) { |
| 20 | const outputDir = await fs.mkdtemp(path.join(os.tmpdir(), 'unsplash-scrape-')) |
| 21 | const opts = JSON.stringify({ |
| 22 | mode: 'auto', |
| 23 | referer: 'https://www.google.com/', |
| 24 | timeout: 60000, |
| 25 | solve_cloudflare: true, |
| 26 | headless: true, |
| 27 | }) |
| 28 | try { |
| 29 | const { stdout } = await execFileAsync(process.env.SCRAPLING_PYTHON || "/tmp/scrapping-venv/bin/python", [BRIDGE, url, outputDir, opts], { |
| 30 | timeout: 90000, |
| 31 | maxBuffer: 10 * 1024 * 1024, |
| 32 | }) |
| 33 | const lastLine = stdout.trim().split('\n').filter((l) => l.trim().startsWith('{')).at(-1) || '{}' |
| 34 | const result = JSON.parse(lastLine) |
| 35 | if (!result.success || !result.file) { |
| 36 | throw new Error(result.error || 'Scrapling gagal mengambil halaman.') |
| 37 | } |
| 38 | return await fs.readFile(result.file, 'utf8') |
| 39 | } finally { |
| 40 | await fs.rm(outputDir, { recursive: true, force: true }).catch(() => {}) |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | export function parseWallpapers(html) { |
| 45 | const ids = [] |
| 46 | const slugs = [] |
| 47 | const alts = [] |
| 48 | |
| 49 | const imgRe = /<img[^>]*src="https:\/\/images\.unsplash\.com\/((?:photo|flagged\/photo)-[^"?]+)[^"]*"[^>]*>/g |
| 50 | let m |
| 51 | while ((m = imgRe.exec(html)) !== null) { |
| 52 | ids.push(m[1]) |
| 53 | const altMatch = /alt="([^"]*)"/.exec(m[0]) |
| 54 | const alt = altMatch ? altMatch[1].replace(/'/g, "'").trim() : '' |
| 55 | if (alt && !/^Go to /.test(alt) && alt.length >= 4) alts.push(alt) |
| 56 | } |
| 57 | |
| 58 | // 2) ID dari <link rel="preload" imageSrcSet="...photo-XXX..."> (halaman browse) |
| 59 | const preloadRe = /imageSrcSet="https:\/\/images\.unsplash\.com\/((?:photo|flagged\/photo)-[^"?]+)/g |
| 60 | while ((m = preloadRe.exec(html)) !== null) ids.push(m[1]) |
| 61 | |
| 62 | // 3) Slug dari link download `/photos/<slug>/download` |
| 63 | const slugRe = /\/photos\/([a-zA-Z0-9_-]{6,})\/download/g |
| 64 | while ((m = slugRe.exec(html)) !== null) slugs.push(m[1]) |
| 65 | |
| 66 | const uniqIds = uniq(ids) |
| 67 | const uniqSlugs = uniq(slugs) |
| 68 | |
| 69 | return uniqIds.map((id, i) => { |
| 70 | const slug = uniqSlugs[i] || '' |
| 71 | return { |
| 72 | id, |
| 73 | slug, |
| 74 | alt: alts[i] || (slug ? `Unsplash Wallpaper ${slug}` : 'Unsplash Wallpaper'), |
| 75 | pageUrl: slug ? `${BASE}/photos/${slug}` : '', |
| 76 | } |
| 77 | }) |
| 78 | } |
| 79 | |
| 80 | /** |
| 81 | * Ambil daftar wallpaper dari URL halaman Unsplash. |
| 82 | */ |
| 83 | export async function fetchWallpapers(url, options = {}) { |
| 84 | const html = await fetchPageHtml(url) |
| 85 | const list = parseWallpapers(html) |
| 86 | if (list.length === 0) throw new Error('Tidak ada wallpaper ditemukan di halaman tersebut.') |
| 87 | return list |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Wallpaper acak dari halaman browse /id/wallpapers. |
| 92 | */ |
| 93 | export async function browseWallpapers(options = {}) { |
| 94 | return fetchWallpapers(`${BASE}/id/wallpapers`, options) |
| 95 | } |
| 96 | |
| 97 | /** |
| 98 | * Cari wallpaper dengan kata kunci via halaman search. |
| 99 | */ |
| 100 | export async function searchWallpapers(query, options = {}) { |
| 101 | const q = String(query).trim() |
| 102 | if (!q) throw new Error('Query pencarian kosong.') |
| 103 | return fetchWallpapers(`${BASE}/s/photos/${encodeURIComponent(q)}`, options) |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Bangun URL CDN dengan ukuran tertentu. Default 2560px q90 (HD, ~1-3 MB), |
| 108 | * `hd` true -> 3840px (4K). |
| 109 | */ |
| 110 | export function buildImageUrl(entry, { w = 2560, q = 90, hd = false } = {}) { |
| 111 | const width = hd ? 3840 : w |
| 112 | return `https://images.unsplash.com/${entry.id}?w=${width}&q=${q}&fm=jpg&auto=format&fit=max&ixlib=rb-4.1.0` |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Download buffer wallpaper (selalu JPEG). |
| 117 | */ |
| 118 | export async function downloadWallpaper(entry, options = {}) { |
| 119 | const url = buildImageUrl(entry, options) |
| 120 | const res = await fetch(url, { |
| 121 | headers: { 'User-Agent': UA, Accept: 'image |
Discussion & Comments0
No comments yet — be the first to leave feedback or start a code discussion.
Sign in with your GitHub account to write comments and join the discussion.