Temu is a fast-growing marketplace known for a huge catalog at aggressive prices, spanning electronics, fashion, and home goods. Its search and listing pages carry exactly the public signals that retailers, analysts, and price trackers care about: what a product is called, what it currently costs, how shoppers rate it, and how many units have already sold. Read together across a category, those fields show what is trending and where prices are moving.

This guide shows you how to scrape Temu with JavaScript and Node.js using cheerio. You build a small, runnable scraper that fetches a Temu search results page through the Crawling API, parses the title, price, rating, sold count, and link for every product, handles the "See More" pagination, and exports the result as JSON and CSV. The whole walkthrough stays scoped to public product-listing data, and the legality section near the end is not boilerplate, so read it before you point this at any real volume.

What you will build

A Node.js script that takes a public Temu search URL, retrieves the rendered HTML through the Crawling API, and extracts a structured record for every product on the listing. We use a generic search query as the running example and pull these fields per item:

  • Title the product name shown on the card, used to identify the item and its category.
  • Price the price text as displayed, for trend monitoring and comparing similar products.
  • Rating the star rating shown on the card, a quick read on customer sentiment.
  • Sold count the "N sold" figure Temu surfaces on many cards, a rough demand signal.
  • Image URL the product thumbnail, for a visual database or downstream app.
  • Link the URL to the individual product page, so you can drill in later.

Why a plain request fails on Temu

If you request a Temu search URL with a bare HTTP client, you rarely get the product grid back. Two things work against you. First, Temu renders its listing cards in the browser with JavaScript, so the initial HTML is a near-empty shell until the page's scripts run and the goods list loads. Second, Temu challenges automated traffic aggressively: datacenter IPs and request patterns that do not look like a real browser get a CAPTCHA, get rate-limited, or get blocked before they ever reach the rendered product data.

So a working Temu scraper needs two things in one request: a browser that actually renders the page, and an IP the platform reads as a real visitor. You can assemble that yourself with a headless browser plus a pool of rotating residential proxies, but stitching those together and keeping them healthy is most of the work. The Crawling API folds both into a single call: you send it the URL, it renders the page behind a trusted IP, and it returns finished HTML for you to parse with cheerio. Because the goods list is loaded by client-side scripts, you ask the API to wait for that content before it returns.

Render tokens

Scraping JavaScript-rendered content like Temu's needs the JavaScript request token from your Crawlbase dashboard, not the plain one. The free tier includes 1,000 requests with no card, and you pay only for successful requests. See pricing for how normal and JavaScript requests are credited.

Prerequisites

You need a few things in place before writing any code. None of them take long.

Basic JavaScript and Node.js. You should be comfortable writing and running a Node script and installing packages with npm. If you are new to Node, the official docs or any beginner course will get you to the level this tutorial assumes.

Node.js 16 or later. Confirm your version with node --version. If you do not have it, install it from the Node.js website or through a version manager like nvm.

A Crawlbase account and token. Sign up, open your dashboard, and copy your JavaScript request token from the account docs page. Treat the token like a password: it authenticates your requests, so keep it out of version control.

Set up the project

Create a project folder, initialize it, and install the two libraries the scraper needs.

bash
node --version

mkdir temu-scraper && cd temu-scraper
npm init -y

npm install crawlbase cheerio

Two dependencies do the work: crawlbase is the official Node client for the Crawling API, and cheerio parses the returned HTML with a jQuery-style API so you can pull out individual fields by CSS selector. Create a file named temu-scraper.js in this folder and add the code from the steps below.

Step 1: Fetch the rendered search page

Start by getting the finished page. Import the CrawlingAPI class, initialize it with your JavaScript token, and request the search URL. Temu loads its goods list with client-side scripts, so pass ajax_wait and a page_wait delay to give that content time to appear before the API returns. Checking the status code before you parse keeps failures loud instead of silent.

javascript
const { CrawlingAPI } = require('crawlbase');

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });

const searchURL =
  'https://www.temu.com/search_result.html?search_key=wireless+earbuds';

const options = { ajax_wait: 'true', page_wait: '5000' };

api
  .get(searchURL, options)
  .then((response) => {
    if (response.statusCode === 200) {
      console.log(response.body.slice(0, 500));
    }
  })
  .catch((error) => console.error('API request error:', error));

Run the script with node temu-scraper.js and you should see real Temu product markup near the top of the body, not a stripped-down shell. That confirms rendering works before you write a single selector. The ajax_wait flag tells the API to hold for in-page requests to settle, and page_wait adds a fixed delay in milliseconds, which together give the goods list time to populate.

Crawlbase Crawling API

That first request just returned a fully rendered Temu search page without a headless browser or a proxy on your side. The Crawling API runs the page in a real browser, waits for the JavaScript goods list to load, rotates through residential IPs server-side, and handles the CAPTCHAs Temu throws at scrapers, so you get finished HTML from one call. Point it at a search query on the free tier first.

Step 2: Parse each product with cheerio

With rendered HTML in hand, load it into cheerio and walk the product cards. Temu lays each search result out in a repeating container inside the goods list, so you select every card, then read the title, price, rating, sold count, image, and link from inside it. Reading each field defensively keeps one missing value from crashing the run.

javascript
const cheerio = require('cheerio');

function parseSearchResults(html) {
  const $ = cheerio.load(html);
  const products = [];

  const cards = $(
    'div.js-search-goodsList > div.autoFitList > div.EKDT7a3v'
  );

  cards.each((index, element) => {
    const card = $(element);

    const title = card.find('h2._2BvQbnbN').text().trim();
    const price = card.find('span._2de9ERAH').text().trim();
    const rating = card.find('div._1bGyLOoB').text().trim();
    const sold = card.find('span._3VxQjs6a').text().trim();
    const imageUrl = card.find('img.goods-img-external').attr('src') || '';

    const href = card.find('a._2Tl9qLr1').attr('href');
    const link = href
      ? new URL(href, 'https://www.temu.com').href
      : '';

    if (title) {
      products.push({ title, price, rating, sold, imageUrl, link });
    }
  });

  return products;
}

A few details keep this faithful to the page. Each card lives under div.js-search-goodsList > div.autoFitList > div.EKDT7a3v, the title comes from the h2._2BvQbnbN heading, the price from span._2de9ERAH, the rating and sold count from their own small text nodes, the thumbnail from the src of img.goods-img-external, and the link from the href of a._2Tl9qLr1, resolved to an absolute URL so it works outside the page. The if (title) guard drops empty placeholder cells that sometimes sit in the grid.

Selectors drift

Temu's class names (_2BvQbnbN, _2de9ERAH, EKDT7a3v, and the rest) are generated and change without notice. Treat the selectors above as a starting template, not a contract. When a field comes back empty, re-inspect the live page in your browser's dev tools and update the selector. Periodic selector maintenance is normal for any production scraper, not a sign something is broken.

Step 3: Handle pagination and assemble the full script

Temu loads more results behind a "See More" button rather than separate numbered pages. The Crawling API can click that button for you before it returns the HTML, using the css_click_selector option, so a single render can surface a larger batch of cards. Wire the fetch, the click, and the parse into one runnable script, then write the records to disk as both JSON and CSV.

javascript
const fs = require('fs');
const { CrawlingAPI } = require('crawlbase');
const cheerio = require('cheerio');

const api = new CrawlingAPI({ token: 'YOUR_CRAWLBASE_TOKEN' });

async function crawl(url) {
  const response = await api.get(url, {
    ajax_wait: 'true',
    page_wait: '5000',
    css_click_selector: 'div.R8mNGZXv[role="button"]',
  });
  if (response.statusCode === 200) return response.body;
  console.error(`Request failed: ${response.statusCode}`);
  return null;
}

function toCsv(rows) {
  const headers = ['title', 'price', 'rating', 'sold', 'imageUrl', 'link'];
  const escape = (value) =>
    `"${String(value).replace(/"/g, '""')}"`;
  const lines = [headers.join(',')];
  for (const row of rows) {
    lines.push(headers.map((h) => escape(row[h])).join(','));
  }
  return lines.join('\n');
}

async function main() {
  const url =
    'https://www.temu.com/search_result.html?search_key=wireless+earbuds';
  const html = await crawl(url);
  if (!html) return;

  const products = parseSearchResults(html);
  fs.writeFileSync('temu-products.json', JSON.stringify(products, null, 2));
  fs.writeFileSync('temu-products.csv', toCsv(products));
  console.log(`Saved ${products.length} products to JSON and CSV`);
}

main();

Paste the parseSearchResults function from Step 2 into the same file so main can call it. The css_click_selector points at Temu's "See More" control (div.R8mNGZXv[role="button"]); the API clicks it during rendering so the returned HTML includes the extra cards that click loads. Run the script with node temu-scraper.js and you get two files: temu-products.json with the full structured records and temu-products.csv ready to open in a spreadsheet. The toCsv helper quotes every field and doubles any embedded quotes, which matters here because product titles are long and frequently contain commas.

What the output looks like

The JSON file holds one object per product in listing order, each with the title, price, rating, sold count, image URL, and link.

json
[
  {
    "title": "Wireless Earbuds Bluetooth 5.3 with Charging Case",
    "price": "$8.97",
    "rating": "4.6",
    "sold": "12K+ sold",
    "imageUrl": "https://img.kwcdn.com/product/open/earbuds-001.jpg",
    "link": "https://www.temu.com/goods-detail-g-601099527865713.html"
  },
  {
    "title": "True Wireless Sports Earphones Noise Cancelling",
    "price": "$11.49",
    "rating": "4.4",
    "sold": "3.2K+ sold",
    "imageUrl": "https://img.kwcdn.com/product/open/earbuds-002.jpg",
    "link": "https://www.temu.com/goods-detail-g-601099537192760.html"
  }
]

The CSV mirrors the same rows with a header line, so it drops straight into Excel, Google Sheets, or any data pipeline that reads delimited files.

csv
title,price,rating,sold,imageUrl,link
"Wireless Earbuds Bluetooth 5.3 with Charging Case","$8.97","4.6","12K+ sold","https://img.kwcdn.com/product/open/earbuds-001.jpg","https://www.temu.com/goods-detail-g-601099527865713.html"
"True Wireless Sports Earphones Noise Cancelling","$11.49","4.4","3.2K+ sold","https://img.kwcdn.com/product/open/earbuds-002.jpg","https://www.temu.com/goods-detail-g-601099537192760.html"

Scale across queries and staying unblocked

One search query is a demo; a real job walks several queries or categories. Build a list of search terms, fetch each through the Crawling API with the same wait-and-click options, parse it with the same function, and tag every row with its query before you export. Because every search page shares the same card structure, the parser you already wrote works across all of them without changes.

javascript
async function scrapeQueries(queries) {
  const all = [];
  for (const query of queries) {
    const term = encodeURIComponent(query);
    const url = `https://www.temu.com/search_result.html?search_key=${term}`;
    const html = await crawl(url);
    if (!html) continue;
    const rows = parseSearchResults(html).map((p) => ({ query, ...p }));
    all.push(...rows);
    await new Promise((r) => setTimeout(r, 2000));
  }
  return all;
}

scrapeQueries(['wireless earbuds', 'phone case', 'led lights']).then((rows) => {
  console.log(`Collected ${rows.length} products across queries`);
});

Even with rendering handled, Temu watches for scraper-shaped traffic, so a few habits keep a run healthy. Pace your requests: the setTimeout delay between queries above spreads traffic out instead of hammering pages in a tight loop, which is the single biggest factor in staying under rate limits. Lean on rotation: a pool of residential IPs spreads requests across many real-user addresses so no single one trips a limit or a CAPTCHA, and the Crawling API handles that for you. Read the status codes: a run that starts returning non-200 responses is telling you to back off, not noise to ignore. For the broader playbook, see how to scrape websites without getting blocked, and for the rendering side specifically, how to crawl JavaScript websites.

This pattern carries straight into pricing and research work. For turning listing data into pricing decisions, see how to use web scraping for price intelligence, and for the wider landscape of marketplace scraping, the guide to ecommerce web scraping covers patterns that reuse the same fetch-then-parse approach across other stores.

Whether scraping Temu is allowed depends on Temu's terms of service, your jurisdiction, and what you do with the data. Temu's terms restrict automated access, so scraping can run against those terms regardless of how careful your tooling is. None of the code here changes that; it just makes the technical part work. Read Temu's Terms of Use and its robots.txt, and treat both as the boundary for what you collect.

A few lines worth holding to. Collect only public product data: the title, price, rating, sold count, image, and product link that anyone can see on a search page without an account. Respect Temu's stated rate expectations and keep your request volume low enough that you are not straining its servers. Avoid personal data, including anything tied to identifiable reviewers beyond the aggregate ratings and counts shown on the page. Do not redistribute Temu's copyrighted media, such as its product photography, as if it were your own; treating an image URL as a reference is different from rehosting the image commercially.

This guide is deliberately scoped to public search and listing data because that is the line that keeps the work defensible. It does not cover anything behind a login, customer or seller personal data, order history, or any attempt to bypass authentication or a CAPTCHA you were not meant to pass. If your project needs more than public listings, or you plan to reuse the data commercially, the right path is an official agreement or a sanctioned partner or affiliate program, not a cleverer scraper. When a marketplace offers an official data channel, prefer it for volume or commercial use.

Recap

Key takeaways

  • Temu renders listings client-side and blocks hard. A plain request returns an empty shell or a CAPTCHA, so you must render the page behind a trusted IP and wait for the goods list before you parse it.
  • The Crawling API does the heavy lifting in one call. It renders the page, waits for AJAX content with ajax_wait and page_wait, rotates residential IPs, and handles CAPTCHAs, returning finished HTML.
  • cheerio extracts the fields. Select every card under the goods list, then read title, price, rating, sold count, image, and link, and expect the generated class names to drift.
  • Pagination is a "See More" click. Temu loads more results behind a button, so css_click_selector tells the API to click it during rendering and return the larger batch.
  • Stay on public data. Respect Temu's ToS and robots.txt, pace your requests, avoid personal data and logged-in content, and export clean JSON and CSV for downstream use.

Frequently Asked Questions (FAQs)

Why does a plain request return incomplete data from Temu?

Because Temu renders its product grid client-side with JavaScript and challenges automated traffic with CAPTCHAs. A raw HTTP request from a datacenter IP usually returns an empty shell or a block page rather than the product cards. To get a complete page you have to render it behind a trusted IP and wait for the goods list to load, which is what the Crawling API handles for you with ajax_wait and page_wait.

Do I need the JavaScript token to scrape Temu?

Yes. Temu's content is JavaScript-rendered, so you use the JavaScript request token from your Crawlbase dashboard rather than the plain one, and you pass the wait options so the rendered goods list is present in the response. The free tier includes 1,000 requests with no card, and JavaScript requests are credited differently from normal ones, so check the pricing page for details.

How does pagination work on Temu?

Temu loads more listings behind a "See More" button instead of separate numbered pages. The Crawling API can click that button during rendering using the css_click_selector option, so a single request can return a larger batch of cards. Point the selector at the button (for example div.R8mNGZXv[role="button"]) and the extra results come back in the same HTML you parse.

My selectors return empty values. What changed?

Almost certainly Temu's markup. Its container and class names like _2BvQbnbN, _2de9ERAH, and EKDT7a3v are generated and change without notice, so selectors that worked last month can break. Re-inspect a live page in your browser's dev tools and update the selectors. Periodic selector maintenance is normal for any production scraper.

Can I store Temu data in a database instead of CSV?

Yes. CSV and JSON are convenient for a first pass, but for larger or ongoing projects a database like PostgreSQL or MongoDB makes the data easier to query and analyze over time. Replace the writeFileSync calls in main with your database client's insert logic and keep the rest of the scraper unchanged.

How do I avoid getting blocked while scraping Temu?

Keep your per-IP request rate low, add delays between queries, and route through rotating residential IPs so no single address trips a rate limit or a CAPTCHA. The Crawling API manages rotation, a trusted IP pool, and CAPTCHA handling for you; if you build your own stack, that is the part to invest in. Watch the status codes and back off when you start seeing challenges.

Start Building

Crawl any site at scale, without fighting infrastructure.

Crawlbase handles proxies, fingerprints, and CAPTCHAs so your team ships data pipelines instead of maintaining crawl plumbing. 1,000 requests free, no card required.

Self-serve · No sales call required · Enterprise crawl volumes available