# Goofish Xianyu Scraper (闲鱼 Idlefish) (`dami_studio/goofish-scraper`) Actor

Turn Goofish (闲鱼 / Xianyu) links into a clean table. Paste listing links or a seller profile and get title, price, condition, want and view counts, seller nickname and good-review rate, location, every photo and the listing URL. No account needed. You only pay for listings that come back.

- **URL**: https://apify.com/dami\_studio/goofish-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 item returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Goofish (闲鱼) Xianyu Scraper

Paste Goofish listing links, or a seller's profile link, and get back a clean table: title, asking
price, what the seller says the condition is, how many people have saved it, how many have looked
at it, where it is, who is selling it and what their good-review rate is, every photo, and the
link back.

Goofish (闲鱼, Xianyu, sometimes written Idlefish) is Alibaba's second-hand marketplace. It is
where most of China's used-goods trade actually happens, and almost none of it is visible from
outside the app.

**Read the limitations section before you buy.** There is a real one at the top of it, and it will
decide whether this is useful to you.

***

### What you put in

Two fields. Use either, or both in the same run.

```json
{
  "itemUrls": [
    "https://www.goofish.com/item?id=1051964780358",
    "1080166744321"
  ],
  "sellerUrls": [
    "https://www.goofish.com/personal?userId=605970805"
  ],
  "maxItems": 100,
  "includeDescription": true
}
```

- **Item links**: a full `goofish.com/item?id=...` link, the `h5.m.goofish.com` mobile version, or
  just the number. All three work.
- **Seller links**: a `goofish.com/personal?userId=...` link, or just the number. Every listing on
  that profile is walked, twenty at a time, and returned in exactly the same shape as an item link.
- **Maximum listings**: a hard stop for the whole run. Item links are done first, then sellers.
- **Include the seller's description**: leave it on for the full text, turn it off for a narrower
  table.

If you run it with nothing filled in, you get one clearly-labelled free example row telling you
what to put where. You are not charged for it.

***

### What you get back

One row per listing. A real one, from a live run:

```json
{
  "itemId": "1069417084034",
  "url": "https://www.goofish.com/item?id=1069417084034",
  "title": "上市公司碳排放量/碳排放强度2010-2024",
  "price": 2,
  "originalPrice": 0,
  "currency": "CNY",
  "shippingFee": 0,
  "conditionLevel": 9,
  "conditionText": "9/10",
  "wantCount": 173,
  "viewCount": 1851,
  "collectCount": 67,
  "soldCount": 0,
  "quantity": 9849,
  "city": "上海",
  "province": "上海",
  "district": "黄浦区",
  "sellerId": "2222727101158",
  "sellerNick": "南***鱼",
  "sellerGoodReviewRate": "98%",
  "sellerItemCount": 679,
  "sellerSoldCount": 3363,
  "sellerReplyRate24h": "99%",
  "sellerLastSeen": "45分钟前来过",
  "sellerUrl": "https://www.goofish.com/personal?userId=2222727101158",
  "categoryId": 50023914,
  "status": "在线",
  "listedAt": "2026-07-21T15:34:17.000Z",
  "images": ["https://img.alicdn.com/bao/uploaded/i2/O1CN01eHDc0ukvCtC2b8G8_..."],
  "imageCount": 2,
  "description": "...",
  "sourceItemRecord": true,
  "sourceDetailPage": true
}
```

A few of those need explaining:

- **`price` is what the seller is asking, in yuan.** `originalPrice` is the "was" figure where the
  seller set one, and is usually null. Nothing is converted to another currency; converting it
  would mean picking an exchange rate and quietly baking it into your data.
- **`conditionLevel` is Goofish's own 10-point scale**, where 10 means brand new. `conditionText`
  is a plain reading of it. The number comes straight from the listing; we do not guess it from
  the photos or the wording.
- **`wantCount` and `viewCount` are the real interest signals.** On a second-hand marketplace they
  tell you more than the price does: a ¥3 listing with 2,119 wants and 10,355 views is a very
  different object from a ¥2,569 camera with 0 and 0.
- **`sellerGoodReviewRate`** is the percentage Goofish shows on the seller's own page.
  `sellerLastSeen` is their "last active" string, in Chinese, exactly as the site renders it.
- **Nicknames arrive masked** (`南***鱼`). That is Goofish masking them, not us. There is no
  unmasked version available to anyone who is not logged in.
- **`sourceItemRecord` and `sourceDetailPage`** say which of the two Goofish endpoints answered for
  that row. Both true is a complete row. If one is false, the fields it owns are null rather than
  guessed, and you can see at a glance that it happened. See the note about old listings below.
- **`foundViaSeller`** appears only on rows that came from a seller profile *and* whose own seller
  id matches the profile you asked for.

#### Rows that are not listings

Some rows are free diagnostics, and they always carry `_diagnostic: true` and `charged: false`:

| `reason` | What happened |
|---|---|
| `not_found` | The listing is gone: sold, withdrawn or deleted. |
| `unreadable_input` | No id could be read from that line. |
| `seller_mismatch` | The listings returned for that profile belong to other people, so none were kept. |
| `no_listings` | The seller has nothing on show right now. |
| `blocked` | Goofish would not answer for that one. Usually clears in a few minutes. |
| `all_exits_refused` | Goofish refused every attempt the run was allowed to make, so the rest were not attempted. |
| `charge_limit_reached` | The run's cost limit was too low to return even one listing, so nothing was fetched. |

Filter them out with `?filter=` on the dataset, or just ignore any row where `_diagnostic` is set.

***

### Limitations: read this part

**There is no keyword search, and that is not an oversight.** Goofish's keyword search endpoint
requires a logged-in account. Signed out it answers with a login demand, and it does so for
everything, including a real Chrome
browser on a home connection, which renders an empty results page. This Actor does not create
accounts and does not ask you for yours, so it cannot search. **If what you need is "find me all
listings for X", this is the wrong tool and you should not buy it.** What it does instead is turn
links you already have (from the app, from a shared link, from a seller you are watching) into
structured data.

Other things worth knowing before you spend anything:

- **Old listings come back thinner, and the row tells you when.** Goofish keeps the detail page for
  long-archived listings but drops the underlying item record, so those rows have the title, price,
  want and view counts and the seller, while `conditionLevel`, `images`, `province`, `district`,
  `categoryId`, `quantity` and `description` are null. Measured on a 150-listing seller walk: 140
  rows complete, 10 thin, all ten of them listings from previous years. `sourceItemRecord: false`
  is the flag. It is never guessed at and never filled in with a placeholder.
- **Seller profiles are capped by Goofish at what the profile itself shows.** Sold and delisted
  items are not on there, so you get the seller's current shelf, not their history.
- **A seller's listing count and sold count come from their profile header.** They are Goofish's
  numbers, not a count of the rows returned, and the two can differ.
- **`soldCount` is 0 on most listings**: Goofish does not populate it consistently, and a 0 there
  does not mean nothing sold. `wantCount` and `viewCount` are the two that are reliably present.
- **Everything is in Chinese**, because that is what is on the listings. Titles, cities, condition
  labels and the seller's "last seen" string are passed through unchanged rather than
  machine-translated into something you cannot check.
- **Prices move.** A Goofish seller can change the asking price at any time, and many relist the
  same object repeatedly. A row is a snapshot of the moment it was fetched.
- **A listing that goes away mid-run comes back as a free `not_found` row**, not a gap and not a
  silent drop.

***

### What it costs you

A flat price per listing that actually comes back, plus a small fee per run. There are no
volume tiers to work out and no monthly minimum.

**Free rows stay free.** The example row, every diagnostic in the table above, and every listing
Goofish could not return are written to your dataset without being charged. A run that finds
nothing charges you for nothing beyond starting it.

If you set a maximum cost on the run and it is too low to return a single listing, the Actor stops
before fetching anything and tells you, rather than spending the budget on a partial answer.

***

### FAQ

**Can it search Goofish by keyword?**
No. Goofish requires a logged-in account for search, and this Actor does not use accounts. It works
from listing links and seller profile links.

**Do I need a Goofish account, a cookie or a token?**
No. You paste links; nothing else is asked of you and nothing is stored.

**Can I give it a link copied from the Goofish app?**
Yes. App share links, `www.goofish.com`, `h5.m.goofish.com` and bare numeric ids all work.

**What is Xianyu, and is it the same thing as Goofish?**
Same marketplace. 闲鱼 (Xianyu) is the Chinese name, Goofish is the name on the international
domain, and Idlefish is a common transliteration. All three refer to Alibaba's second-hand app.

**Why are the seller nicknames full of asterisks?**
Goofish masks them for anyone who is not signed in. That is the platform's behaviour and it applies
to every tool, including this one.

**How many listings can I pull from one seller?**
As many as their profile shows, subject to your maximum-listings setting. Profiles are walked
twenty at a time until the pages stop producing new listings.

**Are the prices in US dollars?**
No, yuan (CNY), as listed. Nothing is converted.

**Can I run it on a schedule to watch a price?**
Yes. Point it at the item links you care about and schedule it; each run is a fresh snapshot with a
timestamp.

**What happens if Goofish blocks a request?**
The Actor tries again. If every attempt it is allowed to
make gets refused, it stops, says so in the log, and writes a free row explaining why. It does not
charge you for the attempts.

***

### Output fields

| Field | Meaning |
|---|---|
| `itemId`, `url` | The listing's id and its link |
| `title` | The listing title, as written |
| `price`, `originalPrice`, `currency` | Asking price and any "was" price, in CNY |
| `shippingFee` | Postage as listed, 0 when free |
| `conditionLevel`, `conditionText` | Condition on Goofish's 10-point scale, 10 = brand new |
| `wantCount`, `viewCount`, `collectCount`, `soldCount` | Interest and sales counters |
| `quantity` | Units the seller says are available |
| `city`, `province`, `district` | Where the item is |
| `sellerId`, `sellerNick`, `sellerUrl` | Who is selling it |
| `sellerGoodReviewRate`, `sellerReplyRate24h`, `sellerLastSeen` | Seller reputation and responsiveness |
| `sellerItemCount`, `sellerSoldCount` | How much they list and how much they have sold |
| `categoryId` | Goofish's own category id |
| `status` | Listing state as Goofish labels it |
| `listedAt` | When it was posted, ISO 8601 |
| `images`, `imageCount` | Every photo URL on the listing |
| `description` | The seller's description, when you leave that option on |
| `sourceItemRecord`, `sourceDetailPage` | Which endpoints answered for this row |
| `foundViaSeller` | Present when the row came from a seller profile you asked for |

# Actor input Schema

## `itemUrls` (type: `array`):

Goofish listing links, one per line. A full link like https://www.goofish.com/item?id=995598771021 works, and so does the bare number. Each one comes back as a full row.

## `sellerUrls` (type: `array`):

Goofish seller profile links, one per line, like https://www.goofish.com/personal?userId=605970805 — or just the number. Every listing on the profile is walked and returned in the same shape as an item link, up to the limit below.

## `maxItems` (type: `integer`):

Stop after this many listings across the whole run. Item links are done first, then sellers. Counts only listings that actually came back — rows explaining a dead link are free and do not count.

## `includeDescription` (type: `boolean`):

Keep the full listing description in each row. Turn it off for a narrower table when you only want prices and counts.

## `proxyConfiguration` (type: `object`):

Optional. Leave this alone unless you need the run to go out through a particular network. Your own proxy servers are used exactly as given.

## Actor input object example

```json
{
  "itemUrls": [
    "https://www.goofish.com/item?id=995598771021"
  ],
  "sellerUrls": [],
  "maxItems": 100,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per Goofish listing: item id and link, title, asking price and original price, condition on the 10-point scale, want / view / collect / sold counts, quantity, city, province and district, seller id, nickname, good-review rate, listing count and reply rate, category id, listing date, every image URL, and the description. A listing that has been sold or withdrawn, a link nothing can be read from, and a seller with no public profile each write a free row explaining what happened instead. Empty input writes one free example row.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "itemUrls": [
        "https://www.goofish.com/item?id=995598771021"
    ],
    "sellerUrls": [],
    "maxItems": 100,
    "includeDescription": true,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/goofish-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "itemUrls": ["https://www.goofish.com/item?id=995598771021"],
    "sellerUrls": [],
    "maxItems": 100,
    "includeDescription": True,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/goofish-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "itemUrls": [
    "https://www.goofish.com/item?id=995598771021"
  ],
  "sellerUrls": [],
  "maxItems": 100,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call dami_studio/goofish-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/goofish-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/H8y2wo45N6ReyC8XM/builds/4Kjof1KBkKOeAEH6H/openapi.json
