PHPackages                             vsimke/article-finder - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. vsimke/article-finder

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

vsimke/article-finder
=====================

Framework-agnostic PHP package for finding articles via Bing and Google SERP scraping using a chain-of-responsibility finder pattern.

v1.0.1(1mo ago)02MITPHPPHP &gt;=8.2CI passing

Since Apr 22Pushed 1mo agoCompare

[ Source](https://github.com/vsimke/article-finder)[ Packagist](https://packagist.org/packages/vsimke/article-finder)[ RSS](/packages/vsimke-article-finder/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (6)Versions (3)Used By (0)

Article Finder
==============

[](#article-finder)

A framework-agnostic PHP package for finding published articles via Bing and Google SERP scraping, using a chain-of-responsibility finder pattern.

[![Build Status](https://github.com/vsimke/article-finder/workflows/tests/badge.svg)](https://github.com/vsimke/article-finder/actions)[![Total Downloads](https://camo.githubusercontent.com/0eb07dc56496295c1148daebbda3862b864c9557c9fb0f7525b24b53d414ed34/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7673696d6b652f61727469636c652d66696e646572)](https://packagist.org/packages/vsimke/article-finder)[![Latest Stable Version](https://camo.githubusercontent.com/07f7d97c054eb3d5eb013f9c60052b18fa7a9a9d642ca7448340b60badb84365/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7673696d6b652f61727469636c652d66696e646572)](https://packagist.org/packages/vsimke/article-finder)[![GitHub Tag](https://camo.githubusercontent.com/5909bdc3bf5e8945e81b3826666abaff50f9e013edf2a4122000c44b97529f0d/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f7461672f7673696d6b652f61727469636c652d66696e646572)](https://github.com/vsimke/article-finder/releases)[![License](https://camo.githubusercontent.com/8e5c865486d3a8d318257adf2730bef42629994d67e50fa0a534b2e8caed1878/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7673696d6b652f61727469636c652d66696e646572)](https://packagist.org/packages/vsimke/article-finder)

Given a site domain and article title, the package queries Bing first and falls back to Google if no sufficiently similar result is found. You can swap or extend the chain with your own finders.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Guzzle 7+

Installation
------------

[](#installation)

```
composer require vsimke/article-finder
```

Usage
-----

[](#usage)

### Default chain (Bing → Google)

[](#default-chain-bing--google)

```
use Vsimke\ArticleFinder\ArticleFinder;
use Vsimke\ArticleFinder\FinderParameter;
use Vsimke\ArticleFinder\Scraper\HQueryScraper;

$scraper = new HQueryScraper();
$finder  = ArticleFinder::create($scraper);

$result = $finder->find([
    FinderParameter::SITE  => 'example.com',
    FinderParameter::TITLE => 'My Article Title',
]);

if ($result !== false) {
    echo $result['title'];  // 'My Article Title'
    echo $result['link'];   // 'https://example.com/my-article-title'
    echo $result['finder']; // 'www.bing.com' or 'www.google.com'
}
```

`ArticleFinder::find()` returns `array` on success or `false` when no match is found across the whole chain.

### Dependency injection

[](#dependency-injection)

```
use Vsimke\ArticleFinder\ArticleFinder;
use Vsimke\ArticleFinder\FinderParameter;
use Vsimke\ArticleFinder\Scraper\HQueryScraper;

class ArticleOnlineChecker
{
    public function __construct(private readonly ArticleFinder $finder) {}

    public function check(string $site, string $title): array|false
    {
        return $this->finder->find([
            FinderParameter::SITE  => $site,
            FinderParameter::TITLE => $title,
        ]);
    }
}

// Wire it up
$checker = new ArticleOnlineChecker(
    ArticleFinder::create(new HQueryScraper())
);
```

### Scraper options

[](#scraper-options)

Pass a custom `ClientInterface` or override the config array to control transport:

```
use GuzzleHttp\Client;
use Vsimke\ArticleFinder\Scraper\HQueryScraper;

// Custom Guzzle client (e.g. with a proxy)
$client  = new Client(['proxy' => 'socks5://127.0.0.1:1080']);
$scraper = new HQueryScraper($client);

// Override User-Agent
$scraper = new HQueryScraper(config: [
    'headers' => [
        'User-Agent' => 'MyBot/1.0',
    ],
]);
```

### Custom finder chain

[](#custom-finder-chain)

Build your own chain by extending `Finder` and wiring it with `setFinder()`:

```
use Vsimke\ArticleFinder\Finders\Finder;

class DuckDuckGoFinder extends Finder
{
    public function find(array $parameters): array
    {
        // ... scrape DuckDuckGo ...

        if ($article['link'] ?? null) {
            return $article;
        }

        return parent::find($parameters); // delegate to next in chain
    }
}

$ddg    = new DuckDuckGoFinder();
$google = new GoogleArticleFinder($scraper);
$ddg->chain($google);

$finder->setFinder($ddg);
```

How it works
------------

[](#how-it-works)

```
ArticleFinder::find()
  └─ BingArticleFinder::find()        ← queries www.bing.com
       ├─ match found → return result
       └─ no match    → GoogleArticleFinder::find()   ← queries www.google.com
                          ├─ match found → return result
                          └─ no match    → []  →  ArticleFinder returns false

```

A result is considered a match when `similar_text()` similarity between the SERP title and the search title exceeds 70%.

> **Note on fragility:** SERP markup changes regularly. The parser fixture tests in `tests/Unit/` are the safety net — update the fixtures if Bing or Google changes their HTML structure.

Development
-----------

[](#development)

### Run tests

[](#run-tests)

```
./vendor/bin/pest
```

### Static analysis

[](#static-analysis)

```
./vendor/bin/phpstan analyse
```

### Code style (PSR-12)

[](#code-style-psr-12)

```
# Check only
./vendor/bin/pint --test

# Fix
./vendor/bin/pint
```

### Rector

[](#rector)

```
# Dry run
./vendor/bin/rector process --dry-run

# Apply
./vendor/bin/rector process
```

### Release

[](#release)

```
.github/release.sh patch   # 1.2.3 → 1.2.4  (default)
.github/release.sh minor   # 1.2.3 → 1.3.0
.github/release.sh major   # 1.2.3 → 2.0.0
```

The script auto-detects the latest `vX.Y.Z` tag, bumps the requested segment, prompts for confirmation, then tags and pushes.

License
-------

[](#license)

MIT — see [LICENSE](https://opensource.org/licenses/MIT).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

47d ago

PHP version history (2 changes)v1.0.0PHP &gt;=8.1

v1.0.1PHP &gt;=8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/5d28d40355dcf83673ab7a0440e9928e83f2d942ed304b79a603fb0885d0891c?d=identicon)[vsimke](/maintainers/vsimke)

---

Top Contributors

[![vsimke](https://avatars.githubusercontent.com/u/5629780?v=4)](https://github.com/vsimke "vsimke (4 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/vsimke-article-finder/health.svg)

```
[![Health](https://phpackages.com/badges/vsimke-article-finder/health.svg)](https://phpackages.com/packages/vsimke-article-finder)
```

###  Alternatives

[neuron-core/neuron-ai

The PHP Agentic Framework.

1.9k496.1k32](/packages/neuron-core-neuron-ai)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3751.2M45](/packages/tencentcloud-tencentcloud-sdk-php)[civicrm/civicrm-core

Open source constituent relationship management for non-profits, NGOs and advocacy organizations.

744284.3k34](/packages/civicrm-civicrm-core)[roundcube/roundcubemail

The Roundcube Webmail suite

7.0k1.4k3](/packages/roundcube-roundcubemail)[spatie/laravel-export

Create a static site bundle from a Laravel app

670139.5k6](/packages/spatie-laravel-export)[nfse-nacional/nfse-php

This is my package nfse

1493.1k](/packages/nfse-nacional-nfse-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
