feat: Facebook page data collection via Apify scraper
- enrich-channels: add Facebook Pages Scraper (apify~facebook-pages-scraper) - Collects: pageName, followers, likes, categories, email, phone, website, intro, rating - transformReport: merge Facebook data into facebookAudit.pages[] (auto-shows section) - Frontend: pass facebookHandle through enrichment pipeline - EnrichChannelsRequest: add facebookHandle parameter Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>claude/bold-hawking
parent
ad625e08ee
commit
a7d8aeeddc
|
|
@ -16,6 +16,7 @@ interface EnrichmentParams {
|
|||
instagramHandle?: string;
|
||||
instagramHandles?: string[];
|
||||
youtubeChannelId?: string;
|
||||
facebookHandle?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ export function useEnrichment(
|
|||
instagramHandle: params.instagramHandle,
|
||||
instagramHandles: params.instagramHandles,
|
||||
youtubeChannelId: params.youtubeChannelId,
|
||||
facebookHandle: params.facebookHandle,
|
||||
address: params.address,
|
||||
})
|
||||
.then((result) => {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export interface EnrichChannelsRequest {
|
|||
instagramHandle?: string;
|
||||
instagramHandles?: string[];
|
||||
youtubeChannelId?: string;
|
||||
facebookHandle?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -439,6 +439,20 @@ export interface EnrichmentData {
|
|||
mapx?: string;
|
||||
mapy?: string;
|
||||
};
|
||||
facebook?: {
|
||||
pageName?: string;
|
||||
pageUrl?: string;
|
||||
followers?: number;
|
||||
likes?: number;
|
||||
categories?: string[];
|
||||
email?: string;
|
||||
phone?: string;
|
||||
website?: string;
|
||||
address?: string;
|
||||
intro?: string;
|
||||
rating?: number;
|
||||
profilePictureUrl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -616,6 +630,31 @@ export function mergeEnrichment(
|
|||
}
|
||||
}
|
||||
|
||||
// Facebook enrichment
|
||||
if (enrichment.facebook) {
|
||||
const fb = enrichment.facebook;
|
||||
merged.facebookAudit = {
|
||||
...merged.facebookAudit,
|
||||
pages: [{
|
||||
url: fb.pageUrl || '',
|
||||
pageName: fb.pageName || '',
|
||||
language: 'KR',
|
||||
label: '메인',
|
||||
followers: fb.followers ?? 0,
|
||||
following: 0,
|
||||
category: fb.categories?.join(', ') || '',
|
||||
bio: fb.intro || '',
|
||||
logo: '',
|
||||
logoDescription: '',
|
||||
link: fb.website || '',
|
||||
linkedDomain: fb.website || '',
|
||||
reviews: 0,
|
||||
recentPostAge: '',
|
||||
hasWhatsApp: false,
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// 네이버 블로그 enrichment
|
||||
if (enrichment.naverBlog) {
|
||||
const nb = enrichment.naverBlog;
|
||||
|
|
|
|||
|
|
@ -63,11 +63,14 @@ export default function ReportPage() {
|
|||
baseData.youtubeAudit?.handle ||
|
||||
undefined;
|
||||
|
||||
const fbHandle = handles?.facebook || undefined;
|
||||
|
||||
return {
|
||||
reportId: baseData.id,
|
||||
clinicName: baseData.clinicSnapshot.name,
|
||||
instagramHandles: igHandles.length > 0 ? igHandles : undefined,
|
||||
youtubeChannelId: ytHandle || undefined,
|
||||
facebookHandle: fbHandle as string | undefined,
|
||||
address: baseData.clinicSnapshot.location || undefined,
|
||||
};
|
||||
}, [baseData, isEnriched, dbSocialHandles, location.state]);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ interface EnrichRequest {
|
|||
instagramHandle?: string;
|
||||
instagramHandles?: string[];
|
||||
youtubeChannelId?: string;
|
||||
facebookHandle?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ Deno.serve(async (req) => {
|
|||
}
|
||||
|
||||
try {
|
||||
const { reportId, clinicName, instagramHandle, instagramHandles, youtubeChannelId, address } =
|
||||
const { reportId, clinicName, instagramHandle, instagramHandles, youtubeChannelId, facebookHandle, address } =
|
||||
(await req.json()) as EnrichRequest;
|
||||
|
||||
// Build list of IG handles to try: explicit array > single handle > empty
|
||||
|
|
@ -327,7 +328,44 @@ Deno.serve(async (req) => {
|
|||
}
|
||||
}
|
||||
|
||||
// 5. YouTube Channel (using YouTube Data API v3)
|
||||
// 5. Facebook Page (using Apify)
|
||||
if (facebookHandle) {
|
||||
tasks.push(
|
||||
(async () => {
|
||||
// Normalize: strip URL parts to get page name, then build full URL
|
||||
let fbUrl = facebookHandle;
|
||||
if (!fbUrl.startsWith("http")) {
|
||||
fbUrl = fbUrl.replace(/^@/, "");
|
||||
fbUrl = `https://www.facebook.com/${fbUrl}`;
|
||||
}
|
||||
|
||||
const items = await runApifyActor(
|
||||
"apify~facebook-pages-scraper",
|
||||
{ startUrls: [{ url: fbUrl }] },
|
||||
APIFY_TOKEN
|
||||
);
|
||||
const page = (items as Record<string, unknown>[])[0];
|
||||
if (page && page.title) {
|
||||
enrichment.facebook = {
|
||||
pageName: page.title,
|
||||
pageUrl: page.pageUrl || fbUrl,
|
||||
followers: page.followers,
|
||||
likes: page.likes,
|
||||
categories: page.categories,
|
||||
email: page.email,
|
||||
phone: page.phone,
|
||||
website: page.website,
|
||||
address: page.address,
|
||||
intro: page.intro,
|
||||
rating: page.rating,
|
||||
profilePictureUrl: page.profilePictureUrl,
|
||||
};
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
// 6. YouTube Channel (using YouTube Data API v3)
|
||||
if (youtubeChannelId) {
|
||||
const YOUTUBE_API_KEY = Deno.env.get("YOUTUBE_API_KEY");
|
||||
if (YOUTUBE_API_KEY) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue