piedweb/facebook-scraper
A PHP package for scraping public Facebook page content, helping retrieve posts and related data without relying on the Facebook Graph API. Suitable for lightweight Laravel/PHP projects that need basic Facebook data extraction.
Start by confirming what the package actually returns before designing anything around it — with 3 stars and 0 dependents you are effectively the QA team.
Install
composer require piedweb/facebook-scraper
There is no service provider or config to publish in most versions; it's a plain PHP class you instantiate.
Look here first
src/ — usually a single FacebookScraper class (or similarly named). Read it top to bottom; it's small.tests/ — the fixtures tell you exactly which Facebook HTML shape the parser expects and therefore how brittle it is.composer.json — check whether it pulls Guzzle, Symfony DomCrawler, or symfony/http-client. That tells you where network/parse failures will surface.First use case — pull the basics off one known-public page:
use PiedWeb\FacebookScraper\FacebookScraper;
$scraper = new FacebookScraper('https://www.facebook.com/somepublicpage');
$data = $scraper->getData(); // check README: may be ->scrape(), ->get(), etc.
// Inspect the raw shape before you type-hint anything
dd($data);
Dump first, model second. The return type (array vs. object vs. JSON string) varies between versions and there is no interface contract to lean on.
Prove it end-to-end with a page you control or one you know is public and stable (a brand page, not a personal profile). If that works, you have a baseline; if it returns empty, the parse target has already drifted — see Gotchas.
Wrap it, don't call it directly from controllers. Scraping is slow, fragile, and network-bound. Put it behind a small service:
final class FacebookPageHarvester
{
public function harvest(string $url): ?FacebookPageData
{
$scraper = new FacebookScraper($url);
$raw = $scraper->getData();
return FacebookPageData::fromScraperArray($raw); // your DTO
}
}
This gives you one place to patch when Facebook changes markup, and lets you swap the package out later without touching call sites — likely, given the project's size.
Queue it, always. A scrape is an outbound HTTP request plus DOM parsing. Run it in a job with $tries = 3, backoff() of several minutes, and a failed() handler that flags the record rather than throwing into the void.
class HarvestFacebookPage implements ShouldQueue
{
public int $tries = 3;
public array $backoff = [60, 300, 900];
public function handle(FacebookPageHarvester $harvester): void
{
$data = $harvester->harvest($this->page->url);
$this->page->update(['payload' => $data?->toArray(), 'harvested_at' => now()]);
}
}
Cache aggressively, scrape rarely. Public page metadata changes on the order of hours, not seconds. Cache the parsed result (not the raw HTML) for 6–24 hours and serve reads from the database. This is your primary defence against rate limiting.
Cache::remember("fb:page:{$id}", now()->addHours(12), fn () => $harvester->harvest($url));
Schedule instead of on-demand. A nightly php artisan fb:harvest command iterating known pages, dispatched as individual jobs with ->delay() staggered 30–90 seconds apart, keeps you under the radar far better than burst requests triggered by user traffic.
Store raw + parsed. Persist the response body alongside your DTO. When a parse breaks, you can replay fixtures locally without re-hitting Facebook:
Storage::put("fb-raw/{$page->id}-".now()->timestamp.'.html', $rawHtml);
Fake it in tests. Never hit Facebook in CI. Bind a fake harvester that returns stored fixtures from tests/fixtures/*.html, and add at least one test asserting your DTO survives a genuinely malformed/empty response.
Integration tip: if you need images or post media, download them separately and store on your own disk/S3 — the CDN URLs Facebook returns are signed and expire.
The parser is the weak point, not the HTTP layer. Facebook ships obfuscated, frequently-rebundled HTML with hashed class names. Any selector in this package is a moving target. Budget for the scraper silently returning empty arrays after a Facebook deploy. Treat empty output as an error, not a valid result — assert on required fields in your DTO constructor and throw.
Consent walls and login redirects. From many IPs and regions, an unauthenticated request gets a cookie-consent interstitial or a login page with HTTP 200. Check for a known page-title or a required DOM node before trusting the body. A 200 OK means nothing here.
JavaScript-rendered content is invisible. Plain HTTP fetching cannot see anything injected client-side. If the field you want is missing from the raw HTML, the package cannot help you — you'd need a headless browser, which is out of scope. Verify what you need actually exists in the served HTML first.
Send realistic headers. A default Guzzle user agent gets blocked fast. Many versions let you pass a client or options — read the constructor. Consider a custom User-Agent, Accept-Language, and a cookie jar.
Rate limits and IP reputation. Scraping a handful of public pages nightly from one IP is usually fine; hundreds will get you 429s or a soft block. Rotate delays, respect Retry-After, and consider proxy support if you scale. There is no API quota to check — you're inferring limits from responses.
Debugging workflow when results go empty:
Config quirks: there may be none — no config file, no env vars. Set HTTP timeouts and retries on the underlying client rather than expecting the package to expose them. Always wrap calls in try/catch for transport exceptions (connection reset, TLS, timeout) — these are distinct from "parsed nothing."
Extension points are limited. The most reliable extension is not subclassing the package but replacing a piece of it in your own service layer (custom fetcher, custom parser, custom DTO). Given the zero-dependent, three-star status, plan for eventual replacement: keep the package isolated behind one interface so a maintained fork or your own DomCrawler implementation can slot in.
Legal and ToS. Scraping Facebook public pages sits in a grey area against their Terms of Service regardless of the MIT license on the code. Confirm with whoever owns compliance that the specific pages and data you harvest are acceptable, and avoid personal data entirely.
How can I help you explore Laravel packages today?