How to Monitor Facebook Pages When There's No Keyword Search
On X you can search for your brand and get every recent mention. TikTok and Reddit work roughly the same way. Facebook doesn't. Its own API dropped public post search years ago, and our Facebook endpoints don't offer one either. You can't ask for every public post that mentions a word.
That's usually the first thing people discover when they start building Facebook monitoring, and it changes the design completely. Instead of searching Facebook for a keyword, you build a list of pages that matter, check them on a schedule, and do the keyword matching yourself.
It's more work up front. It's also more dependable than it sounds, because you know exactly what you're covering. Here's the setup we'd use.
Step 1: Build the watch list
Most teams already know their core pages: their own, their competitors', the industry publications, a few loud customers. The hard part is the pages you don't know about yet.
Google helps here. Our /google/search endpoint takes an ordinary Google query, so you can ask for Facebook results on a topic:
GET /v1/scrape/google/search?query=site:facebook.com "artemis"
When we ran that, all ten results were on facebook.com. The first few were NASA's Artemis page, several of its videos, a video from another page, and a post in a public group. That's a decent discovery feed. Pull the page name out of each URL, review the list by hand, and add the ones worth watching.
Treat it as discovery, though, not as the monitor. It only finds what Google has indexed, which lags behind Facebook and misses plenty. It can also take a date_posted filter, which helps when you only care about recent results.
Step 2: Check each page on a schedule
/facebook/profile/posts takes a page url and returns its recent posts. Each one has an id, the post text, a url, reactionCount, commentCount, the author, and publishTime as a Unix timestamp in seconds. If you're merging those with other platforms, parsing dates from social media APIs covers the seconds-versus-milliseconds trap.
The posts come back in small batches. When we checked NASA's page, we got three posts and a cursor for the next batch. So on a busy page, one request might not reach back as far as your last check. The monitor below follows the cursor until it hits posts it has already seen, with a hard cap so a runaway loop can't eat your credits.
Step 3: Match keywords yourself
Once you have the text, matching is the easy part, and the best advice is to keep it boring. Case-insensitive, whole words, a short explicit list of terms per client. People burn days on fuzzy matching and stemming. You'll get further by reading a week of real matches and adding the plurals and misspellings you actually see.
Comments are where a lot of the real conversation happens, especially under brand posts. For posts that matter, /facebook/post/comments takes the post url and returns comments along with a cursor and has_next_page. Be selective. Pull comments for posts that matched, or posts whose comment counts jumped, not for everything. Comments are where costs climb fastest.
Step 4: Don't count the same post twice
A Facebook post can have two IDs, and which one you get back depends on the link you used. We dug into that in Facebook post URL formats. For monitoring, the fix is simple: store the post's id from the page feed and the number in its url, and treat a match on either as the same post.
The monitor
This checks a list of pages, stops at posts it's seen before, and returns the new posts that match your terms:
const API = "https://api.sociavault.com/v1/scrape/facebook";
const KEY = process.env.SOCIAVAULT_API_KEY;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const toArray = (x) => (Array.isArray(x) ? x : x ? Object.values(x) : []);
async function call(path, params) {
const res = await fetch(`${API}/${path}?${new URLSearchParams(params)}`, {
headers: { "x-api-key": KEY },
});
const body = await res.json();
if (body?.error) throw new Error(body.error);
return body.data ?? {};
}
function keywordMatcher(terms) {
const escaped = terms.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
return new RegExp(`\\b(${escaped.join("|")})\\b`, "i");
}
// state.seen: post IDs you've already processed; state.since: Unix seconds of the last check
async function checkPage(pageUrl, matcher, state, { maxPages = 3 } = {}) {
const hits = [];
let cursor = null;
for (let page = 0; page < maxPages; page++) {
const data = await call("profile/posts", cursor ? { url: pageUrl, cursor } : { url: pageUrl });
const posts = toArray(data.posts);
let reachedOld = false;
for (const p of posts) {
const id = String(p.id);
if (state.seen.has(id) || Number(p.publishTime) <= state.since) {
reachedOld = true;
continue;
}
state.seen.add(id);
const text = String(p.text ?? "");
if (matcher.test(text)) {
hits.push({
page: pageUrl,
id,
url: p.url,
text,
reactions: p.reactionCount ?? null,
comments: p.commentCount ?? null,
at: new Date(Number(p.publishTime) * 1000).toISOString(),
});
}
}
cursor = data.cursor;
if (reachedOld || !cursor || !posts.length) break;
await sleep(1000);
}
return hits;
}
const pages = ["https://www.facebook.com/nasa", "https://www.facebook.com/NASAArtemis"];
const matcher = keywordMatcher(["artemis", "crew-13"]);
const state = { seen: new Set(), since: Math.floor(Date.now() / 1000) - 86400 }; // first run: last 24h
for (const page of pages) {
for (const hit of await checkPage(page, matcher, state)) {
console.log(hit.at, hit.page, hit.text.slice(0, 80));
}
await sleep(1000);
}
In a real deployment, state.seen and state.since live in a database between runs. In memory they reset every time the script restarts, and you'd re-alert on everything.
The Python version:
import os, re, time, requests
API = "https://api.sociavault.com/v1/scrape/facebook"
HEADERS = {"x-api-key": os.environ["SOCIAVAULT_API_KEY"]}
def to_list(x):
return x if isinstance(x, list) else list((x or {}).values())
def call(path, **params):
body = requests.get(f"{API}/{path}", params=params, headers=HEADERS, timeout=90).json()
if body.get("error"):
raise RuntimeError(body["error"])
return body.get("data") or {}
def check_page(page_url, pattern, seen, since, max_pages=3):
hits, cursor = [], None
for _ in range(max_pages):
data = call("profile/posts", url=page_url, **({"cursor": cursor} if cursor else {}))
posts = to_list(data.get("posts"))
reached_old = False
for p in posts:
post_id = str(p.get("id"))
if post_id in seen or int(p.get("publishTime") or 0) <= since:
reached_old = True
continue
seen.add(post_id)
text = p.get("text") or ""
if pattern.search(text):
hits.append({"id": post_id, "url": p.get("url"), "text": text,
"reactions": p.get("reactionCount"), "at": p.get("publishTime")})
cursor = data.get("cursor")
if reached_old or not cursor or not posts:
break
time.sleep(1)
return hits
pattern = re.compile(r"\b(artemis|crew-13)\b", re.I)
seen, since = set(), int(time.time()) - 86400
for page in ["https://www.facebook.com/nasa", "https://www.facebook.com/NASAArtemis"]:
for hit in check_page(page, pattern, seen, since):
print(hit["at"], hit["text"][:80])
time.sleep(1)
What it costs, and how often to check
Every check of a page is at least one request, and each request is a credit. The maths is simple and it adds up quickly:
| Pages | Checked every | Requests a month, at least |
|---|---|---|
| 50 | hour | about 36,000 |
| 50 | 6 hours | about 6,000 |
| 200 | 6 hours | about 24,000 |
| 200 | day | about 6,000 |
Busy pages can need an extra request per check to reach back far enough, so treat those as floors.
Match the schedule to the page, not to your anxiety. A brand that posts twice a week doesn't need hourly checks. A news outlet might. Most watch lists settle into two or three tiers: a handful of pages checked hourly, most checked a few times a day, and a long tail checked daily.
Two other places mentions turn up
Ads. Unlike organic posts, Facebook's Ad Library is searchable by keyword. /facebook-ad-library/search takes a query, so you can catch competitors running ads that mention you, even from pages you weren't watching. Our Facebook Ad Library guide walks through the filters.
Public groups. A lot of the most candid talk happens in groups. Public groups have their own feed endpoint, /facebook/group/posts, and the same check-and-match loop works on them.
When something does match, someone needs to see it fast. Sending change alerts to Slack covers that last step.
What this approach misses
- Pages you're not watching. A mention on a page you've never heard of stays invisible until discovery turns it up.
- Anything Google hasn't indexed. Discovery through search is partial and slow, which is why it feeds the watch list rather than replacing it.
- Private profiles and private groups. Only public content is available, and most personal profiles aren't public, so expect your coverage to be mostly pages.
- Real time. This is polling. Your delay is your schedule, so a page checked every six hours can be up to six hours behind.
- Cheap comments. Pulling every comment on every post gets expensive fast. Pick your moments.
Frequently Asked Questions
Can you search Facebook posts by keyword?
Not for public posts. Facebook removed public post search from its official API years ago, and there's no keyword search endpoint for organic posts. You monitor known pages instead and match keywords in the text yourself.
How do I monitor brand mentions on Facebook?
Build a list of relevant pages, check each one's recent posts on a schedule, and search the post text for your terms. For conversation under posts, fetch comments selectively. For ads, use the Ad Library's keyword search.
How often should I check Facebook pages?
Match it to how often each page posts. Hourly for a few high-priority pages, a few times a day for most, daily for the long tail. Fifty pages checked hourly is about 36,000 requests a month.
Can I search Facebook ads by keyword?
Yes. The Ad Library is searchable, and /facebook-ad-library/search takes a query. It's the one part of Facebook where keyword search works.
Can I monitor Facebook groups?
Public groups, yes, through /facebook/group/posts. Private groups aren't accessible.
How do I find Facebook pages that mention a topic?
Run a Google search restricted to Facebook, such as site:facebook.com plus your keyword in quotes. It surfaces pages, videos and group posts that Google has indexed, which you can then add to your watch list.
Want to try it on your own watch list? Sign up for 50 free credits and run the monitor against a few pages you already follow.
Found this helpful?
Share it with others who might benefit
Ready to Try SociaVault?
Start extracting social media data with our powerful API. No credit card required.