xiaohongshu
xiaohongshu
instagram-pipeline-expert
Expert knowledge on Instagram search providers (Serper vs Apify), rate limiting, data normalization, and cost optimization. Use this skill when user asks about "instagram search", "serper", "apify", "scraping instagram", "provider selection", "instagram pipeline", "instagram reels", or "normalize...
Full skill instructions
You are an expert in the Instagram US Reels search pipeline for this influencer discovery platform. This skill provides comprehensive knowledge about search providers, rate limits, normalization logic, and cost optimization strategies.
This skill activates when users:
Serper (Primary Provider)
https://google.serper.dev/searchKey Implementation:
// lib/instagram-us-reels/clients/serper.ts
const SERPER_ENDPOINT = 'https://google.serper.dev/search';
export async function fetchSerperHandles(
params: SerperHandleParams,
options: SerperOptions = {},
): Promise<string[]> {
const apiKey = resolveSerperKey(options);
const body = {
q: params.query,
location: params.location ?? 'United States',
gl: params.gl ?? 'us',
hl: params.hl ?? 'en',
num: Math.min(Math.max(params.num ?? 10, 1), 20),
};
const response = await fetch(SERPER_ENDPOINT, {
method: 'POST',
headers: {
'X-API-KEY': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(15000),
});
// Extract handles from organic results
const payload = await response.json();
const organic = Array.isArray(payload?.organic) ? payload.organic : [];
for (const entry of organic) {
push(extractInstagramHandle(entry?.link ?? ''));
}
return handles.slice(0, body.num);
}
Apify (Legacy/Alternative Provider)
Apify is NOT currently active in the main pipeline but available in:
/lib/platforms/instagram-similar/api.ts/scripts/test-apify-instagram-*.jsSerper Rate Limits:
/lib/instagram-us-reels/index.ts// Sequential processing to avoid rate limits
for (const kw of keywords) {
const handles = await fetchSerperHandles({
query: `site:instagram.com "${kw}" reels`,
num: Math.min(resultsPerKeyword, 20)
});
// Rate limit pause between keywords
if (i < keywords.length - 1) {
await sleep(config.keywordDelayMs);
}
}
Best Practices:
The pipeline normalizes creator data from multiple sources into a unified format.
Normalization File: /lib/instagram-us-reels/utils/creator-normalizer.ts
Key Fields:
interface NormalizedCreator {
username: string; // Primary identifier
displayName?: string; // Full name
platform: 'instagram';
profileUrl: string; // https://instagram.com/{username}
avatarUrl?: string;
followers?: number;
isVerified?: boolean;
bio?: string;
email?: string;
// Search metadata
source: 'serper' | 'apify' | 'scrapecreators';
keyword?: string; // Which keyword found this creator
searchScore?: number; // Relevance score
}
Duplicate Detection:
// Deduplication by username
const seen = new Set<string>();
const dedupedCreators = creators.filter(c => {
const key = c.username.toLowerCase();
if (seen.has(key)) return false;
seen.add(key);
return true;
});
Handle Extraction from URLs:
const DISALLOWED_SEGMENTS = new Set([
'p', 'reel', 'reels', 'tv', 'explore', 'tags', 'tag',
'directory', 'accounts', 'about', 'legal', 'privacy',
'developers', 'business', 'topics', 'guide', 'stories'
]);
function extractInstagramHandle(url: string): string | null {
const parsed = new URL(url);
if (!parsed.hostname.includes('instagram.com')) return null;
const segments = parsed.pathname.split('/').filter(Boolean);
const handle = segments[0]?.replace('@', '').trim();
if (!handle || handle.length > 50) return null;
if (!/^[a-z0-9._]+$/i.test(handle)) return null;
if (DISALLOWED_SEGMENTS.has(handle.toLowerCase())) return null;
return handle.toLowerCase();
}
Cost Breakdown:
Optimization Techniques:
Keyword Expansion (Cheap)
/lib/instagram-us-reels/steps/keyword-expansion.tsHandle Discovery (Serper)
Profile Screening (ScapeCreators)
Lazy Enrichment
Example Cost Calculation:
Search Job: "fitness influencers"
├─ Keyword Expansion: $0.0001 (1 keyword → 10)
├─ Handle Discovery: $0.01 (10 Serper queries)
├─ Profile Screening: $0 (not implemented yet)
└─ Total: $0.0101 per search job
With 1000 results target:
- Current: ~$0.01 per job
- With Apify: ~$30 per job (300x more expensive)
// Good: Sequential processing with rate limiting
async function processKeywordsSequentially(keywords: string[]) {
const allHandles: string[] = [];
for (let i = 0; i < keywords.length; i++) {
const kw = keywords[i];
try {
const handles = await fetchSerperHandles({
query: `site:instagram.com "${kw}" reels`,
num: 20
});
allHandles.push(...handles);
// Rate limit pause between keywords
if (i < keywords.length - 1) {
await sleep(2000); // 2 second delay
}
} catch (error) {
console.error(`Failed to fetch handles for "${kw}":`, error);
// Continue with next keyword
}
}
return allHandles;
}
When to use: Always for Serper queries to respect rate limits
// Good: Distribute results evenly across keywords
function distributeFairly(keywords: string[], targetResults: number) {
const basePerKeyword = Math.floor(targetResults / keywords.length);
const remainder = targetResults % keywords.length;
return keywords.map((kw, i) => ({
keyword: kw,
limit: basePerKeyword + (i < remainder ? 1 : 0)
}));
}
// Example: 100 results across 7 keywords
// Results: [15, 15, 14, 14, 14, 14, 14]
When to use: When processing multiple keywords to ensure balanced coverage
// Good: Validate handles before processing
function isValidHandle(handle: string): boolean {
if (!handle || handle.length > 50) return false;
if (!/^[a-z0-9._]+$/i.test(handle)) return false;
if (DISALLOWED_SEGMENTS.has(handle.toLowerCase())) return false;
return true;
}
const validHandles = rawHandles.filter(isValidHandle);
When to use: Always after extracting handles from URLs
// BAD: Parallel requests will hit rate limits
const promises = keywords.map(kw =>
fetchSerperHandles({ query: kw, num: 20 })
);
const results = await Promise.all(promises);
Why it's bad: Exceeds Serper's 100 req/sec limit, causes 429 errors
Do this instead:
// GOOD: Sequential processing
const results = [];
for (const kw of keywords) {
const handles = await fetchSerperHandles({ query: kw, num: 20 });
results.push(handles);
await sleep(1000); // Rate limit pause
}
// BAD: Expensive and slow for initial discovery
const profiles = await apifyClient.call('instagram-profile-scraper', {
usernames: allHandles // 1000 handles
});
// Cost: $30-50, Time: 30+ minutes
Why it's bad: 300x more expensive than Serper, much slower
Do this instead:
// GOOD: Use Serper for discovery, Apify for enrichment
const handles = await fetchSerperHandles({ query: keyword });
// Cost: $0.001, Time: 2 seconds
// Only enrich when user clicks
if (userClickedOnCreator) {
const profile = await apifyClient.call('instagram-profile-scraper', {
username: creator.username
});
}
// BAD: Processing invalid handles wastes API calls
const handles = urls.map(url => url.split('/').pop());
const profiles = await fetchProfiles(handles);
Why it's bad: Wasting API calls on URLs like /p/abc123, /reel/xyz789
Do this instead:
// GOOD: Validate handles first
const handles = urls
.map(extractInstagramHandle)
.filter(Boolean)
.filter(isValidHandle);
const profiles = await fetchProfiles(handles);
Symptoms:
Diagnosis:
// Look for this in the pipeline
const uniqueCreators = dedupeCreators(allCreators);
Solution:
// lib/utils/dedupe-creators.ts
import { dedupeCreators } from '@/lib/utils/dedupe-creators';
const dedupedCreators = dedupeCreators(creators, {
by: 'username', // or 'profileUrl'
keepFirst: true // Keep first occurrence
});
Symptoms:
Error: Serper error 429: Rate limit exceededDiagnosis:
Solution:
// Add exponential backoff
async function fetchWithRetry(params: SerperHandleParams, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fetchSerperHandles(params);
} catch (error) {
if (error.message.includes('429') && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
await sleep(delay);
continue;
}
throw error;
}
}
}
Symptoms:
Diagnosis:
Solution:
// Normalize with fallbacks
function normalizeCreator(raw: any): NormalizedCreator {
return {
username: raw.username || raw.handle || 'unknown',
displayName: raw.displayName || raw.fullName || raw.username,
profileUrl: raw.profileUrl || `https://instagram.com/${raw.username}`,
avatarUrl: raw.avatarUrl || raw.profilePicUrl || null,
followers: parseInt(raw.followers) || 0,
isVerified: raw.isVerified || raw.verified || false,
bio: raw.bio || raw.biography || null,
platform: 'instagram',
source: raw.source || 'unknown'
};
}
Symptoms:
Diagnosis:
site:instagram.com "keyword" reelsSolution:
// Add keyword validation and expansion
async function searchWithFallback(keyword: string) {
// Try exact keyword first
let handles = await fetchSerperHandles({ query: keyword });
if (handles.length === 0) {
// Try broader search
const broader = keyword.split(' ')[0]; // First word only
handles = await fetchSerperHandles({ query: broader });
}
if (handles.length === 0) {
// Try without site: filter
handles = await fetchSerperHandles({
query: `${keyword} instagram influencer`
});
}
return handles;
}
Understand these files to work with the Instagram pipeline:
/lib/instagram-us-reels/clients/serper.ts - Serper API client/lib/instagram-us-reels/index.ts - Main pipeline orchestration/lib/instagram-us-reels/steps/keyword-expansion.ts - GPT-4 keyword expansion/lib/instagram-us-reels/utils/creator-normalizer.ts - Data normalization/lib/platforms/instagram-similar/api.ts - Apify integration (legacy)/lib/utils/dedupe-creators.ts - Deduplication logic/scripts/test-both-hashtag-scrapers.js - Provider comparison script/scripts/quick-test-instagram-apis.js - Manual testing scriptTest Serper Directly:
curl -X POST 'https://google.serper.dev/search' \
-H 'X-API-KEY: your-api-key' \
-H 'Content-Type: application/json' \
-d '{
"q": "site:instagram.com \"fitness\" reels",
"num": 10,
"gl": "us"
}'
Test Full Pipeline:
node scripts/quick-test-instagram-apis.js
Expected Results:
Typical Search Job (10 keywords, 1000 results target):
Comparison (1000 Instagram Profiles):
| Provider | Time | Cost | Data Quality |
|---|---|---|---|
| Serper | 30s | $0.01 | Handles only |
| Apify | 30min | $30 | Full profiles |
| ScapeCreators | 5min | $5 | Basic profiles |
/instagram-us-reels-search/ directory (archived docs)xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience