Gallery scraper

Bulk download images from login-protected gallery websites using an attached browser session.

How to use it

Claude Code
  1. Run the line below. It pulls the whole folder into ~/.claude/skills/gallery-scraper, including the files SKILL.md points to.
  2. Describe your job in plain words. Claude Code follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit jdrhyne/agent-skills/clawdbot/gallery-scraper#main ~/.claude/skills/gallery-scraper

For one project only, change the path to .claude/skills/gallery-scraper. This skill also uses urls.txt — copying SKILL.md alone won't be enough. See the folder on GitHub.

Claude (web or desktop app)
  1. On this page open ⋯ → Download .md.
  2. Save it as SKILL.md in a folder, zip the folder, then Customize → Skills → + → Create skill → Upload a skill.
  3. Pick the file and Save. Claude shows the name and description and runs a security scan.
  4. Check the skill is switched on.
  5. Start a new chat and describe your job in plain words. The AI follows the skill from there.
ChatGPT or another app
  1. ChatGPT: make a Project and paste it into Instructions.
  2. Neither? Paste it at the top of a new chat — it works for that chat.
Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Source of Gallery scraper

Show the full text225 lines
namedescriptionpermissions
gallery-scraperBulk 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. - 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.

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 

Discussion

Alternatives

Also in Company & contact dataSee all 138 in Sales →