# Likee Scraper — Videos, Profiles & Creator Discovery (`scrapelabmax/likee-scraper`) Actor

Scrape Likee profile videos + history, discover creators by country, and pull trending-hashtag metadata — with engagement filters, dedup, creator analytics, and incremental monitoring. Plain HTTP, no browser.

- **URL**: https://apify.com/scrapelabmax/likee-scraper.md
- **Developed by:** [Scrapelab Max](https://apify.com/scrapelabmax) (community)
- **Categories:** Social media, Videos
- **Stats:** 5 total users, 4 monthly users, 99.7% runs succeeded, 0 bookmarks
- **User rating**: 3.32 out of 5 stars

## Pricing

from $5.00 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Likee Scraper

Apify Actor that scrapes **Likee** (like-video.com) profile videos and history, discovers
creators by country, and pulls trending-hashtag metadata — with engagement filters, dedup,
creator analytics, and incremental ("only new") monitoring.

Plain HTTP only (`fetch` + `undici` `ProxyAgent`). No browser, no Playwright/Puppeteer/Crawlee,
no reverse-engineered request signing.

### What it scrapes (Tier A — available in v1)

- **Creators via video share link** (`videoUrls`) — paste any `likee.video/v/<id>` share link
  (in the app: video → Share → Copy link). The actor scrapes that video (including a
  **playable mp4 URL**, which the profile-videos API never returns) and, by default
  (`hydrateVideoUrlAuthors: true`), the poster's full profile videos and analytics.
  Untick it for a cheap single-video lookup. Caveats: the share page has **no view count**
  (`stats.views` is `null`, so a `minViews > 0` filter drops the share-page record), and
  like/comment/share counts are parsed from humanized strings ("84.77K" → 84770, approximate).
- **Profile videos + history** — every video on a profile, paginated, reverse-chronological
  (`videoSort: "latest"` is the only supported order); reached via a numeric `uid` (Advanced
  input) or automatically via a video share link's hydrated poster (see above).
- **Creator discovery** — Likee's recommended-producer list, filterable by country and minimum
  follower count.
- **Creator hydration** — optionally fetch each discovered creator's recent videos too (counts
  against `maxResults`; needed to compute analytics for discovered creators).
- **Hashtag metadata** — post count / play count / follower count for a requested hashtag, looked
  up against Likee's recommended-hashtag list.
- **Creator analytics** — computed from the API-collected videos per uid (share-URL videos are
  excluded: they carry no view count and would skew the averages): posting cadence
  (posts/7d, posts/30d, posts/week), average/median views, average likes/comments/shares, average
  engagement rate, view-to-follower ratio, and the best-performing recent video.
- **Engagement + date filters** — `minViews`, `minLikes`, `minComments`, `minShares`,
  `minEngagementRate`, `publishedAfter`, `publishedBefore` — applied to videos before they count
  against the budget.
- **Incremental monitoring** (`onlyNewResults`) — skips videos already seen on a prior run with the
  same input identity (same uids/handles/discovery settings), tracked via a fingerprint file keyed
  by an input hash.
- **Budget priority** — a single global `maxResults` cap is spent in a fixed order:
  profile-videos → creator-discovery (+ hydration) → hashtag-metadata. Videos already pushed are
  never removed to make room for anything lower in priority; analytics computed after the budget
  runs out are dropped with a note instead of silently omitted.
- **Schema-drift canary** — the run summary flags two conditions that likely mean the Likee API
  shape or availability changed: a Tier A endpoint returning "system busy" (52010) three or more
  times, or a Tier A endpoint parsing zero items across an entire run (when the budget wasn't the
  reason).

### Limitations — verified, not worked around

These are the actual, tested behavior of Likee's public JSON API — not something this Actor
chose to skip. Each Tier B endpoint is gated by a per-endpoint request signature Likee's
mobile/web clients compute; **reverse-engineering that signature is out of scope for this
Actor** (see `CLAUDE.md`), so the seam stays disabled rather than half-working:

- **No keyword/hashtag video search.** There is no way to list videos for a search query.
- **No hashtag VIDEO listing.** Hashtag support in v1 is metadata only (post/play/follower
  counts) — you cannot list the videos under a hashtag.
- **Single-video lookup works ONLY via share URLs** (`videoUrls`, see above). The signed
  `getVideoInfo` API stays out of scope, and canonical `likee.video/@handle/video/<postId>`
  page URLs are not supported — that page redirects to the homepage, and there is no unsigned
  postId→share-id conversion.
- **No comments.** Comment scraping is not available.

If you pass any Tier B-shaped input field (`searchQueries`, `scrapeComments`,
`maxCommentsPerVideo`, `hashtagVideos`), the Actor accepts it without failing the run but logs a
warning and ignores it — see `tierBWarnings` in the run summary.

#### Usernames / profile URLs were removed in 0.2.0

Likee provides no way to resolve a username or `@handle` profile URL to an account without
a browser or signed mobile-API requests (both out of scope for this actor — probed
2026-08-07 and 2026-08-08, evidence in the design specs). Earlier versions accepted
`usernames`/`profileUrls` anyway and returned zero results for them; 0.2.0 removes the
fields, and supplying them fails the run immediately with a migration message.

**Migration:** for any creator, paste one of their video share links into `videoUrls`
(app: video → Share → Copy link) — the creator is scraped automatically. Power users can
supply numeric `uids` (from a previous run's output) under Advanced.

#### Profile follower counts and analytics ratios populated only via cross-fill

`profile.followers`, `profile.following`, and `creatorAnalytics.viewToFollowerRatio` are
populated **only when the same uid is also discovered via creator discovery in the same run**.
They come from cross-filling `fansCount` and `followCount` from discovered `creator` records
into buffered profile and analytics records at finalization. For uid-only runs (no discovery),
these fields remain `null` — there is no unsigned API path to fetch a user's follower count given
only a numeric uid.

#### Dataset ordering: profiles after discovery and analytics

`profile` records are now emitted during finalization, after all discovery and creator-hydration
work, alongside `creatorAnalytics` records. This allows cross-fill of follower data from
discovered creators to reach profiles before emission. In runs with no discovery, profiles still
emit at finalization with `followers`/`following` as `null`.

#### Hashtag coverage is limited to the recommended list

Hashtag metadata comes from `getRecommendHashtag`, which returns a paginated list of currently
recommended hashtags. A requested hashtag is matched against that list; if it isn't present, the
Actor emits a record for it anyway with `found: false` and null stats, and a note in the run
summary — it does not silently drop the request.

#### `onlyNewResults` only applies to videos

The incremental fingerprint only tracks `videoId`s. Creator, profile, hashtag, and analytics
records are not deduplicated across runs. In particular, **a hashtags-only run
(`hashtags: [...], no uids/videoUrls/discoverCreators`) with `onlyNewResults: true` is a
no-op with respect to that setting** — there are no videos in the run for it to filter.

### Example input

```json
{
    "uids": ["1324736462"],
    "videoUrls": ["https://likee.video/v/oaoej1"],
    "discoverCreators": true,
    "country": "US",
    "maxCreators": 50,
    "hydrateCreatorVideos": true,
    "videosPerCreatorForAnalytics": 30,
    "hashtags": ["newmusicfriday", "dance"],
    "maxResults": 500,
    "maxVideosPerProfile": 100,
    "videoSort": "latest",
    "minFollowers": 1000,
    "minViews": 0,
    "minLikes": 0,
    "minComments": 0,
    "minShares": 0,
    "minEngagementRate": 0,
    "publishedAfter": "2026-01-01",
    "publishedBefore": "",
    "includeProfileAnalytics": true,
    "onlyNewResults": true,
    "maxConcurrency": 5,
    "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

The only requirement to run at all is at least one entry point: `videoUrls`, `uids`,
`discoverCreators: true`, or `hashtags`.

Proxy note: if a run reports `CONTENT_UNAVAILABLE` with a `/notfound` final URL, switch the
proxy country — Likee is blocked in some regions (e.g. IN). The default pins `US` for this
reason; share-page lookups also retry once on a fresh proxy exit before giving up.

### Example output records

Every dataset item is `type`-tagged. Missing/unavailable fields are always `null`, never
fabricated.

**Video:**

```json
{
    "type": "video",
    "videoId": "7641427886757765070",
    "description": "",
    "author": {
        "uid": "71622063",
        "username": "HouseofB",
        "nickname": "HouseofBrooklyn",
        "avatar": "https://img.like.video/...",
        "profileUrl": "https://likee.video/@HouseofB",
        "isPrivate": false
    },
    "stats": { "views": 674077, "likes": 12094, "comments": 21, "shares": 760 },
    "engagementRate": 1.9,
    "media": {
        "videoUrl": null,
        "coverUrl": "https://videosnap.like.video/...",
        "width": 1080,
        "height": 1920,
        "download": null
    },
    "hashtags": [],
    "mentions": [],
    "music": { "id": null, "name": "HouseofBrooklyn" },
    "country": null,
    "publishedAt": "2026-05-11T12:22:53.000Z",
    "matchedQueries": [],
    "matchedHashtags": [],
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

`media.videoUrl` is `null` for API-sourced videos (the clean mp4 needs Tier B) but **populated
for videos scraped via `videoUrls`** — the share page embeds the playable mp4. When the same
video arrives from both paths, the mp4 is kept.

**Profile (one per scraped profile; `followers`/`following` are cross-filled from discovery
when the same uid is also discovered, otherwise `null`):**

```json
{
    "type": "profile",
    "uid": "1324736462",
    "username": "HouseofB",
    "nickname": "HouseofBrooklyn",
    "avatar": "https://img.like.video/...",
    "followers": 8756507,
    "following": 118,
    "isPrivate": false,
    "videoCount": 30,
    "profileUrl": "https://likee.video/@HouseofB",
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

**Creator (discovery):**

```json
{
    "type": "creator",
    "uid": "1324736462",
    "likeid": "someLikeId",
    "nickName": "Some Creator",
    "fansCount": 52000,
    "followCount": 120,
    "avatar": "https://img.like.video/...",
    "country": "US",
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

**Creator analytics:**

```json
{
    "type": "creatorAnalytics",
    "uid": "1324736462",
    "videosAnalyzed": 30,
    "postsLast7d": 3,
    "postsLast30d": 12,
    "postsPerWeek": 2.8,
    "avgViews": 45210.5,
    "medianViews": 38000,
    "avgLikes": 3021.4,
    "avgComments": 88.2,
    "avgShares": 210.1,
    "avgEngagementRate": 7.35,
    "viewToFollowerRatio": 0.87,
    "bestRecentVideoId": "7641427886757765070",
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

**Hashtag metadata (found vs. not found):**

```json
{
    "type": "hashtag",
    "tagName": "newmusicfriday",
    "found": true,
    "postCnt": 15234,
    "playCnt": 9021332,
    "fansCnt": 525,
    "banners": [],
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

```json
{
    "type": "hashtag",
    "tagName": "some-obscure-tag",
    "found": false,
    "postCnt": null,
    "playCnt": null,
    "fansCnt": null,
    "banners": [],
    "scrapedAt": "2026-08-07T00:00:00.000Z"
}
```

### Run summary (`RUN_SUMMARY` key-value store record)

Every run writes a `RUN_SUMMARY` record to the default key-value store with:

- per-type counts (`videosCollected`, `profilesCollected`, `creatorsCollected`,
  `analyticsCollected`, `hashtagsCollected`)
- `failedRequests` / `notes` — structured entries (`level`, optional `code`, `message`), including
  `PROFILE_NOT_FOUND`, `PRIVATE_PROFILE`, `CONTENT_UNAVAILABLE`, and `SCHEMA_DRIFT` when the canary
  fires
- `tierBWarnings` — any ignored Tier B input fields
- `budgetExhausted` + `skippedWorkflows` — set when `maxResults` was hit before every workflow ran

### Development

```bash
npm install
npm test          # unit tests on fixtures — never hits live Likee
npm run lint
npm run build
npm run start:dev # tsx src/main.ts, reads local .actor/input_schema.json-shaped INPUT
```

`tests/**/*.live.test.ts` files are excluded from `npm test` (see `vitest.config.ts`) and hit the
real API; run them explicitly and sparingly.

# Actor input Schema

## `videoUrls` (type: `array`):

likee.video/v/<id> share links (in the app: video → Share → Copy link). Each resolves to a video record with a playable mp4 URL; with 'Also scrape the creator' on, the poster's whole profile is scraped too. Note: the share page has no view count, so a Min views filter above 0 drops the share-page record itself.

## `hydrateVideoUrlAuthors` (type: `boolean`):

After each successful share-link lookup, scrape the poster's full profile videos and analytics (counts against Max results). Untick for a cheap single-video lookup.

## `discoverCreators` (type: `boolean`):

Discover creators by country via Likee's recommended-producer list.

## `country` (type: `string`):

ISO-2 country for discovery.

## `maxCreators` (type: `integer`):

Maximum number of creators to return from discovery.

## `hydrateCreatorVideos` (type: `boolean`):

Fetch recent videos per discovered creator to compute analytics. Increases request count and proxy spend; counts against Max results.

## `videosPerCreatorForAnalytics` (type: `integer`):

How many recent videos to fetch per creator when hydration is enabled.

## `hashtags` (type: `array`):

Trending-hashtag metadata only. Coverage is limited to Likee's recommended-hashtag list; hashtag VIDEO listing is not available.

## `maxResults` (type: `integer`):

Global cap across all record types.

## `maxVideosPerProfile` (type: `integer`):

Maximum videos to fetch per profile (paginated via cursor).

## `videoSort` (type: `string`):

Only 'latest' (reverse-chronological) is supported by the API.

## `minFollowers` (type: `integer`):

Drop profiles/creators with fewer followers than this.

## `minViews` (type: `integer`):

Drop videos with fewer views (play count) than this.

## `minLikes` (type: `integer`):

Drop videos with fewer likes than this.

## `minComments` (type: `integer`):

Drop videos with fewer comments than this.

## `minShares` (type: `integer`):

Drop videos with fewer shares than this.

## `minEngagementRate` (type: `number`):

Drop videos below this engagement rate, as a percentage.

## `publishedAfter` (type: `string`):

ISO date; videos older than this are skipped.

## `publishedBefore` (type: `string`):

ISO date; videos newer than this are skipped.

## `includeProfileAnalytics` (type: `boolean`):

Emit creatorAnalytics records. Disable to skip analytics computation.

## `onlyNewResults` (type: `boolean`):

Skip videos seen in previous runs with the same targets.

## `maxConcurrency` (type: `integer`):

Maximum concurrent requests to Likee.

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

Residential proxy strongly recommended.

## `uids` (type: `array`):

Numeric account ids. Usernames/@handles are NOT accepted anywhere — Likee provides no way to resolve them; use a video share link instead.

## Actor input object example

```json
{
  "videoUrls": [
    "https://l.likee.video/v/oaoej1"
  ],
  "hydrateVideoUrlAuthors": true,
  "discoverCreators": false,
  "country": "US",
  "maxCreators": 100,
  "hydrateCreatorVideos": false,
  "videosPerCreatorForAnalytics": 30,
  "hashtags": [],
  "maxResults": 1000,
  "maxVideosPerProfile": 100,
  "videoSort": "latest",
  "minFollowers": 0,
  "minViews": 0,
  "minLikes": 0,
  "minComments": 0,
  "minShares": 0,
  "minEngagementRate": 0,
  "publishedAfter": "",
  "publishedBefore": "",
  "includeProfileAnalytics": true,
  "onlyNewResults": false,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "uids": [
    "1324736462"
  ]
}
```

# Actor output Schema

## `records` (type: `string`):

All type-tagged records: videos, profiles, discovered creators, creator analytics, and hashtag metadata.

## `runSummary` (type: `string`):

Per-run counts, failed/blocked/private/empty inputs, Tier B warnings, budget status, and schema-drift flags.

# 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 = {
    "videoUrls": [
        "https://l.likee.video/v/oaoej1"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapelabmax/likee-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 = { "videoUrls": ["https://l.likee.video/v/oaoej1"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapelabmax/likee-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 '{
  "videoUrls": [
    "https://l.likee.video/v/oaoej1"
  ]
}' |
apify call scrapelabmax/likee-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapelabmax/likee-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/loovRdUfmHZsdeZax/builds/gM3kSkVp1SmqfQTFA/openapi.json
