By the end of this tutorial, you will have a short Python script that turns an Enjoei search into structured rows: title, price in Brazilian reais, original price and discount tag, brand, size, used or new, category slugs, photo and the seller's public store link.

Enjoei (enjoei.com.br) is a Brazilian second-hand marketplace for fashion, electronics and lifestyle items. Its search runs on a JSON API, and in testing on 2026-10-17 a direct request with no proxy reached it without a challenge page.

In this guide, we use Python and the Apify Actor Enjoei Brazil Scraper (reventadata/enjoei-scraper). You will configure a search, run the Actor and filter by price, condition and keyword. The row shown below is a sample from a capture dated 2026-10-17, parsed with the Actor's own parser.

⏱️ Time estimate: one sitting: install one package, set one token, run three short Python blocks.

Prerequisites

To follow this tutorial, you will need:

  1. Python 3.11+ (apify-client 3.3.0 requires it).
  2. The Apify API client for Python.
  3. An Apify account and its API token, which sits on the API & Integrations page in Apify Console.

Install the client:

pip install "apify-client~=3.0" 

The code reads the token from an environment variable:

export APIFY_API_TOKEN="your_apify_token_here" 

What you'll achieve

You will turn an Enjoei search into one row per listing. Each row carries itemId and url, title, price and originalPrice in reais, discountPercentage from the listing's discount tag, brand, size, isUsed, shipping fields, three levels of category slugs, imageUrl, and the seller's public storeName and storeUrl.

Note: Per the Actor's README, no phone numbers, e-mail addresses, personal IDs or addresses are collected. The seller appears only as the public store name and link that Enjoei shows on the listing.

Example use case: Suppose you track used iPhone 14 prices between R$ 1,500 and R$ 3,000. You would set searchQueries: ["iphone 14"], condition: "used", minPrice: 1500, maxPrice: 3000 and sortBy: "priceAsc", and every saved row would be a used listing inside that band, cheapest first.

Step 1: Access Enjoei Brazil Scraper

Go to the Enjoei Brazil Scraper page on Apify Store. The README lists every input and output field. In this tutorial we drive the Actor from Python, so everything after this step happens in code.

Step 2: Configure the search

The main input is searchQueries: one Enjoei search per term, written as you would type it in Enjoei's search box (for example iphone, vestido farm or tenis vans). maxItems (default 100, minimum 1) caps the rows saved across all queries.

Here is the setup. It searches iphone and saves at most 10 rows, the same values the Actor's input form is prefilled with:

import os from apify_client import ApifyClient # 1. Initialize ApifyClient with the token from the environment client = ApifyClient(os.environ["APIFY_API_TOKEN"]) # 2. Define the search: one keyword, at most 10 rows run_input = { "searchQueries": ["iphone"], "maxItems": 10, } 

💡 Tip: one term per search. Put vestido farm and iphone 14 as two entries, not one string; each entry is a separate Enjoei search.

No proxy setting is needed in run_input: by default the Actor sends direct requests, and Apify Proxy is used only if you turn it on in proxyConfiguration.

Step 3: Add optional filters and sorting

These inputs narrow or order the results:

Parameter Type What it does
sortBy select relevance (default, Enjoei's own order), priceAsc, priceDesc or discountDesc.
minPrice / maxPrice integer Price band in reais, both ends included.
condition select all (default), used or new.
requireKeywordMatch boolean Keep only rows whose title contains every word of the query, ignoring case and accents.

The price band and condition are sent to Enjoei with the search, and the Actor checks them again on each row before saving it.

Three combinations that work:

  • Cheapest used phones first: searchQueries: ["iphone 13"], condition: "used", sortBy: "priceAsc".
  • Biggest discount tags: searchQueries: ["vestido farm"], sortBy: "discountDesc".
  • New items under a budget: condition: "new", maxPrice: 200.

⚠️ sortBy and condition only take the values listed above. Anything else is refused before the run starts.

Step 4: Run the Actor

Now start the run with client.actor("reventadata/enjoei-scraper").call(run_input=run_input). The call starts the Actor on Apify and waits for it to finish before returning the run:

# 3. Start the Actor run and wait for it to finish print("Starting Enjoei extraction...") run = client.actor("reventadata/enjoei-scraper").call(run_input=run_input) if run is None or run.status != "SUCCEEDED": status = run.status if run else "unknown" raise SystemExit(f"Run did not succeed (status: {status}).") # 4. Fetch the saved listings items = client.dataset(run.default_dataset_id).list_items().items print(f"Retrieved {len(items)} listings.\n") 

Behind the scenes:

  • For each term, the Actor calls Enjoei's search API over GET and reads JSON back; no page is rendered.
  • It asks for 30 listings per page and follows the cursor to the next page.
  • It turns each listing into a row, applies your filters, skips any listing already saved under an earlier term, and saves the rest.
  • It stops at maxItems, or when Enjoei has no further page for the term.

Step 5: View and download results

Loop over the rows to print the parts you need:

for item in items: print(item["title"]) print(f" Price: R$ {item['price']:.2f}") if item.get("discountPercentage") is not None: print(f" Discount tag: {item['discountPercentage']}% (was R$ {item.get('originalPrice')})") print(f" Brand: {item.get('brand') or 'none'} | Used: {item['isUsed']}") print(f" Store: {item['storeName']} ({item['storeUrl']})") print(f" Link: {item['url']}") print("-" * 40) 

Here is one row, a sample from a capture dated 2026-10-17, parsed with the Actor's own parser for the search term iphone. A saved row also carries scrapedAt, the UTC time the row was built:

{ "itemId": "148674892", "title": "iphone 14 128gb vermelho", "url": "https://www.enjoei.com.br/p/iphone-14-128gb-vermelho-148674892", "price": 2450.0, "originalPrice": 2786.0, "discountPercentage": 12, "brand": "apple", "size": null, "isUsed": true, "freeShipping": false, "shippingType": "charge_by_weight", "department": "info-eletro", "category": "info-eletro-telefonia", "subCategory": "info-eletro-telefonia-outros-celulares", "imageUrl": "https://photos.enjoei.com.br/public/300x300/czM6Ly9waG90b3MuZW5qb2VpLmNvbS5ici9wcm9kdWN0cy85MTI2MjkzLzk4OWM1Y2UxMjQxMzhlYmNlNWM2NmY2MTk1YmVhOWFkLmpwZw", "storeName": "tatiana", "storeUrl": "https://www.enjoei.com.br/@tatiana-30723d", "matchedKeyword": "iphone" } 

Apify datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL.

Understanding your results

The fields that matter most:

  • price and originalPrice: the asking price and the earlier price, in reais, as numbers. In the sample, R$ 2,450 against R$ 2,786.
  • discountPercentage: read from the listing's own discount tag (12% in the sample), not calculated by the Actor. Null when the listing carries no percentage tag.
  • isUsed: true for second-hand, false for new. condition filters on it.
  • brand and size: as Enjoei sends them, so apple stays lowercase. A listing with no size, like this phone, gets size: null.
  • department, category, subCategory: Enjoei's category slugs, broad to narrow. The sample sits under info-eletro-telefonia-outros-celulares.
  • freeShipping and shippingType: the shipping flag and Enjoei's shipping type (charge_by_weight in the sample).
  • storeName and storeUrl: the seller's public store name and page.
  • matchedKeyword: which of your terms found the row.
  • itemId, url and imageUrl: Enjoei's listing ID, the listing page, and a 300x300 photo link.

Pro tips

  1. Let Enjoei filter first. minPrice, maxPrice and condition go to Enjoei's search as parameters; the Actor's own check on each row is a second pass.
  2. Know what the keyword check does. requireKeywordMatch only asks that the title contain each word: capinha de celular iphone 12 pro max, a phone-case title captured on 2026-10-17, passes an iphone check. For phones only, use a precise query such as iphone 14 128gb and a price floor.
  3. Batch queries in one run. Put several terms in searchQueries. A listing kept under one term is not saved again under the next.
  4. Sort before you cap. With maxItems set, sortBy: "priceAsc" or "discountDesc" decides which listings make the cut.
  5. Fetch only the columns you need. In Python, client.dataset(run.default_dataset_id).iterate_items(fields=["title", "price", "url"]) fetches just those fields.
  6. Start small. Keep maxItems: 10 until your downstream code handles the fields, then raise it.

Troubleshooting

  • Zero rows: a run that is blocked or cannot read a page ends FAILED; an over-strict filter or a query with no results ends SUCCEEDED with an empty dataset and a status message saying so. Widen the price band or drop requireKeywordMatch.
  • Fewer rows than maxItems, no rejections: Enjoei ran out of results for the term; the Actor stops a term when a page comes back with fewer than 30 listings.
  • Input refused before the run starts: sortBy or condition holds a value outside its list.
  • Fewer rows than expected: read the summary line near the end of the log, Enjoei run finished: pushed N rows. Rejections summary: {...}; it counts each reason a listing was dropped (duplicate_id, price_out_of_range, keyword_mismatch, condition_not_used or condition_not_new, unparseable_node). From Python: client.log(run.id).get().

For help, use the Actor's Store page.

Pricing and next steps

For current pricing and details, see the Store page.

To repeat a search, save the input as a task and add an Apify schedule, which runs an Actor or task on a cron expression (0 8 * * * is every day at 8 AM). To react when a run finishes, add a webhook on ACTOR.RUN.SUCCEEDED; its payload carries the run object, including defaultDatasetId.

Which brands or items are you planning to track on Enjoei? Drop a comment! 💬