Back to Home
Gistify Snippet
kyiov
Sep 4·1 file(s)
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
Raw
unsplash.jsjavascript
121 lines
1
2
3import fs from 'node:fs/promises'
4import os from 'node:os'
5import path from 'node:path'
6import { execFile } from 'node:child_process'
7import { promisify } from 'node:util'
8import { fileURLToPath } from 'node:url'
9
10const execFileAsync = promisify(execFile)
11
12const BASE = 'https://unsplash.com'
13const 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
15const BRIDGE = fileURLToPath(new URL('./scrapling_fetch.py', import.meta.url))
16
17const uniq = (arr) => [...new Set(arr)]
18
19async 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
44export 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(/&#x27;/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 */
83export 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 */
93export async function browseWallpapers(options = {}) {
94 return fetchWallpapers(`${BASE}/id/wallpapers`, options)
95}
96
97/**
98 * Cari wallpaper dengan kata kunci via halaman search.
99 */
100export 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 */
110export 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 */
118export 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.