Yupoo Scraper
Under maintenancePricing
from $6.00 / 1,000 product scrapeds
Yupoo Scraper
Under maintenanceScrape Yupoo stores, categories, or single albums: images, titles, descriptions, and album URLs.
Pricing
from $6.00 / 1,000 product scrapeds
Rating
0.0
(0)
Developer
Ibrahim
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
🖼️ Yupoo Scraper — Albums, Images, Titles, Descriptions
Point it at a Yupoo store, category, or single album and get clean structured data back. No login, no browser automation, no guesswork.
This Actor does one job properly: it reads Yupoo pages and hands you exactly what is on them — image URLs, titles, descriptions, album URLs. It does not score, rank, rewrite, or "enrich" anything. What you see in the output is what the seller published.
💡 Who is this for?
- Catalog and inventory work — pulling a supplier's full album list into a spreadsheet or database.
- Developers building on top of Yupoo data who want a clean JSON feed instead of writing an HTML parser.
- Anyone doing bulk image collection from albums that hold 30–100 photos each.
🎯 What it does
| You give it | You get back |
|---|---|
A store URL — https://STORE.x.yupoo.com/albums | Every album in the store |
A category URL — https://STORE.x.yupoo.com/categories/5280382 | Every album in that category |
A single album URL — https://STORE.x.yupoo.com/albums/246388734?uid=1 | That one album |
A bare store name — chaosmade20 | Same as the store URL |
Mix as many as you like in one run — each URL is detected automatically, no mode switch to set.
⚡ Quick start
Just the album URLs from a store (fastest and cheapest — reads index pages only, never opens an album):
{ "urls": ["chaosmade20"], "albumUrlsOnly": true }
Everything from one category:
{ "urls": ["https://chaosmade20.x.yupoo.com/categories/5280382"] }
Only the images from a single album:
{"urls": ["https://chaosmade20.x.yupoo.com/albums/246388734?uid=1"],"fields": ["images"]}
💡 Start small. Run one album first and check the output is shaped the way you need before pointing it at a store with thousands of albums. Yupoo stores vary a lot — some put their whole catalog on one page, some paginate, some leave descriptions empty.
📤 Output
One record per album, pushed straight to the Dataset — no file keys to hunt for, no post-processing step. Export as JSON, CSV, or Excel from the Storage tab, or pull it from the API.
{"store": "chaosmade20","albumUrl": "https://chaosmade20.x.yupoo.com/albums/246388734?uid=1","title": "Washed Black Tee","description": "https://example.com/products/tee","images": ["https://photo.yupoo.com/chaosmade20/95d571333a/big.jpg","https://photo.yupoo.com/chaosmade20/a87080adfd/big.jpg"]}
store and albumUrl are always present. Everything else is whatever you selected in fields — unselected fields are left out entirely, not returned as empty strings or nulls.
⚠️ Read this before you import images anywhere
Yupoo image URLs are hotlink-protected. This is a property of Yupoo, not of this Actor, and it will affect you no matter which scraper you use. Measured directly:
| How the image is requested | What Yupoo returns |
|---|---|
No Referer header | HTTP 567 — a 7 KB HTML error page, not an image |
Referer from your own site | HTTP 567 — blocked |
Referer: https://STORE.x.yupoo.com/ | HTTP 200 — the real JPEG (337 KB) |
What this means in practice:
- Pasting an image URL straight into a browser tab shows a broken image. The URL is still correct.
- Embedding these URLs directly in a storefront or web page will not work.
- Downloading them does work — send a
Refererheader ofhttps://STORE.x.yupoo.com/with the request:
curl -H "Referer: https://chaosmade20.x.yupoo.com/" \"https://photo.yupoo.com/chaosmade20/95d571333a/big.jpg" -o image.jpg
Once downloaded, host the files wherever you like and the restriction no longer applies.
✅ The Actor already fixes this for you
Rehosting is on by default. Every image is downloaded from Yupoo using the referer that defeats the block, re-uploaded, and returned as a working URL in the images field — links that display anywhere, in any storefront, with no referer tricks. You do not have to configure anything.
To use your own image-host account instead of the built-in one, set imageHostApiKey. To skip rehosting entirely and get raw Yupoo URLs — much faster and lighter — set rehostImages: false.
{"urls": ["https://chaosmade20.x.yupoo.com/albums/246388734?uid=1"],"imageHostApiKey": "YOUR_KEY"}
imageHost | Get a key at | Notes |
|---|---|---|
freeimage | freeimage.host | Default. Chevereto API. |
imgbb | api.imgbb.com | Supports link expiry. |
custom | — | Any Chevereto-compatible v1 endpoint via uploadEndpoint. |
How it behaves:
- Keys are validated before the run starts. A wrong or blocked key fails in seconds instead of dying three thousand images into a long job.
- If no key is available at all, the run still completes — you get every album with its original Yupoo URLs, plus a warning in the log. The scrape is never lost because of an image-host problem.
- A failed upload keeps its original Yupoo URL rather than dropping out of the results. Your record is never left short — but a run can return a mix of rehosted and original URLs, so make sure your import handles both.
- The run log reports the split — how many images were rehosted and how many kept their originals.
imageLinkExpirySeconds: 0(the default) keeps images forever. Expiry is an ImgBB feature and is ignored by hosts that do not support it.- Resolution is preserved, but the file is re-encoded. Measured on freeimage.host: a 1080×1080 source came back 1080×1080, with the JPEG about 18% smaller. You keep full dimensions; you do not get a byte-identical copy. If you need the exact original file, download it yourself with the referer method above.
⚠️ Free image hosts have their own limits and terms. Bulk-uploading tens of thousands of images may get you rate limited or blocked. Uploads go to your own account, so quotas and content rules are between you and that host. If one blocks you, switch
imageHostrather than rewriting anything.
💰 What a run costs
There is no charge from this Actor for rehosting — you bring your own free image-host key, and that host bills you nothing on its free tier. The cost that matters is Apify platform usage, which is driven by requests and data transfer. Measured from real runs:
| Mode | Requests | Data moved |
|---|---|---|
albumUrlsOnly: true | 1 per ~77–120 albums (index pages only) | Negligible |
rehostImages: false | 1 per album, plus index pages | ~50–150 KB per album |
| Default (rehosting on) | 1 per album + 2 per image | ~780 KB per image |
The default row is the one to plan around. Each image is downloaded (~340 KB) and re-uploaded as base64, which inflates it by roughly a third (~440 KB). Albums hold 30–100 images, so a single album moves 25–75 MB.
A worked example: 100 albums averaging 50 images each is 5,000 images ≈ 3.9 GB of transfer and 10,000 upload round trips. That is a long, heavy run.
How to keep it cheap:
- Use
albumUrlsOnlywhen you only need the catalog structure — it is roughly 100× cheaper than a full scrape. - Set
rehostImages: falsefor exploratory runs. Turn it back on for the albums you are actually importing. - Use
maxImagesPerAlbumto cap rehosting if you only need a few photos per product. - Set
fieldsto just what you need. It does not reduce requests (all fields come from the same page) but it does shrink your dataset. - Always test on one album first and read the run's usage before scaling to a store.
📥 Input reference
| Field | Type | Default | Description |
|---|---|---|---|
urls | array | — | Required. Album, category, or store URLs — mix freely. Bare store names work. |
fields | array | all three | Any of images, title, description. |
albumUrlsOnly | boolean | false | List album URLs without opening each album. Dramatically faster. |
maxAlbumsPerStore | integer | 100 | Per store or category URL. 0 = no limit. |
maxPages | integer | 5 | Album-index pages to walk. |
imageSize | string | big | small, medium, or big. Costs no extra requests — see below. |
maxImagesPerAlbum | integer | 0 | 0 = no limit. |
albumConcurrency | integer | 4 | Album pages fetched at once. |
requestDelayMs | integer | 250 | Minimum pause before each request. Raise if you see 429s. |
maxRetries | integer | 3 | Retries per failed request. |
rehostImages | boolean | true | On by default. Re-uploads images so the URLs work anywhere. Set false for a faster, lighter run. |
imageHost | string | freeimage | freeimage, imgbb, or custom. |
imageHostApiKey | string | — | Optional — use your own image-host account. Stored as a secret. |
uploadEndpoint | string | — | Only for imageHost: "custom". |
imageLinkExpirySeconds | integer | 0 | 0 = never expire. ImgBB only. |
imageConcurrency | integer | 4 | Images downloaded and uploaded at once. |
proxyConfiguration | object | Apify Proxy | Yupoo is served from China and can be slow or unreachable from some regions. |
🔍 Notes from the field
Things learned the hard way, documented so you do not have to rediscover them:
- Album URLs need their
uidquery parameter. Strip it and Yupoo replies200 OKwith a body reading "this album is not exist" — not a404. A naive scraper writes that as a valid empty record. This Actor keeps the parameter and detects the body anyway, so a dead album is reported rather than silently returned. - Two gallery layouts are live simultaneously. Older stores render
album3__maincards, newer onesalbum__main. Both are handled. - Titles live in three different places depending on layout. When a page forces a fall back to
<title>, that value carries a| album | STORE | …suffix, which is stripped before output. - Not every store paginates. Some render the entire catalog on page one. Pagination stops as soon as a page yields nothing new instead of walking empty pages.
- Albums are large. Across the stores tested, albums held roughly 30–100 images each. That is why
maxImagesPerAlbumdefaults to no limit — a silent cap would quietly discard most of your data. - Image size is a URL rewrite. Yupoo serves
/small.jpg,/medium.jpg, and/big.jpgfrom the same path, so choosingbigcosts nothing extra. - Descriptions are freeform. Sellers write whatever they want there — sometimes a product link, sometimes measurements, sometimes nothing. You get the text as-is.
🔌 API integration
Replace YOUR_TOKEN with your token from Apify Console → Settings → API & Integrations.
Small jobs — one call, results returned inline
For a single album or a small category, run synchronously and get the data straight back:
curl -X POST "https://api.apify.com/v2/acts/Vw1EDApmqzgczTA1a/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":["https://chaosmade20.x.yupoo.com/albums/246388734?uid=1"],"fields":["images"]}'
The response is the array of results. No polling, no second request.
Large jobs — start, poll, fetch
Big stores take a while, so start the run asynchronously:
curl -X POST "https://api.apify.com/v2/acts/Vw1EDApmqzgczTA1a/runs?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":["chaosmade20"],"maxAlbumsPerStore":0}'
Save data.id (the run ID) and data.defaultDatasetId from the response, poll until the status is SUCCEEDED, then download:
$curl "https://api.apify.com/v2/datasets/DATASET_ID/items?token=YOUR_TOKEN&format=json" -o output.json
Full script (Node.js)
Save as scrape.mjs and run with node scrape.mjs.
const ACTOR = 'Vw1EDApmqzgczTA1a';const TOKEN = 'YOUR_APIFY_TOKEN';const BASE = 'https://api.apify.com/v2';const INPUT = {urls: ['chaosmade20'], // store, category, or album URLsfields: ['images', 'title', 'description'],maxAlbumsPerStore: 0, // 0 = no limit};const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));const getJson = async (url) => (await fetch(url)).json();// 1. Start the runconst start = await (await fetch(`${BASE}/acts/${ACTOR}/runs?token=${TOKEN}`, {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(INPUT),})).json();const { id: runId, defaultDatasetId: datasetId } = start.data;console.log('Run:', runId);// 2. Poll until it finisheslet status = start.data.status;while (['RUNNING', 'READY'].includes(status)) {await wait(30_000);({ data: { status } } = await getJson(`${BASE}/actor-runs/${runId}?token=${TOKEN}`));console.log('Status:', status);}if (status !== 'SUCCEEDED') throw new Error(`Run ended as ${status}`);// 3. Download the resultsconst items = await getJson(`${BASE}/datasets/${datasetId}/items?token=${TOKEN}&format=json`);const { writeFileSync } = await import('node:fs');writeFileSync('output.json', JSON.stringify(items, null, 2));console.log(`Saved ${items.length} albums to output.json`);
🧰 What this Actor does not do
Stated plainly so you can judge fit before spending anything:
- No AI or LLM processing. Prices, SKUs, and sizes are not extracted from description text. Yupoo has no price field — sellers type prices into freeform text, so any extraction is guesswork. Across the stores tested, price text was absent entirely from 1 in 4 of them. You get the raw description and can parse it yourself if your catalog is consistent.
- No video extraction. Images only.
- No password-protected albums.
🖥️ Running it locally
$npm install
$npm test
Put your input in storage/key_value_stores/default/INPUT.json, then:
$node src/main.js
Results land in storage/datasets/default/.
⚖️ Responsible use
This Actor reads publicly accessible pages. It does not bypass authentication, rate limits, or access controls, and it ships with a request delay and conservative concurrency — please leave them in place.
You are responsible for what you do with the output. Images and listings on Yupoo are usually someone else's intellectual property. Republishing or selling based on scraped content may carry legal obligations depending on the content and your jurisdiction. Check before you build on it.