New: Earn 20% as an AgentAlley affiliate. Share your link and earn on every sale you refer. Join now New: Earn 20% as an affiliate. Join now

Bulk download images from login-protected gallery websites

Gallery scraper

by jdrhyne · MIT license · ★ 240 on GitHub · updated Aug 30

Gallery scraper cover

Bulk download images from login-protected gallery websites using an attached browser session. Use when asked to scrape, download, or save images from authenticated gallery pages, extract full-size images from thumbnails, or batch…

---
name: gallery-scraper
description: Bulk download images from login-protected gallery websites using an attached browser session. Use when asked to scrape, download, or save images from authenticated gallery pages, extract full-size images from thumbnails, or batch download from multi-page galleries.
permissions:
  - exec: "Runs local download commands for the URL list gathered from the attached browser session."
  - file_write: "Creates URL lists and downloaded image files in the user-approved output directory."
  - network: "Uses the attached browser session and direct image downloads against the user-approved gallery domain."
---

# Gallery Scraper

Bulk download images from authenticated gallery websites via browser relay.

## Safety Boundaries

- Do not access gallery sites or user accounts that the user has not explicitly attached and authorized.
- Do not download beyond the selected gallery, profile, or page range without confirmation.
- Do not store cookies, tokens, or hidden form values in local output files.

Overview

Bulk download images from authenticated gallery websites via browser relay.

Manual install (npx, ZIP)
Claude Codenpx skills add jdrhyne/agent-skills --skill gallery-scraper -g -a claude-code
Claude Code (manual)mkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/jdrhyne/agent-skills/tar.gz/439cd3a9da78 \ | tar -xz -C ~/.claude/skills --strip-components=2 agent-skills-439cd3a9da78/clawdbot/gallery-scraper
Windows (PowerShell)New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/f1076f7db16b15d3dc7d/gallery-scraper.zip" -OutFile "$env:TEMP\gallery-scraper.zip"; Expand-Archive "$env:TEMP\gallery-scraper.zip" -DestinationPath "$HOME\.claude\skills" -Force
Claude.ai / desktopDownload the ZIP and upload it under Settings → Capabilities → Skills.
Codex, Cursor, other agentsPoint them at SKILL.md. The other files are plain text.

Connects with Agent Alley MCP · agentalley.io/mcp

Files included

  • SKILL.md6.5 KB
  • scripts/2 files · 5.0 KB
Read the files here
jdrhyne/439cd3a3 files
SKILL.md
Show the full text225 lines

Bulk download images from authenticated gallery websites via browser relay.

Safety Boundaries

  • Do not access gallery sites or user accounts that the user has not explicitly attached and authorized.
  • Do not download beyond the selected gallery, profile, or page range without confirmation.
  • Do not store cookies, tokens, or hidden form values in local output files.
  • Do not keep retrying blocked downloads indefinitely; surface rate limits or auth failures instead.

Prerequisites

  • User must have Chrome with OpenClaw Browser Relay extension
  • User must be logged into the target site
  • User must attach the browser tab (click relay toolbar button, badge ON)

Workflow

1. Attach Browser Tab

Ask user to:

  1. Log into the gallery site in Chrome
  2. Navigate to the target gallery/profile page
  3. Click the OpenClaw Browser Relay toolbar button (badge shows ON)
2. Discover Image URL Pattern

Most gallery sites store full-size URLs in data attributes. Common patterns:

// Extract via browser evaluate
() => {
  // Try common patterns
  const patterns = [
    'img[data-max]',           // data-max attribute
    'img[data-src]',           // lazy-load pattern
    'img[data-full]',          // full-size pattern
    'a[data-lightbox] img',    // lightbox galleries
    '.gallery-item img'        // generic gallery
  ];
  
  for (const sel of patterns) {
    const imgs = document.querySelectorAll(sel);
    if (imgs.length > 0) {
      return {
        selector: sel,
        count: imgs.length,
        sample: imgs[0].outerHTML.substring(0, 200)
      };
    }
  }
  return null;
}
3. Extract Full-Size URLs

Once pattern identified, extract all URLs:

// For data-max pattern (common)
() => Array.from(document.querySelectorAll('img[data-max]'))
  .map(img => img.dataset.max)

// For thumbnail→full conversion (replace path segment)
() => Array.from(document.querySelectorAll('.gallery img'))
  .map(img => img.src.replace('/thumb/', '/full/'))
4. Handle Pagination

Check for multiple pages:

() => {
  const pagination = document.querySelectorAll('.pagination a, [class*="page"] a');
  return Array.from(pagination).map(a => ({text: a.textContent, href: a.href}));
}

Navigate to each page and collect URLs.

4b. Batch scrape multiple galleries (iframe trick)

When you need multiple galleries quickly and can’t automate CDP, you can load each gallery in a hidden iframe and extract data-max URLs:

async () => {
  const urls = [
    'https://site.example/galleries/view/123',
    'https://site.example/galleries/view/456'
  ];
  const results = [];
  for (const url of urls) {
    const iframe = document.createElement('iframe');
    iframe.style.position = 'fixed';
    iframe.style.left = '-9999px';
    iframe.style.width = '800px';
    iframe.style.height = '600px';
    iframe.src = url;
    document.body.appendChild(iframe);
    await new Promise((resolve, reject) => {
      const t = setTimeout(() => reject(new Error('timeout load')), 20000);
      iframe.onload = () => { clearTimeout(t); resolve(); };
    });
    const doc = iframe.contentDocument;
    const start = Date.now();
    let imgs = [];
    while (Date.now() - start < 20000) {
      imgs = Array.from(doc.querySelectorAll('img[data-max]')).map(i => i.dataset.max);
      if (imgs.length) break;
      await new Promise(r => setTimeout(r, 500));
    }
    results.push({ id: url.split('/').pop(), urls: imgs });
    iframe.remove();
  }
  return results;
}
5. Check CDN Access

Test if CDN requires authentication or just Referer:

# Test direct access
curl -I "CDN_URL" 2>/dev/null | head -3

# Test with Referer
curl -I -H "Referer: https://SITE_DOMAIN/" "CDN_URL" 2>/dev/null | head -3
6. Bulk Download

Collect the URLs into a text file, then parallel download:

# Create output directory
mkdir -p ~/Downloads/gallery_name

# Download with Referer header (parallel)
cd ~/Downloads/gallery_name
while IFS= read -r url; do
  filename=$(basename "$url")
  curl -s -H "Referer: https://SITE_DOMAIN/" -o "$filename" "$url" &
  [ $(jobs -r | wc -l) -ge 8 ] && wait -n
done < urls.txt
wait

Python ThreadPool fallback (avoids shell quoting + wait -n issues):

import os
import requests
from concurrent.futures import ThreadPoolExecutor

outdir = os.path.expanduser('~/Downloads/gallery_name')
os.makedirs(outdir, exist_ok=True)
headers = {'Referer': 'https://SITE_DOMAIN/', 'User-Agent': 'Mozilla/5.0'}

with open('urls.txt') as f:
    urls = [line.strip() for line in f if line.strip()]

def download(url):
    filename = os.path.join(outdir, os.path.basename(url))
    if os.path.exists(filename) and os.path.getsize(filename) > 0:
        return
    r = requests.get(url, headers=headers, timeout=60)
    r.raise_for_status()
    with open(filename, 'wb') as f:
        f.write(r.content)

with ThreadPoolExecutor(max_workers=8) as ex:
    for url in urls:
        ex.submit(download, url)

Handling Lock Buttons

Some galleries have "lock" buttons to reveal hidden content. Look for:

// Find lock/unlock buttons
() => {
  const locks = document.querySelectorAll(
    '[class*="lock"], [class*="unlock"], ' +
    'button[title*="lock"], .premium-unlock'
  );
  return Array.from(locks).map(el => ({
    tag: el.tagName,
    class: el.className,
    text: el.innerText?.substring(0, 30)
  }));
}

Click each lock button before extracting URLs.

Output Organization

Optionally organize by gallery:

# Derive a gallery-specific folder name from the selected URL
mkdir -p "gallery_<id>"

Troubleshooting

  • 403 Forbidden: Add Referer header or extract cookies from browser
  • Rate limited: Reduce parallel downloads, add delays
  • Missing images: Check for JavaScript-loaded content, may need scroll injection
  • Login required for CDN: Extract session cookies via document.cookie
1---
2name: gallery-scraper
3description: Bulk download images from login-protected gallery websites using an attached browser session. Use when asked to scrape, download, or save images from authenticated gallery pages, extract full-size images from thumbnails, or batch download from multi-page galleries.
4permissions:
5 - exec: "Runs local download commands for the URL list gathered from the attached browser session."
6 - file_write: "Creates URL lists and downloaded image files in the user-approved output directory."
7 - network: "Uses the attached browser session and direct image downloads against the user-approved gallery domain."
8---
9 
10# Gallery Scraper
11 
12Bulk download images from authenticated gallery websites via browser relay.
13 
14## Safety Boundaries
15 
16- Do not access gallery sites or user accounts that the user has not explicitly attached and authorized.
17- Do not download beyond the selected gallery, profile, or page range without confirmation.
18- Do not store cookies, tokens, or hidden form values in local output files.
19- Do not keep retrying blocked downloads indefinitely; surface rate limits or auth failures instead.
20 
21## Prerequisites
22 
23- User must have Chrome with OpenClaw Browser Relay extension
24- User must be logged into the target site
25- User must attach the browser tab (click relay toolbar button, badge ON)
26 
27## Workflow
28 
29### 1. Attach Browser Tab
30 
31Ask user to:
321. Log into the gallery site in Chrome
332. Navigate to the target gallery/profile page
343. Click the OpenClaw Browser Relay toolbar button (badge shows ON)
35 
36### 2. Discover Image URL Pattern
37 
38Most gallery sites store full-size URLs in data attributes. Common patterns:
39 
40```javascript
41// Extract via browser evaluate
42() => {
43 // Try common patterns
44 const patterns = [
45 'img[data-max]', // data-max attribute
46 'img[data-src]', // lazy-load pattern
47 'img[data-full]', // full-size pattern
48 'a[data-lightbox] img', // lightbox galleries
49 '.gallery-item img' // generic gallery
50 ];
51 
52 for (const sel of patterns) {
53 const imgs = document.querySelectorAll(sel);
54 if (imgs.length > 0) {
55 return {
56 selector: sel,
57 count: imgs.length,
58 sample: imgs[0].outerHTML.substring(0, 200)
59 };
60 }
61 }
62 return null;
63}
64```
65 
66### 3. Extract Full-Size URLs
67 
68Once pattern identified, extract all URLs:
69 
70```javascript
71// For data-max pattern (common)
72() => Array.from(document.querySelectorAll('img[data-max]'))
73 .map(img => img.dataset.max)
74 
75// For thumbnail→full conversion (replace path segment)
76() => Array.from(document.querySelectorAll('.gallery img'))
77 .map(img => img.src.replace('/thumb/', '/full/'))
78```
79 
80### 4. Handle Pagination
81 
82Check for multiple pages:
83 
84```javascript
85() => {
86 const pagination = document.querySelectorAll('.pagination a, [class*="page"] a');
87 return Array.from(pagination).map(a => ({text: a.textContent, href: a.href}));
88}
89```
90 
91Navigate to each page and collect URLs.
92 
93### 4b. Batch scrape multiple galleries (iframe trick)
94 
95When you need multiple galleries quickly and can’t automate CDP, you can load each gallery in a hidden iframe and extract `data-max` URLs:
96 
97```javascript
98async () => {
99 const urls = [
100 'https://site.example/galleries/view/123',
101 'https://site.example/galleries/view/456'
102 ];
103 const results = [];
104 for (const url of urls) {
105 const iframe = document.createElement('iframe');
106 iframe.style.position = 'fixed';
107 iframe.style.left = '-9999px';
108 iframe.style.width = '800px';
109 iframe.style.height = '600px';
110 iframe.src = url;
111 document.body.appendChild(iframe);
112 await new Promise((resolve, reject) => {
113 const t = setTimeout(() => reject(new Error('timeout load')), 20000);
114 iframe.onload = () => { clearTimeout(t); resolve(); };
115 });
116 const doc = iframe.contentDocument;
117 const start = Date.now();
118 let imgs = [];
119 while (Date.now() - start < 20000) {
120 imgs = Array.from(doc.querySelectorAll('img[data-max]')).map(i => i.dataset.max);
121 if (imgs.length) break;
122 await new Promise(r => setTimeout(r, 500));
123 }
124 results.push({ id: url.split('/').pop(), urls: imgs });
125 iframe.remove();
126 }
127 return results;
128}
129```
130 
131### 5. Check CDN Access
132 
133Test if CDN requires authentication or just Referer:
134 
135```bash
136# Test direct access
137curl -I "CDN_URL" 2>/dev/null | head -3
138 
139# Test with Referer
140curl -I -H "Referer: https://SITE_DOMAIN/" "CDN_URL" 2>/dev/null | head -3
141```
142 
143### 6. Bulk Download
144 
145Collect the URLs into a text file, then parallel download:
146 
147```bash
148# Create output directory
149mkdir -p ~/Downloads/gallery_name
150 
151# Download with Referer header (parallel)
152cd ~/Downloads/gallery_name
153while IFS= read -r url; do
154 filename=$(basename "$url")
155 curl -s -H "Referer: https://SITE_DOMAIN/" -o "$filename" "$url" &
156 [ $(jobs -r | wc -l) -ge 8 ] && wait -n
157done < urls.txt
158wait
159```
160 
161**Python ThreadPool fallback (avoids shell quoting + wait -n issues):**
162 
163```python
164import os
165import requests
166from concurrent.futures import ThreadPoolExecutor
167 
168outdir = os.path.expanduser('~/Downloads/gallery_name')
169os.makedirs(outdir, exist_ok=True)
170headers = {'Referer': 'https://SITE_DOMAIN/', 'User-Agent': 'Mozilla/5.0'}
171 
172with open('urls.txt') as f:
173 urls = [line.strip() for line in f if line.strip()]
174 
175def download(url):
176 filename = os.path.join(outdir, os.path.basename(url))
177 if os.path.exists(filename) and os.path.getsize(filename) > 0:
178 return
179 r = requests.get(url, headers=headers, timeout=60)
180 r.raise_for_status()
181 with open(filename, 'wb') as f:
182 f.write(r.content)
183 
184with ThreadPoolExecutor(max_workers=8) as ex:
185 for url in urls:
186 ex.submit(download, url)
187```
188 
189## Handling Lock Buttons
190 
191Some galleries have "lock" buttons to reveal hidden content. Look for:
192 
193```javascript
194// Find lock/unlock buttons
195() => {
196 const locks = document.querySelectorAll(
197 '[class*="lock"], [class*="unlock"], ' +
198 'button[title*="lock"], .premium-unlock'
199 );
200 return Array.from(locks).map(el => ({
201 tag: el.tagName,
202 class: el.className,
203 text: el.innerText?.substring(0, 30)
204 }));
205}
206```
207 
208Click each lock button before extracting URLs.
209 
210## Output Organization
211 
212Optionally organize by gallery:
213 
214```bash
215# Derive a gallery-specific folder name from the selected URL
216mkdir -p "gallery_<id>"
217```
218 
219## Troubleshooting
220 
221- **403 Forbidden**: Add Referer header or extract cookies from browser
222- **Rate limited**: Reduce parallel downloads, add delays
223- **Missing images**: Check for JavaScript-loaded content, may need scroll injection
224- **Login required for CDN**: Extract session cookies via `document.cookie`
225 
View all files on GitHub →

Details

Updated
Aug 30, 2026
License
MIT
Stars on the repo
★ 240
Checked
Source
jdrhyne/agent-skills ↗
Creator
jdrhyne · 24 listings
View creator

Similar skills

See all →
SkillAct as the company research engine for /sales research <urlCompany Research & Firmographic Analysiszubair-trabzada/ai-sales-team-claude · by zubair-trabzadaYou are the company research engine for /sales research <url>. You produce deep, structured intelligence on a prospect company covering 8…★ 1.4k GitHub starsSkillEthical people-finding and identity verificationPrivateinvestigatordanielmiessler/LifeOS · by danielmiesslerEthical people-finding and identity verification via parallel research agents across people-search sites, social media, public records, and reverse phone/email/image/username lookups, with confidence-scored results requiring 3+ matching identifiers. USE WHEN find person, locate person, reconnect, lost contact, old friend, reverse phone lookup, who owns this email, reverse image search, find by username, verify identity, people search, public-data background check, who is this caller. NOT FOR structured due-diligence or company/entity intelligence investigations, or general web research synthesis (use Research).★ 19.3k GitHub starsSkillFree People Search to discover ICP-matching leadsApollo lead findergooseworks-ai/goose-skills · by gooseworks-aiTwo-phase Apollo.io prospecting: free People Search to discover ICP-matching leads, then selective enrichment to reveal emails/phones (credits per contact). Creates Apollo lists. Deduplicates against existing contacts by LinkedIn URL.★ 1.2k GitHub starsSkillFind decision-makers at a specific company using ApolloCompany contact findergooseworks-ai/goose-skills · by gooseworks-aiFind decision-makers at a specific company using Apollo, Crustdata, Fiber, and PDL people search via Gooseworks MCP. Given a company name and target titles, returns a list of contacts with name, title, LinkedIn URL, and location.★ 1.2k GitHub starsSkillFind company funding history, investorsCompany funding searchgooseworks-ai/goose-skills · by gooseworks-aiFind company funding history, investors, and investment details★ 1.2k GitHub starsSkillFull company intelligence reportCompany Intelligence - Comprehensive Company Reportsgooseworks-ai/goose-skills · by gooseworks-aiFull company intelligence report - overview, team, funding, products, news★ 1.2k GitHub stars

More from jdrhyne

SkillRun Codex CLI or references OpenAI Codex for code analysisCodex Skill Guidejdrhyne/agent-skills · by jdrhyneUse when the user asks to run Codex CLI (codex exec, codex resume) or references OpenAI Codex for code analysis, refactoring, or automated editing. Uses GPT-5.2 by default for state-of-the-art software engineering.★ 240 GitHub starsSkillCreating a Claude Code slash commandCommand creatorjdrhyne/agent-skills · by jdrhyneThis skill should be used when creating a Claude Code slash command. Use when users ask to "create a command", "make a slash command", "add a command", or want to document a workflow as a reusable command. Essential for creating optimized, agent-executable slash commands with proper structure and best practices.★ 240 GitHub starsSkillRecover missing conversation context after explicitContext recoveryjdrhyne/agent-skills · by jdrhyneRecover missing conversation context after explicit compaction or truncation, or when the user explicitly asks to recover prior work. Use for requests such as "where were we before compaction?" when the current thread is insufficient. Do not trigger on a generic "continue" when the current thread already provides an actionable next step.★ 240 GitHub starsSkillGenerate beautifully designed PDF reportsElegant reportsjdrhyne/agent-skills · by jdrhyneGenerate beautifully designed PDF reports with a Nordic/Scandinavian aesthetic. Use when creating polished executive briefings, analysis reports, or presentation-style PDF outputs from markdown and HTML via Nutrient DWS.★ 240 GitHub starsSkillRead Google Analytics 4 reporting data through the GA4 DataGA4 Data APIjdrhyne/agent-skills · by jdrhyneRead Google Analytics 4 reporting data through the GA4 Data API. Use for explicit GA4 property metadata, compatible metric/dimension reports, traffic analysis, key events, quotas, and bounded exports.★ 240 GitHub starsSkillRun Gemini CLI for code review, plan reviewGemini Skill Guidejdrhyne/agent-skills · by jdrhyneUse when the user asks to run Gemini CLI for code review, plan review, or big context (>200k) processing. Ideal for comprehensive analysis requiring large context windows. Uses Gemini 3 Pro by default for state-of-the-art reasoning and coding.★ 240 GitHub stars