DEV Community

Daniyal Zia
Daniyal Zia

Posted on

From a LinkedIn request to an AI-callable Apify Actor

I built a Facebook Page scraper for a real automation project, published it on Apify, then connected it to Claude through the Apify MCP server.
Author: Daniyal Zia
The problem started with a LinkedIn message
The idea for this Actor did not start as a side project. It started with a LinkedIn message. Someone reached out to me because they needed Meta Ads data collected through automation. I started working on the workflow and quickly found that the difficult part was not only collecting ad-related data. The automation also needed useful information from the Facebook Pages behind those ads.
The client wanted the available public contact and social links from each Facebook Page. That could include the Instagram profile, website, WhatsApp number or link, phone number, email address, TikTok, YouTube, LinkedIn, and other social profiles.
I wanted the result in structured data because it would feed into the rest of the automation. Manually opening Pages and copying links would defeat the purpose of automating the workflow.

Original LinkedIn request

The LinkedIn request that led me to build the Facebook Page data extraction workflow

I found existing Actors, but I needed a more specific workflow
My first step was to look for an existing Apify Actor instead of immediately building another scraper. I found Facebook scraping options, including Apify's own Actor, that could already extract useful information from Facebook Pages.
But my requirement was more specific than simply scraping a Facebook Page.
I was building an automation for a client who wanted Meta Ads data enriched with as many relevant public contact and social links as possible. I needed the output in a predictable structure so the next steps in my workflow could process it automatically.
I wanted fields such as the Facebook Page URL, website, Instagram, phone number, WhatsApp, email, TikTok, YouTube, LinkedIn, and other available social profiles.
The existing Actors gave me a starting point, but I wanted more control over what the Actor extracted, how it handled Facebook redirect URLs, how it normalized and deduplicated results, and how the output was structured for my workflow.
Instead of changing the client's automation around an existing Actor's output, I decided to build a custom Actor around the exact requirements of my workflow.
That decision also gave me something else I wanted: a scraper I could modify, publish, and later expose as a tool for an AI agent through the Apify MCP server.
Building my own Facebook Page scraper

I built the Actor with Python, Playwright, and the Apify Actor SDK. The basic input is a Facebook Page URL. The Actor opens the Page, collects links and visible text, and then checks relevant About or Contact pages when enabled.
I kept the extraction logic separate from the Actor entry point. The main Actor handles the browser and run lifecycle, while the helper module classifies links, normalizes phones, decodes redirects, removes tracking parameters, and merges duplicate values.
That separation became useful because Facebook links are not always presented as clean destination URLs. The helper code can unwrap redirect URLs such as Facebook's redirect format and then classify the real destination.
I also added text extraction because useful contact details are sometimes displayed as plain text instead of a clickable link. For example, the Actor can look for phone numbers and domains in visible page text when those options are enabled.
The core input schema starts with a required Facebook Page URL:
{
"$schema": "https://apify.com/schemas/v1/input.ide.json",
"title": "Facebook Page Contact & Social Scraper",
"description": "Enter a Facebook page URL to extract its Instagram, website, phone / WhatsApp number, email and all other social links (TikTok, YouTube, LinkedIn, Twitter/X, Telegram, Snapchat, Pinterest and more).",
"type": "object",
"schemaVersion": 1,
"properties": {
"url": {
"title": "Facebook Page URL",
"type": "string",
"description": "Example: https://www.facebook.com/aiwithdaniyal",
"editor": "textfield",
"prefill": "https://www.facebook.com/aiwithdaniyal"
},
"urls": {
"title": "More Facebook Page URLs (optional)",
"type": "array",
"description": "Add extra pages here to scrape many in one run. Each page becomes its own dataset row.",
"editor": "stringList",
"sectionCaption": "Batch",
"sectionDescription": "Leave empty if you only want the single URL above.",
"default": []
},
"scrapeAboutPage": {
"title": "Also open the About page",
"type": "boolean",
"description": "Phone numbers, email and the website usually live on the About / Contact info tab, so keep this on for the best results. Turning it off makes runs faster but finds much less.",
"default": true,
"sectionCaption": "What to extract"
},
"maxAboutPages": {
"title": "Max About sub-pages per profile",
"type": "integer",
"description": "How many About tabs to try (contact info, details, about). The Actor stops early once it has the website plus a number.",
"default": 2,
"minimum": 0,
"maximum": 3
},
"extractPhonesFromText": {
"title": "Find phone / WhatsApp numbers in page text",
"type": "boolean",
"description": "Many pages write their number as plain text instead of a link. Turn this off if you only want numbers that come from real tel: / wa.me links.",
"default": true
},
"extractWebsitesFromText": {
"title": "Find websites written as plain text",
"type": "boolean",
"description": "Picks up domains typed in the bio or posts, e.g. \"shop now: mystore.pk\". Turn off for link-only results.",
"default": true
}
},
"required": [
"url"
]
}

I also added optional batch URLs and controls for opening About pages, extracting phone numbers from text, and finding websites written as plain text. The descriptions explain why those options exist, rather than leaving the AI or a human to guess what each setting does.
For the browser side, I used Playwright's asynchronous API. The Actor first tries to load a page with a network-idle wait. If Facebook stalls, it retries with DOM content loaded instead.
async def open_page(page, url):
try:
await page.goto(url, wait_until="networkidle", timeout=60000)
except Exception:
Actor.log.warning(f"networkidle timed out, retrying: {url}")
await page.goto(url, wait_until="domcontentloaded", timeout=60000)
I also made one bad URL fail without killing the whole run. Each URL is processed inside its own exception handler, and the error is pushed as a dataset item with the Facebook URL.
for url in urls:
try:
result = await scrape_one(page, url, options)
except Exception as exc:
Actor.log.exception(f"Failed to scrape {url}: {exc}")
result = {"facebookUrl": url, "error": str(exc)}

await Actor.push_data(result)
Enter fullscreen mode Exit fullscreen mode

Actor page

.
The Facebook Page Contact & Social Scraper Actor running on Apify
What the Actor returns
The useful part of the Actor is the structured result. The exact fields depend on what the Page exposes, but the Actor is designed to return fields such as the Facebook URL, Instagram, website, phone, WhatsApp, email, TikTok, YouTube, LinkedIn, and other detected social destinations.
I also normalize duplicate websites and phone numbers. For websites, the helper code compares normalized hostnames so variants such as www and non-www URLs do not become separate results. For phones, it uses the digits as a deduplication key and prefers an international format when both versions are found.
That mattered for the automation because I wanted structured data that could be consumed by another system, not a page full of raw links.

Actor input

Placement: Immediately after the input-schema explanation.
Caption: The Actor input form with a Facebook Page URL and the optional extraction controls.
Alt text: Apify Actor input form showing the Facebook Page URL field and extraction options.

Actor output
Structured results returned by the Actor after scraping a Facebook Page

Publishing the Actor turned the solution into a reusable tool
Once the Actor was working for the original workflow, I published it on Apify. That changed the project from a piece of code built for one automation into a reusable Actor that I could run independently and connect to other workflows.
I liked that separation. The Actor had one clear responsibility: take Facebook Page information and turn the publicly available data into structured output. The rest of the automation could decide what to do with those results.
The next question was whether an AI agent could use it
After the Actor was working, I started thinking about how an AI agent could use it. I did not want to manually open Apify, find the Actor, enter the Page URL, start a run, and then copy the result every time.
I wanted the agent to have the Actor available as a tool. That is where the Apify MCP server became useful.
Apify's MCP server exposes Actors as tools to external AI clients. The hosted server supports Streamable HTTP and can be connected to clients such as Claude Desktop. Apify also supports selecting specific Actors through the tools parameter, which was useful for my setup.
Official documentation: Apify MCP server documentation
Connecting my Actor to Claude through Apify MCP
I connected Claude to the hosted Apify MCP server and authorized my Apify account. The basic hosted endpoint is the remote MCP server at mcp.apify.com.
I initially asked Claude to find my Actor through the normal Actor search flow. That was not the right approach for my specific setup because I wanted Claude to use my particular Actor directly.
Instead, I configured the MCP server to expose the specific Actor I wanted Claude to use. For my setup, that Actor was:
daniyal_zia/my-actor-2
The important idea was not to make the agent search for a vaguely matching scraper every time. I wanted to give it a specific tool whose purpose and input I controlled.
After reconnecting Claude, the Actor became available to the conversation. Claude could see the tool, understand the input, call it, and receive the resulting data.

Claude connected to my Apify Actor through MCP

Claude with my specific Apify Actor available as a tool through the Apify MCP server

From a scraper to an AI tool
This was the point where the project changed for me. The Actor was no longer only something I ran manually or from an automation workflow. It had become a tool an AI agent could call when the user's request required the data it provided.
The workflow now looked like this:
User request

Claude

Apify MCP server

My Facebook Page Actor

Facebook Page

Structured result

Claude

Final answer
For example, I could ask Claude for the Instagram profile and website associated with a Facebook Page. Claude could use the Actor, receive the structured result, and return the relevant fields instead of making me run the Actor manually.
The separation of responsibilities is what I found most useful. The Actor handles extraction. The AI agent handles the user's request and decides how to use the available tool. MCP provides the connection between the agent and the Actor.

Claude calling the Actor and receiving its result

Claude calls my Facebook Page Actor through MCP and uses the returned data to answer the request

Making the Actor useful to an AI agent
Connecting an Actor to MCP is only part of the job. I also realized that an Actor intended for an AI agent needs clear inputs and predictable outputs.
The required input has a clear name: Facebook Page URL. Optional fields explain what they change. For example, the About-page option explains that contact information often lives there and that disabling it can make runs faster but return less data.
The same principle applies to the output. An agent should not have to guess whether a field called instagram means a profile URL or something else. Consistent field names and structured values make tool use easier to reason about.
The Actor also returns an explicit error for a failed URL instead of allowing one bad Page to stop the entire batch. That is useful for both automation and AI-agent workflows because the failure becomes data the next step can interpret.
What did not work
There were two moments where the implementation went differently from my first expectation.
First, I expected an existing Facebook scraper Actor to cover the complete output I needed. It did not match my specific requirements, so I built my own instead.
Second, I expected Claude's Actor search to find my particular Actor immediately. That was not how I wanted to work with my private, specific Actor. Exposing the Actor directly through the MCP tools configuration solved that problem.
Those two issues changed how I think about these integrations. Finding an existing tool is useful when it matches the job, but building a small, focused Actor can be more practical when the required input and output are specific.
What I learned
The biggest lesson was that building an Actor for a human and building an Actor that an AI agent can reliably use are slightly different problems.
When I was building the scraper for the original automation, I was focused mainly on extraction quality. After connecting it to Claude, I paid much more attention to the interface: what the input means, what the output means, and what happens when something goes wrong.
I also learned that MCP does not replace the Actor. It gives an AI client a standard way to access the Actor as a tool. The Actor still owns the actual scraping logic and the data extraction decisions.
What I would do differently next time
If I started this project again, I would think about the AI-agent interface earlier. I would design the input schema with the tool-selection problem in mind from the beginning and make the output schema as explicit as possible.
I would also document the Actor's intended use more clearly from the first version. An AI agent has to understand not only what a tool accepts, but also when that tool is appropriate.
Conclusion
What started as a LinkedIn request for automated Meta Ads research turned into a reusable Apify Actor and then into an AI-callable tool.
I built the Actor because I could not find an existing solution that matched the exact Facebook Page information my workflow needed. After publishing it, I connected the Actor to Claude through the Apify MCP server so an AI agent could call it instead of requiring a manual Actor run.
For me, the interesting part was not simply scraping another Facebook Page. It was seeing how a focused Actor could move from a client-specific automation component to a reusable tool inside an AI workflow.
The complete source code for the Actor is available in the GitHub repository below.
GitHub repository: https://github.com/daniyalzia332/Facebook-Page-Scraper-Social-Links-Contact-Info
FAQ
Can an Apify Actor be used by an AI agent?
Yes. The Apify MCP server exposes Actors as tools to compatible AI clients. The agent can call the Actor and receive its results as part of the conversation or workflow.
Why did I use MCP instead of manually running the Actor?
MCP removed the manual handoff between the AI conversation and the Actor. Instead of switching to Apify, starting a run, and copying the result, Claude could access the Actor as a tool.
What does my Actor accept?
The primary input is a Facebook Page URL. It also supports optional batch URLs and controls for About-page scraping and text-based phone and website extraction.
What makes an Actor easier for an AI agent to use?
Clear input names, useful descriptions, predictable output fields, and explicit error handling make the Actor easier for an AI agent to understand and call reliably.
Author bio
Daniyal Zia is a developer and automation specialist working with AI automation, web development, CRM integrations, and workflow automation. He builds practical automation systems using tools such as Apify, Playwright, n8n, and other AI and automation platforms. His work focuses on turning repetitive research and business processes into reusable technical workflows.
Sources and documentation
Apify MCP server: https://docs.apify.com/integrations/mcp
Claude Desktop integration: https://docs.apify.com/integrations/claude-desktop
Apify Actors: https://docs.apify.com/actors

Top comments (0)