# Asia Social Listening – HK, TW & SG Forum Brand Monitor (`claude_code_reviewer/asia-social-listening-en`) Actor

Monitor brand mentions across Asia's forums in one Actor: LIHKG (Hong Kong), PTT (Taiwan), HardwareZone (Singapore) and Telegram. Enter a keyword — it searches, deduplicates and scores sentiment, with competitor share-of-voice. Priced per deduplicated mention. For SG/HK/TW social listening.

- **URL**: https://apify.com/claude\_code\_reviewer/asia-social-listening-en.md
- **Developed by:** [Chad](https://apify.com/claude_code_reviewer) (community)
- **Categories:** Social media, News, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $50.00 / 1,000 deduplicated mentions

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?

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

## Asia Social Listening | LIHKG, PTT, HardwareZone & Telegram Brand Mention Aggregator

Give it one brand keyword and it scrapes related discussion across four platforms in a single run — Hong Kong's LIHKG, Taiwan's PTT, Singapore's HardwareZone, and Telegram — with automatic cross-platform deduplication, lightweight sentiment analysis, and competitor share-of-voice comparison. It hits each platform's public endpoints directly (**it does not call our other actors as sub-actors**, so you're never billed twice through a child actor's own metering), and outputs one flat, structured mention list.

### What this is (and isn't)

- This is a **meta-actor**: it uses lightweight HTTP (`got-scraping`) to hit each platform's public endpoints directly, reusing (via copy, not import) the parsing logic from our own LIHKG / PTT / HardwareZone / Telegram scrapers. It does **not** invoke a separately-billed child actor.
- LIHKG runs a **true, site-wide search**. PTT, HardwareZone, and Telegram do **keyword/title filtering within a target list you supply** (boards / forums / channels) — not a site-wide search. Filter-based platforms can't surface discussion outside the list you give them; see Notes below.
- Threads (Asia · true search) is planned for **v1.1** — it needs a browser (no public JSON endpoint), and the $0.05/mention price point can absorb that cost, but v1 ships with these four pure-HTTP platforms first.

### Output Fields (MentionRow)

| Field | Type | Description |
|---------|------|------|
| mention\_id | string | `{platform}:{original ID}`, stable across runs — used by deltaMode to diff new vs. seen |
| platforms | array | Which source it came from: `lihkg` / `ptt` / `hardwarezone` / `telegram` (all on by default) |
| brand\_matched | string | The brand term matched (the primary brandKeyword, or one of the competitorBrands) |
| is\_competitor | boolean | Whether this is a competitor mention (false = the primary brand itself) |
| matched\_alias | string | null | The alias term matched (from searchAliases); null if matched on the primary term |
| text | string | null | Post title / body / message text |
| author | string | null | Author / poster |
| url | string | null | Link to the original post |
| created\_at | string | null | Post time (ISO 8601; native precision varies by platform) |
| engagement\_likes | number | null | Like count (most platforms don't expose this — reported honestly as null) |
| engagement\_replies | number | null | Reply/comment count |
| engagement\_views | number | null | View count |
| sentiment | string | null | `positive` / `neutral` / `negative`; null when sentiment analysis is off |
| sentiment\_score | number | null | -1 (negative) to 1 (positive); null when sentiment analysis is off |
| is\_duplicate | boolean | Whether this is a duplicate mention (same post, or a cross-platform repost); only false ones are billed |
| duplicate\_of | string | null | Points to the first mention\_id it duplicates, otherwise null |
| source\_query | string | The query/filter term actually matched (primary term or alias) |
| scraped\_at | string | Scrape timestamp (ISO 8601) |

### Platform Capabilities

| Platform | Keyword Capability | Needs a Target List? |
|---|---|---|
| LIHKG (Hong Kong) | 🟢 True, site-wide search | No |
| PTT (Taiwan) | 🟡 Title filter within given boards | Yes (`pttBoards`, defaults to Gossiping / Stock) |
| HardwareZone (Singapore) | 🟡 Title filter within given forums | Yes (`hwzForums`, defaults to 16 / EDMW) |
| Telegram | 🟡 Text filter within given channels | Yes (`telegramChannels`; leaving it empty skips Telegram with a warning) |

### Input Fields

| Field | Type | Default | Description |
|-----|------|------|-----|
| brandKeyword | string | — | The brand/keyword to monitor (required) |
| competitorBrands | array | `[]` | Competitor brands — each gets its own pass, letting you compare share of voice in the output |
| searchAliases | array | `[]` | Extra search terms for the primary brand (English name/abbreviation/common misspellings) — primary brand only |
| platforms | array | all four | Which platforms to monitor, see "Platform Capabilities" above |
| pttBoards | array | `["Gossiping","Stock"]` | Applies when platforms includes ptt |
| hwzForums | array | `["16"]` | Applies when platforms includes hardwarezone |
| telegramChannels | array | `[]` | Required when platforms includes telegram, otherwise skipped with a warning |
| lookbackDays | integer | 7 | Only scrape content from the last N days, range 1-90 |
| maxMentionsPerPlatform | integer | 100 | Max mentions per platform per brand (after merging aliases), range 1-1000 |
| sentimentAnalysis | boolean | true | Whether to run sentiment analysis (lightweight keyword wordlist, not AI) |
| deduplication | boolean | true | Whether to dedupe within and across platforms |
| deltaMode | boolean | false | Whether to return only mentions new since the last run |
| proxyConfiguration | proxy | Apify datacenter proxy | On by default, to avoid rate limiting from sharing egress IPs with other runs |

### Input Examples

#### Single brand, LIHKG and PTT only

```json
{
  "brandKeyword": "誠品",
  "platforms": ["lihkg", "ptt"],
  "lookbackDays": 30,
  "maxMentionsPerPlatform": 40
}
```

#### Brand vs. competitor, with PTT/HardwareZone target lists

```json
{
  "brandKeyword": "麥當勞",
  "competitorBrands": ["肯德基"],
  "platforms": ["lihkg", "ptt", "hardwarezone"],
  "pttBoards": ["Gossiping"],
  "hwzForums": ["16"],
  "lookbackDays": 30,
  "maxMentionsPerPlatform": 30
}
```

### Scheduled Brand Monitoring

`lookbackDays` paired with `deltaMode` is designed for **Apify Schedule**:

- First run: `deltaMode: false` (or omit it) to get a full historical baseline.
- Subsequent scheduled runs (e.g. daily/weekly): turn `deltaMode` on, and set `lookbackDays` a bit longer than your schedule interval as a buffer (e.g. daily runs with `lookbackDays: 3`). Each run then returns only mentions not seen before (tracked via a brand-specific named KV store, `SEEN-{hash of brand}`, recording seen `mention_id`s across runs) — so a missed run won't drop data, and you won't see the same old mentions twice.
- Mentions already seen but filtered out this run by deltaMode still count toward `skipped_delta` in the run summary, so you can confirm the schedule is actually working (the number won't just sit at 0).
- Every run also writes a `SUMMARY` record to the KV store (counts per platform, sentiment breakdown, share-of-voice comparison) — handy for feeding a dashboard or pulling out for trend charts.

### Notes

- **Sentiment analysis is a lightweight, rule-based keyword wordlist (10 positive/negative terms each in Traditional Chinese and English), not an AI/LLM model.** Score = (positive hits − negative hits) / (positive + negative + 1), with a ±0.15 threshold splitting positive/neutral/negative. Good for a quick read on general sentiment, not a substitute for precise sentiment judgment.
- **Only public content is scraped** — no account or login required. PTT's 18+ boards are handled automatically via the over18 cookie; every other platform here is a fully public page.
- **True search vs. filter-based platforms**: LIHKG runs an actual site-wide search, so in principle it can surface any matching discussion anywhere on the site. PTT/HardwareZone/Telegram only filter by title/text keyword **within** the boards/forums/channels you list — anything outside that list, or an unconfigured channel, is invisible to this actor. If your brand is discussed elsewhere, add that board/channel to the list yourself.
- **Threads is planned for v1.1**: Threads search is browser-rendered with no public JSON endpoint, so it needs a browser. This actor's v1 sticks to pure HTTP across the four platforms above. The $0.05/mention price point can actually absorb Threads' browser compute cost — v1.1 will add it via a separate browser adapter.
- **searchAliases applies to the primary brand only**: competitor brands are always searched/filtered using just their own brand term, never the primary brand's alias list.
- **Deduplication rules**: within a platform, dedup is by original post ID (a post counts once); across platforms, it's by a hash of normalized text (URLs/punctuation/emoji/whitespace stripped, lowercased) to catch reposts/duplicate content across platforms — the first one found is kept as canonical, and later ones are flagged `is_duplicate: true`. **Only non-duplicate mentions are billed.**
- **maxMentionsPerPlatform is a per-platform, per-brand cap**: if the primary brand has searchAliases set, the cap applies after merging and deduping the primary term with its aliases — it's not a separate cap per alias.
- If HardwareZone / PTT / LIHKG redesigns or adds anti-scraping measures down the line, that platform returns 0 rows with a warning instead of failing the whole run — the other platforms keep collecting data as normal.

### FAQ

**Q: Why are some fields (engagement\_likes, etc.) always null?**

A: Not every platform exposes like counts publicly. A null there isn't a broken field — that platform's public page simply doesn't carry that information.

**Q: Why did I get far fewer mentions than expected?**

A: The most common cause is that PTT/HardwareZone/Telegram filter **within your target list**, not a site-wide search — check that `pttBoards`/`hwzForums`/`telegramChannels` actually cover where your brand is likely to be discussed. It could also be `lookbackDays` set too short, or `deltaMode` being on with most mentions already seen in a prior run.

**Q: Why do is\_duplicate posts still show up in the dataset?**

A: So you can see the full raw data and the duplicate relationship (you can trace `duplicate_of` back to the original mention). Duplicates are still pushed to the dataset — they're just not billed.

**Q: Can I get only competitor mentions, without the primary brand?**

A: Not currently — brandKeyword always runs. If you only want to compare competitorBrands against each other, add multiple competitors and just ignore rows where `brand_matched` equals your brandKeyword.

### Disclaimer

This tool is for academic research and lawful business analysis only. Users must comply with the terms of service of each platform (LIHKG, PTT, HardwareZone, Telegram) and applicable laws. Do not use this tool to invade others' privacy, spread false information, or engage in any other unlawful activity. Sentiment analysis results are produced by a lightweight, rule-based algorithm, are for reference only, and do not constitute professional advice. The author accepts no responsibility for any misuse.

***

social listening, brand monitoring, LIHKG, PTT, HardwareZone, Telegram, sentiment analysis, share of voice, Asia social media monitoring

# Actor input Schema

## `brandKeyword` (type: `string`):

The brand or keyword to monitor (required), e.g. "iPhone" (Eslite). All selected platforms search/filter using this term.

## `competitorBrands` (type: `array`):

(Optional) Competitor brands/keywords — each one also gets its own scrape pass. Use the output's brand\_matched/is\_competitor fields to compare share of voice.

## `searchAliases` (type: `array`):

(Optional) Extra search terms for the primary brand — English name, abbreviation, common misspellings, etc. Applies only to the primary brand (brandKeyword), not to competitors. When a post matches both the primary term and an alias, the primary term wins (matched\_alias is null).

## `platforms` (type: `array`):

Which platforms to monitor? LIHKG/Threads use true site-wide search; PTT/HardwareZone/Telegram filter by keyword within specific boards/forums/channels (requires the matching target list below).

## `pttBoards` (type: `array`):

Applies when PTT is among the monitored platforms. Which boards to filter by title keyword within, e.g. Gossiping (general chat) or Stock. 18+ boards are handled automatically with the over18 cookie.

## `hwzForums` (type: `array`):

Applies when HardwareZone is among the monitored platforms. HardwareZone forum IDs (numeric), e.g. 16 (Eat-Drink-Man-Woman / EDMW).

## `telegramChannels` (type: `array`):

Applies when Telegram is among the monitored platforms (required in that case, otherwise Telegram is skipped automatically with a warning). Enter the channel username without the @, e.g. durov. Only public channels can be scraped.

## `lookbackDays` (type: `integer`):

Only scrape content from the last N days (based on post time / last reply time; paging stops early to save cost).

## `maxMentionsPerPlatform` (type: `integer`):

Maximum number of mentions to scrape per platform per brand (including competitors) — this cap applies after merging the primary brand's aliases. Maximum 1000.

## `sentimentAnalysis` (type: `boolean`):

When enabled, adds sentiment/sentiment\_score to every mention. Determined with a lightweight Chinese/English keyword wordlist, not an AI model (see README for details). When disabled, both fields are null.

## `deduplication` (type: `boolean`):

When enabled, flags the same post (same platform, same ID) and cross-platform reposts/duplicate content (identical normalized text) as is\_duplicate. Only non-duplicate mentions are billed.

## `deltaMode` (type: `boolean`):

When enabled, only returns mentions new since the last run (tracked via a brand-specific named KV store recording seen mention\_ids, compared across runs). Ideal for scheduled monitoring, avoiding repeat sightings of old data.

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

Uses Apify datacenter proxy by default: without a proxy, you share egress IPs with other runs on the platform and are more likely to get rate-limited by individual platforms. Response payloads across all platforms are small, so proxy bandwidth cost is negligible. Turn it off only for your own proxy or local testing.

## Actor input object example

```json
{
  "brandKeyword": "iPhone",
  "platforms": [
    "lihkg",
    "ptt",
    "hardwarezone",
    "telegram"
  ],
  "pttBoards": [
    "Gossiping",
    "Stock"
  ],
  "hwzForums": [
    "16"
  ],
  "lookbackDays": 7,
  "maxMentionsPerPlatform": 100,
  "sentimentAnalysis": true,
  "deduplication": true,
  "deltaMode": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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 = {
    "brandKeyword": "iPhone",
    "platforms": [
        "lihkg",
        "ptt",
        "hardwarezone",
        "telegram"
    ],
    "pttBoards": [
        "Gossiping",
        "Stock"
    ],
    "hwzForums": [
        "16"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("claude_code_reviewer/asia-social-listening-en").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 = {
    "brandKeyword": "iPhone",
    "platforms": [
        "lihkg",
        "ptt",
        "hardwarezone",
        "telegram",
    ],
    "pttBoards": [
        "Gossiping",
        "Stock",
    ],
    "hwzForums": ["16"],
}

# Run the Actor and wait for it to finish
run = client.actor("claude_code_reviewer/asia-social-listening-en").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 '{
  "brandKeyword": "iPhone",
  "platforms": [
    "lihkg",
    "ptt",
    "hardwarezone",
    "telegram"
  ],
  "pttBoards": [
    "Gossiping",
    "Stock"
  ],
  "hwzForums": [
    "16"
  ]
}' |
apify call claude_code_reviewer/asia-social-listening-en --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,claude_code_reviewer/asia-social-listening-en"
        }
    }
}

```

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/rypsoO6pWbejAvmzi/builds/8XCQzjQFosqhgVEHv/openapi.json
