PHPackages                             tacman/graby - 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. tacman/graby

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

tacman/graby
============

Graby helps you extract article content from web pages, fork of j0k3r/graby

3.0.4(1y ago)0396MITPHPPHP ^8.2

Since Aug 29Pushed 1y agoCompare

[ Source](https://github.com/tacman/graby)[ Packagist](https://packagist.org/packages/tacman/graby)[ GitHub Sponsors](https://github.com/j0k3r)[ RSS](/packages/tacman-graby/feed)WikiDiscussions tac Synced 1mo ago

READMEChangelog (5)Dependencies (26)Versions (105)Used By (0)

composer config repositories.graby '{"type": "path", "url": "~/g/tacman/graby"}' composer req tacman/graby:"\*@dev"

 [![Graby logo](https://user-images.githubusercontent.com/62333/67490348-5dfc5280-f673-11e9-9b3d-584e6cbeb9e2.png)](https://user-images.githubusercontent.com/62333/67490348-5dfc5280-f673-11e9-9b3d-584e6cbeb9e2.png)

[![Join the chat at https://gitter.im/j0k3r/graby](https://camo.githubusercontent.com/09d72e9494e3ee2522beb3cbfd045c1de337907acd6d1be556b98f4f5c718881/68747470733a2f2f6261646765732e6769747465722e696d2f6a306b33722f67726162792e737667)](https://gitter.im/j0k3r/graby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)[![CI](https://github.com/j0k3r/graby/workflows/CI/badge.svg)](https://github.com/j0k3r/graby/workflows/CI/badge.svg)[![Coverage Status](https://camo.githubusercontent.com/1a40e0aba5a63dc090e6c8f6f1b5999030018f68ae6f201533f612ec52232786/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6a306b33722f67726162792f62616467652e7376673f6272616e63683d6d617374657226736572766963653d676974687562)](https://coveralls.io/github/j0k3r/graby?branch=master)[![Total Downloads](https://camo.githubusercontent.com/9664da4f49ad06b8592d7dd9529dd099452f27a1a09736d119a925c057cc431c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6a306b33722f67726162792e737667)](https://packagist.org/packages/j0k3r/graby)[![License](https://camo.githubusercontent.com/284e8d3ae3dea7ad15d2cc71b8c87082338f37a87333b1738a9c61f659d6ab42/68747470733a2f2f706f7365722e707567782e6f72672f6a306b33722f67726162792f6c6963656e7365)](https://packagist.org/packages/j0k3r/graby)

Graby helps you extract article content from web pages

- it's based on [php-readability](https://github.com/j0k3r/php-readability)
- it uses [site\_config](http://help.fivefilters.org/customer/portal/articles/223153-site-patterns) to extract content from websites
- it's a fork of Full-Text RSS v3.3 from [@fivefilters](http://fivefilters.org/)

Why this fork ?
---------------

[](#why-this-fork-)

Full-Text RSS works great as a standalone application. But when you need to encapsulate it in your own library it's a mess. You need this kind of ugly thing:

```
$article = 'http://www.bbc.com/news/entertainment-arts-32547474';
$request = 'http://example.org/full-text-rss/makefulltextfeed.php?format=json&url='.urlencode($article);
$result  = @file_get_contents($request);
```

Also, if you want to understand how things work internally, it's really hard to read and understand. And finally, there are **no tests** at all.

That's why I made this fork:

1. Easiest way to integrate it (using composer)
2. Fully tested
3. (hopefully) better to understand
4. A bit more decoupled

How to use it
-------------

[](#how-to-use-it)

> **Note**These instructions are for development version of Graby, which has an API incompatible with the stable version. Please check out the [README in the `2.x` branch](https://github.com/j0k3r/graby/blob/2.x/README.md#how-to-use-it) for usage instructions for the stable version.

### Requirements

[](#requirements)

- PHP &gt;= 8.2
- [Tidy](https://github.com/htacg/tidy-html5) &amp; cURL extensions enabled

### Installation

[](#installation)

Add the lib using [Composer](https://getcomposer.org/):

```
composer require 'j0k3r/graby dev-master' php-http/guzzle7-adapter

```

Why `php-http/guzzle7-adapter`? Because Graby is decoupled from any HTTP client implementation, thanks to [HTTPlug](http://httplug.io/) (see [that list of client implementation](https://packagist.org/providers/php-http/client-implementation)).

Graby is tested &amp; should work great with:

- Guzzle 7 (using `php-http/guzzle7-adapter`)
- cURL (using `php-http/curl-client`)

Note: if you want to use Guzzle 5 or 6, use Graby 2 (support has dropped in v3 because of dependencies conflicts)

### Retrieve content from an url

[](#retrieve-content-from-an-url)

Use the class to retrieve content:

```
use Graby\Graby;

$article = 'http://www.bbc.com/news/entertainment-arts-32547474';

$graby = new Graby();
$result = $graby->fetchContent($article);

var_dump($result->getResponse()->getStatus()); // 200
var_dump($result->getHtml()); // "[Fetched and readable content…]"
var_dump($result->getTitle()); // "Ben E King: R&B legend dies at 76"
var_dump($result->getLanguage()); // "en-GB"
var_dump($result->getDate()); // "2015-05-01T16:24:37+01:00"
var_dump($result->getAuthors()); // ["BBC News"]
var_dump((string) $result->getResponse()->getEffectiveUri()); // "http://www.bbc.com/news/entertainment-arts-32547474"
var_dump($result->getImage()); // "https://ichef-1.bbci.co.uk/news/720/media/images/82709000/jpg/_82709878_146366806.jpg"
var_dump($result->getSummary()); // "Ben E King received an award from the Songwriters Hall of Fame in &hellip;"
var_dump($result->getIsNativeAd()); // false
var_dump($result->getResponse()->getHeaders()); /*
[
  'server' => ['Apache'],
  'content-type' => ['text/html; charset=utf-8'],
  'x-news-data-centre' => ['cwwtf'],
  'content-language' => ['en'],
  'x-pal-host' => ['pal074.back.live.cwwtf.local:80'],
  'x-news-cache-id' => ['13648'],
  'content-length' => ['157341'],
  'date' => ['Sat, 29 Apr 2017 07:35:39 GMT'],
  'connection' => ['keep-alive'],
  'cache-control' => ['private, max-age=60, stale-while-revalidate'],
  'x-cache-action' => ['MISS'],
  'x-cache-age' => ['0'],
  'x-lb-nocache' => ['true'],
  'vary' => ['X-CDN,X-BBC-Edge-Cache,Accept-Encoding'],
]
*/
```

In case of error when fetching the url, graby won't throw an exception but will return information about the error (at least the status code):

```
var_dump($result->getResponse()->getStatus()); // 200
var_dump($result->getHtml()); // "[unable to retrieve full-text content]"
var_dump($result->getTitle()); // "BBC - 404: Not Found"
var_dump($result->getLanguage()); // "en-GB"
var_dump($result->getDate()); // null
var_dump($result->getAuthors()); // []
var_dump((string) $result->getResponse()->getEffectiveUri()); // "http://www.bbc.co.uk/404"
var_dump($result->getImage()); // null
var_dump($result->getSummary()); // "[unable to retrieve full-text content]"
var_dump($result->getIsNativeAd()); // false
var_dump($result->getResponse()->getHeaders()); // […]
```

The `date` result is the same as displayed in the content. If `date` is not `null` in the result, we recommend you to parse it using [`date_parse`](http://php.net/date_parse) (this is what we are using to validate that the date is correct).

### Retrieve content from a prefetched page

[](#retrieve-content-from-a-prefetched-page)

If you want to extract content from a page you fetched outside of Graby, you can call `setContentAsPrefetched()` before calling `fetchContent()`, e.g.:

```
use Graby\Graby;

$article = 'http://www.bbc.com/news/entertainment-arts-32547474';

$input = '[...]';

$graby = new Graby();
$graby->setContentAsPrefetched($input);
$result = $graby->fetchContent($article);
```

### Cleanup content

[](#cleanup-content)

Since the 1.9.0 version, you can also send html content to be cleanup in the same way graby clean content retrieved from an url. The url is still needed to convert links to absolute, etc.

```
use Graby\Graby;

$article = 'http://www.bbc.com/news/entertainment-arts-32547474';
// use your own way to retrieve html or to provide html
$html = ...

$graby = new Graby();
$result = $graby->cleanupHtml($html, $article);
```

### Use custom handler &amp; formatter to see output log

[](#use-custom-handler--formatter-to-see-output-log)

You can use them to display graby output log to the end user. It's aim to be used in a Symfony project using Monolog.

Define the graby handler service (somewhere in a `service.yml`):

```
services:
    # ...
    graby.log_handler:
        class: Graby\Monolog\Handler\GrabyHandler
```

Then define the Monolog handler in your `app/config/config.yml`:

```
monolog:
    handlers:
        graby:
            type: service
            id: graby.log_handler
            # use "debug" to got a lot of data (like HTML at each step) otherwise "info" is fine
            level: debug
            channels: ['graby']
```

You can then retrieve logs from graby in your controller using:

```
$logs = $this->get('monolog.handler.graby')->getRecords();
```

### Timeout configuration

[](#timeout-configuration)

If you need to define a timeout, you must create the `Http\Client\HttpClient` manually, configure it and inject it to `Graby\Graby`.

- For Guzzle 7:

    ```
    use Graby\Graby;
    use GuzzleHttp\Client as GuzzleClient;
    use Http\Adapter\Guzzle7\Client as GuzzleAdapter;

    $guzzle = new GuzzleClient([
        'timeout' => 2,
    ]);
    $graby = new Graby([], new GuzzleAdapter($guzzle));
    ```

Full configuration
------------------

[](#full-configuration)

This is the full documented configuration and also the default one.

```
$graby = new Graby([
    // Enable or disable debugging.
    // This will only generate log information in a file (log/graby.log)
    'debug' => false,
    // use 'debug' value if you want more data (HTML at each step for example) to be dumped in a different file (log/html.log)
    'log_level' => 'info',
    // If enabled relative URLs found in the extracted content are automatically rewritten as absolute URLs.
    'rewrite_relative_urls' => true,
    // If enabled, we will try to follow single page links (e.g. print view) on multi-page articles.
    // Currently this only happens for sites where single_page_link has been defined
    // in a site config file.
    'singlepage' => true,
    // If enabled, we will try to follow next page links on multi-page articles.
    // Currently this only happens for sites where next_page_link has been defined
    // in a site config file.
    'multipage' => true,
    // Error message when content extraction fails
    'error_message' => '[unable to retrieve full-text content]',
    // Default title when we won't be able to extract a title
    'error_message_title' => 'No title found',
    // List of URLs (or parts of a URL) which will be accept.
    // If the list is empty, all URLs (except those specified in the blocked list below)
    // will be permitted.
    // Example: array('example.com', 'anothersite.org');
    'allowed_urls' => [],
    // List of URLs (or parts of a URL) which will be not accept.
    // Note: this list is ignored if allowed_urls is not empty
    'blocked_urls' => [],
    // If enabled, we'll pass retrieved HTML content through htmLawed with
    // safe flag on and style attributes denied, see
    // http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/htmLawed_README.htm#s3.6
    // Note: if enabled this will also remove certain elements you may want to preserve, such as iframes.
    'xss_filter' => true,
    // Here you can define different actions based on the Content-Type header returned by server.
    // MIME type as key, action as value.
    // Valid actions:
    // * 'exclude' - exclude this item from the result
    // * 'link' - create HTML link to the item
    'content_type_exc' => [
       'application/zip' => ['action' => 'link', 'name' => 'ZIP'],
       'application/pdf' => ['action' => 'link', 'name' => 'PDF'],
       'image' => ['action' => 'link', 'name' => 'Image'],
       'audio' => ['action' => 'link', 'name' => 'Audio'],
       'video' => ['action' => 'link', 'name' => 'Video'],
       'text/plain' => ['action' => 'link', 'name' => 'Plain text'],
    ],
    // How we handle link in content
    // Valid values :
    // * preserve: nothing is done
    // * footnotes: convert links as footnotes
    // * remove: remove all links
    'content_links' => 'preserve',
    'http_client' => [
        // User-Agent used to fetch content
        'ua_browser' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.92 Safari/535.2',
        // default referer when fetching content
        'default_referer' => 'http://www.google.co.uk/url?sa=t&source=web&cd=1',
        // Currently allows simple string replace of URLs.
        // Useful for rewriting certain URLs to point to a single page or HTML view.
        // Although using the single_page_link site config instruction is the preferred way to do this, sometimes, as
        // with Google Docs URLs, it's not possible.
        'rewrite_url' => [
            'docs.google.com' => ['/Doc?' => '/View?'],
            'tnr.com' => ['tnr.com/article/' => 'tnr.com/print/article/'],
            '.m.wikipedia.org' => ['.m.wikipedia.org' => '.wikipedia.org'],
            'm.vanityfair.com' => ['m.vanityfair.com' => 'www.vanityfair.com'],
        ],
        // Prevent certain file/mime types
        // HTTP responses which match these content types will
        // be returned without body.
        'header_only_types' => [
           'image',
           'audio',
           'video',
        ],
        // URLs ending with one of these extensions will
        // prompt Humble HTTP Agent to send a HEAD request first
        // to see if returned content type matches $headerOnlyTypes.
        'header_only_clues' => ['mp3', 'zip', 'exe', 'gif', 'gzip', 'gz', 'jpeg', 'jpg', 'mpg', 'mpeg', 'png', 'ppt', 'mov'],
        // User Agent strings - mapping domain names
        'user_agents' => [],
        // AJAX triggers to search for.
        // for AJAX sites, e.g. Blogger with its dynamic views templates.
        'ajax_triggers' => [
            " 'fingerprint.blogspot.com',
            '/\(.*?)!is' => '')
            'pre_filters' => [],
            'post_filters' => [],
        ],
        'src_lazy_load_attributes' => [
            'data-src',
            'data-lazy-src',
            'data-original',
            'data-sources',
            'data-hi-res-src',
        ],
        // these JSON-LD types will be ignored
        'json_ld_ignore_types' => ['Organization', 'WebSite', 'Person', 'VideoGame'],
    ],
]);
```

Credits
-------

[](#credits)

- [FiveFilters](https://github.com/fivefilters) for [Full-Text-RSS](https://fivefilters.org/content-only/)
- [Caneco](https://twitter.com/caneco) for the awesome logo ✨

###  Health Score

44

—

FairBetter than 92% of packages

Maintenance40

Moderate activity, may be stable

Popularity13

Limited adoption so far

Community16

Small or concentrated contributor base

Maturity92

Battle-tested with a long release history

 Bus Factor1

Top contributor holds 76.2% 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 ~34 days

Recently: every ~11 days

Total

99

Last Release

502d ago

Major Versions

1.19.0 → 2.0.0-alpha.02019-01-26

1.20.1 → 2.0.02019-05-22

2.4.5 → 3.02024-11-15

PHP version history (5 changes)1.0.0-alpha.0PHP &gt;=5.4

2.0.0-alpha.0PHP &gt;=5.5

2.0.0PHP &gt;=7.1

2.3.0PHP &gt;=7.1.3

3.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/21b39551f92ed4143772c622f9e571589c5a72c96ab3c53fe67489ce0d83e806?d=identicon)[tacman1123](/maintainers/tacman1123)

---

Top Contributors

[![j0k3r](https://avatars.githubusercontent.com/u/62333?v=4)](https://github.com/j0k3r "j0k3r (574 commits)")[![jtojnar](https://avatars.githubusercontent.com/u/705123?v=4)](https://github.com/jtojnar "jtojnar (56 commits)")[![Kdecherf](https://avatars.githubusercontent.com/u/226063?v=4)](https://github.com/Kdecherf "Kdecherf (41 commits)")[![aaa2000](https://avatars.githubusercontent.com/u/163941?v=4)](https://github.com/aaa2000 "aaa2000 (38 commits)")[![tacman](https://avatars.githubusercontent.com/u/619585?v=4)](https://github.com/tacman "tacman (14 commits)")[![Simounet](https://avatars.githubusercontent.com/u/582666?v=4)](https://github.com/Simounet "Simounet (10 commits)")[![techexo](https://avatars.githubusercontent.com/u/1850197?v=4)](https://github.com/techexo "techexo (7 commits)")[![caneco](https://avatars.githubusercontent.com/u/502041?v=4)](https://github.com/caneco "caneco (3 commits)")[![nicosomb](https://avatars.githubusercontent.com/u/121870?v=4)](https://github.com/nicosomb "nicosomb (2 commits)")[![phiamo](https://avatars.githubusercontent.com/u/207291?v=4)](https://github.com/phiamo "phiamo (1 commits)")[![shtrom](https://avatars.githubusercontent.com/u/160280?v=4)](https://github.com/shtrom "shtrom (1 commits)")[![HolgerAusB](https://avatars.githubusercontent.com/u/3876469?v=4)](https://github.com/HolgerAusB "HolgerAusB (1 commits)")[![gitter-badger](https://avatars.githubusercontent.com/u/8518239?v=4)](https://github.com/gitter-badger "gitter-badger (1 commits)")[![tcitworld](https://avatars.githubusercontent.com/u/2197836?v=4)](https://github.com/tcitworld "tcitworld (1 commits)")[![girishpanchal30](https://avatars.githubusercontent.com/u/79647963?v=4)](https://github.com/girishpanchal30 "girishpanchal30 (1 commits)")[![Vendin](https://avatars.githubusercontent.com/u/9011975?v=4)](https://github.com/Vendin "Vendin (1 commits)")[![zyuhel](https://avatars.githubusercontent.com/u/4603624?v=4)](https://github.com/zyuhel "zyuhel (1 commits)")

###  Code Quality

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/tacman-graby/health.svg)

```
[![Health](https://phpackages.com/badges/tacman-graby/health.svg)](https://phpackages.com/packages/tacman-graby)
```

###  Alternatives

[j0k3r/graby

Graby helps you extract article content from web pages

384349.6k2](/packages/j0k3r-graby)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[phpro/http-tools

HTTP tools for developing more consistent HTTP implementations.

28137.8k](/packages/phpro-http-tools)[wallabag/wallabag

open source self hostable read-it-later web application

12.6k2.2k](/packages/wallabag-wallabag)[google/cloud-core

Google Cloud PHP shared dependency, providing functionality useful to all components.

343121.4M79](/packages/google-cloud-core)[friendsofsymfony/http-cache

Tools to manage HTTP caching proxies with PHP

36114.7M36](/packages/friendsofsymfony-http-cache)

PHPackages © 2026

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