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(3mo ago)02MITPHPPHP &gt;=8.2CI passing

Since Apr 22Pushed 3mo 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 3w 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

36

—

LowBetter than 79% of packages

Maintenance82

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

92d 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.

2.0k656.1k46](/packages/neuron-core-neuron-ai)[tencentcloud/tencentcloud-sdk-php

TencentCloudApi php sdk

3661.3M47](/packages/tencentcloud-tencentcloud-sdk-php)[spatie/laravel-export

Create a static site bundle from a Laravel app

674146.0k6](/packages/spatie-laravel-export)[eslazarev/wildberries-sdk

Wildberries OpenAPI clients (generated).

293.1k](/packages/eslazarev-wildberries-sdk)[oat-sa/tao-core

TAO core extension

65143.7k124](/packages/oat-sa-tao-core)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)

PHPackages © 2026

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