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;
|
instagramHandle?: string;
|
||||||
instagramHandles?: string[];
|
instagramHandles?: string[];
|
||||||
youtubeChannelId?: string;
|
youtubeChannelId?: string;
|
||||||
|
facebookHandle?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +46,7 @@ export function useEnrichment(
|
||||||
instagramHandle: params.instagramHandle,
|
instagramHandle: params.instagramHandle,
|
||||||
instagramHandles: params.instagramHandles,
|
instagramHandles: params.instagramHandles,
|
||||||
youtubeChannelId: params.youtubeChannelId,
|
youtubeChannelId: params.youtubeChannelId,
|
||||||
|
facebookHandle: params.facebookHandle,
|
||||||
address: params.address,
|
address: params.address,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export interface EnrichChannelsRequest {
|
||||||
instagramHandle?: string;
|
instagramHandle?: string;
|
||||||
instagramHandles?: string[];
|
instagramHandles?: string[];
|
||||||
youtubeChannelId?: string;
|
youtubeChannelId?: string;
|
||||||
|
facebookHandle?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,20 @@ export interface EnrichmentData {
|
||||||
mapx?: string;
|
mapx?: string;
|
||||||
mapy?: 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
|
// 네이버 블로그 enrichment
|
||||||
if (enrichment.naverBlog) {
|
if (enrichment.naverBlog) {
|
||||||
const nb = enrichment.naverBlog;
|
const nb = enrichment.naverBlog;
|
||||||
|
|
|
||||||
|
|
@ -63,11 +63,14 @@ export default function ReportPage() {
|
||||||
baseData.youtubeAudit?.handle ||
|
baseData.youtubeAudit?.handle ||
|
||||||
undefined;
|
undefined;
|
||||||
|
|
||||||
|
const fbHandle = handles?.facebook || undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
reportId: baseData.id,
|
reportId: baseData.id,
|
||||||
clinicName: baseData.clinicSnapshot.name,
|
clinicName: baseData.clinicSnapshot.name,
|
||||||
instagramHandles: igHandles.length > 0 ? igHandles : undefined,
|
instagramHandles: igHandles.length > 0 ? igHandles : undefined,
|
||||||
youtubeChannelId: ytHandle || undefined,
|
youtubeChannelId: ytHandle || undefined,
|
||||||
|
facebookHandle: fbHandle as string | undefined,
|
||||||
address: baseData.clinicSnapshot.location || undefined,
|
address: baseData.clinicSnapshot.location || undefined,
|
||||||
};
|
};
|
||||||
}, [baseData, isEnriched, dbSocialHandles, location.state]);
|
}, [baseData, isEnriched, dbSocialHandles, location.state]);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ interface EnrichRequest {
|
||||||
instagramHandle?: string;
|
instagramHandle?: string;
|
||||||
instagramHandles?: string[];
|
instagramHandles?: string[];
|
||||||
youtubeChannelId?: string;
|
youtubeChannelId?: string;
|
||||||
|
facebookHandle?: string;
|
||||||
address?: string;
|
address?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +49,7 @@ Deno.serve(async (req) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { reportId, clinicName, instagramHandle, instagramHandles, youtubeChannelId, address } =
|
const { reportId, clinicName, instagramHandle, instagramHandles, youtubeChannelId, facebookHandle, address } =
|
||||||
(await req.json()) as EnrichRequest;
|
(await req.json()) as EnrichRequest;
|
||||||
|
|
||||||
// Build list of IG handles to try: explicit array > single handle > empty
|
// 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) {
|
if (youtubeChannelId) {
|
||||||
const YOUTUBE_API_KEY = Deno.env.get("YOUTUBE_API_KEY");
|
const YOUTUBE_API_KEY = Deno.env.get("YOUTUBE_API_KEY");
|
||||||
if (YOUTUBE_API_KEY) {
|
if (YOUTUBE_API_KEY) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue