{"id":36994521,"url":"https://github.com/balintpethe/laravel-universal-scraper","last_synced_at":"2026-01-13T23:46:45.002Z","repository":{"id":325306329,"uuid":"1100646962","full_name":"balintpethe/laravel-universal-scraper","owner":"balintpethe","description":"Universal Scraper for Laravel","archived":false,"fork":false,"pushed_at":"2025-11-20T16:29:17.000Z","size":38,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-20T17:25:20.992Z","etag":null,"topics":["crawler","laravel","scraper","web-scraper"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/balintpethe.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-20T15:05:09.000Z","updated_at":"2025-11-20T16:53:37.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/balintpethe/laravel-universal-scraper","commit_stats":null,"previous_names":["balintpethe/laravel-universal-scraper"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/balintpethe/laravel-universal-scraper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balintpethe%2Flaravel-universal-scraper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balintpethe%2Flaravel-universal-scraper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balintpethe%2Flaravel-universal-scraper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balintpethe%2Flaravel-universal-scraper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/balintpethe","download_url":"https://codeload.github.com/balintpethe/laravel-universal-scraper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balintpethe%2Flaravel-universal-scraper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28405304,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T21:51:37.118Z","status":"ssl_error","status_checked_at":"2026-01-13T21:45:14.585Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crawler","laravel","scraper","web-scraper"],"created_at":"2026-01-13T23:46:44.936Z","updated_at":"2026-01-13T23:46:44.992Z","avatar_url":"https://github.com/balintpethe.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel Universal Scraper\n\n[![Tests Status Badge](https://github.com/balintpethe/laravel-universal-scraper/actions/workflows/tests.yml/badge.svg)](https://github.com/balintpethe/laravel-universal-scraper/actions/workflows/tests.yml)\n[![PHPStan Status Badge](https://github.com/balintpethe/laravel-universal-scraper/actions/workflows/phpstan.yml/badge.svg)](https://github.com/balintpethe/laravel-universal-scraper/actions/workflows/phpstan.yml)\n\nEgy könnyen bővíthető, konfigurálható web scraping toolkit Laravel projektekhez. Célja, hogy deklaratív módon — profile-okon és CSS selectorokon keresztül — tudd leírni, hogyan kell egy webshop vagy tetszőleges oldal HTML-jéből strukturált adatot kinyerni.\n\n## Fő funkciók\n\n- Laravel-ready Composer package (auto-discovery)\n- HTTP hívások Laravel `Http` kliensen keresztül (timeout, retry, default header-ek)\n- `ConfigProfile`: scraper profile deklarálása config-ból\n- List scraping deklaratív `fields` definícióval\n- Alap típuskonverzió (ár → int/float, stb.)\n- Kiterjeszthető `ScraperProfile` interfész egyedi logikához\n- Facade: `UniversalScraper`\n\n---\n\n## Követelmények\n\n- PHP: 8.1+\n- Laravel: 10.x vagy 11.x\n- Composer\n\n---\n\n## Telepítés\n\n1. Telepítés composer-rel:\n\n```bash\ncomposer require balintpethe/laravel-universal-scraper\n```\n\n2. (Opció) Ha szükséges, publikáld a config fájlt a host alkalmazatba:\n\n```bash\nphp artisan vendor:publish --provider=\"BalintPethe\\\\LaravelUniversalScraper\\\\LaravelUniversalScraperServiceProvider\" --tag=config\n```\n\nA publikálás után a `config/universal-scraper.php` fájlban tudod a profilingokat és globális beállításokat szerkeszteni.\n\n---\n\n## Gyors használat\n\nA csomag egy `UniversalScraper` facade-ot biztosít a legegyszerűbb használathoz. A leggyakoribb forgatókönyv: van egy profil (például `products`), amely leírja, hogyan találjuk meg a terméklistát és a mezőket.\n\nPélda Controller-ből:\n\n```php\nuse App\\Http\\Controllers\\Controller;\nuse UniversalScraper; // facade\n\nclass ScrapeController extends Controller\n{\n    public function show()\n    {\n        $url = 'https://example.com/category?page=1';\n\n        // A profil neve, vagy egy ConfigProfile objektum\n        $result = UniversalScraper::scrape('example_product_profile', $url);\n\n        // $result tömb formában tér vissza\n        return response()-\u003ejson($result);\n    }\n}\n```\n\nAlternatív mód: a szolgáltatás használata service container-en keresztül:\n\n```php\n$scraper = app()-\u003emake(\\BalintPethe\\UniversalScraper\\ScraperManager::class);\n$result = $scraper-\u003escrape('example_product_profile', $url);\n```\n\n---\n\n## Konfigurációs profil (példa)\n\nA `config/universal-scraper.php` tipikusan tartalmaz egy `profiles` tömböt. Egy egyszerű példa profil (\nfigurális, tetszőleges mezőkkel):\n\n```php\nreturn [\n    'default' =\u003e [\n        'timeout' =\u003e 10,\n        'retry' =\u003e 1,\n    ],\n\n    'profiles' =\u003e [\n        'example_product_profile' =\u003e [\n            'type' =\u003e 'list',\n            'list_selector' =\u003e '.product-list .product-item',\n            'fields' =\u003e [\n                'title' =\u003e ['selector' =\u003e '.title', 'type' =\u003e 'string'],\n                'price' =\u003e ['selector' =\u003e '.price', 'type' =\u003e 'money'],\n                'url' =\u003e ['selector' =\u003e '.title a', 'attr' =\u003e 'href', 'type' =\u003e 'string'],\n                'in_stock' =\u003e ['selector' =\u003e '.stock', 'type' =\u003e 'bool']\n            ],\n            // opcionális: egyedi post-processing class vagy closure\n            //'post_processor' =\u003e MyCustomProcessor::class,\n        ],\n    ],\n];\n```\n\nA fenti profil azt írja le, hogy a scraping egy listát ad vissza, minden elemre alkalmazzuk a `fields`-et.\n\n---\n\n## Részletes profil példák\n\nAz alábbi példák bemutatják a gyakori profil-típusokat: terméklista, termék oldal (item) és paginációs profil.\n\n1) Terméklista (list) - egyszerű webshop lista\n\n```php\n'profiles' =\u003e [\n    'products_list' =\u003e [\n        'type' =\u003e 'list',\n        'list_selector' =\u003e '.products .product',\n        'fields' =\u003e [\n            'id' =\u003e ['selector' =\u003e '.product-id', 'type' =\u003e 'string'],\n            'title' =\u003e ['selector' =\u003e '.title', 'type' =\u003e 'string'],\n            'price' =\u003e ['selector' =\u003e '.price', 'type' =\u003e 'money'],\n            'image' =\u003e ['selector' =\u003e '.thumb img', 'attr' =\u003e 'src', 'type' =\u003e 'string'],\n            'url' =\u003e ['selector' =\u003e '.title a', 'attr' =\u003e 'href', 'type' =\u003e 'string'],\n        ],\n    ],\n],\n```\n\n2) Termék oldal (item) - részletes adat egy termékoldalról\n\n```php\n'profiles' =\u003e [\n    'product_item' =\u003e [\n        'type' =\u003e 'item',\n        'fields' =\u003e [\n            'title' =\u003e ['selector' =\u003e 'h1.product-title', 'type' =\u003e 'string'],\n            'price' =\u003e ['selector' =\u003e '.product-price .amount', 'type' =\u003e 'money'],\n            'description' =\u003e ['selector' =\u003e '.description', 'type' =\u003e 'string'],\n            'images' =\u003e ['selector' =\u003e '.gallery img', 'attr' =\u003e 'src', 'multiple' =\u003e true, 'type' =\u003e 'string'],\n            'availability' =\u003e ['selector' =\u003e '.stock-status', 'type' =\u003e 'string'],\n        ],\n    ],\n],\n```\n\nMegjegyzés: a `multiple: true` jelzi, hogy egy selector több találatot adhat vissza, és a mező értéke tömb lesz.\n\n3) Paginalás - következő oldal kinyerése és összefűzés\n\n```php\n'profiles' =\u003e [\n    'products_paginated' =\u003e [\n        'type' =\u003e 'list',\n        'list_selector' =\u003e '.products .product',\n        'fields' =\u003e [\n            'title' =\u003e ['selector' =\u003e '.title', 'type' =\u003e 'string'],\n            'price' =\u003e ['selector' =\u003e '.price', 'type' =\u003e 'money'],\n            'url' =\u003e ['selector' =\u003e '.title a', 'attr' =\u003e 'href', 'type' =\u003e 'string'],\n        ],\n        // A csomag alapból nem futtat automatikus paginációt, de megadhatsz\n        // next_page_selector-t és egy post_processor-t, amely iterál az oldalak felett.\n        'next_page_selector' =\u003e '.pagination .next a',\n        'post_processor' =\u003e App\\Scrapers\\Processors\\PaginatedCollector::class,\n    ],\n],\n```\n\n---\n\n## Példa: `post_processor` osztály és regisztráció\n\nA `post_processor` egy tetszőleges osztály vagy closure lehet, amely a scraping eredményein fut le, és módosítja vagy összegzi azokat. A csomag a profilban megadott `post_processor` értékét meghívja (ha osztály, akkor példányosítja és hív egy `process(array $items, array $meta = []): array` metódust), vagy ha closure, akkor meghívja a closure-t.\n\nPélda egyszerű osztályra, amely abszolutizálja a relatív URL-eket és normalizálja az árakat:\n\n```php\nnamespace App\\Scrapers\\Processors;\n\nclass ProductPostProcessor\n{\n    protected string $baseUrl;\n\n    public function __construct(string $baseUrl = '')\n    {\n        $this-\u003ebaseUrl = rtrim($baseUrl, '/');\n    }\n\n    /**\n     * @param array $items\n     * @param array $meta opcionális meta adatok (pl. current_url)\n     * @return array\n     */\n    public function process(array $items, array $meta = []): array\n    {\n        $currentUrl = $meta['current_url'] ?? 'http://example.com';\n\n        foreach ($items as \u0026$item) {\n            // absolutize url mező, ha relatív\n            if (!empty($item['url']) \u0026\u0026 str_starts_with($item['url'], '/')) {\n                $item['url'] = $this-\u003ebaseUrl . $item['url'];\n            }\n\n            // price például centes formára konvertálás (ha string)\n            if (isset($item['price']) \u0026\u0026 is_string($item['price'])) {\n                // egyszerű példa: 49.99 -\u003e 4999\n                $normalized = preg_replace('/[^0-9.,]/', '', $item['price']);\n                $normalized = str_replace(',', '.', $normalized);\n                $item['price'] = (int) round((float) $normalized * 100);\n            }\n        }\n\n        return $items;\n    }\n}\n```\n\nRegisztráció a profilban (config):\n\n```php\n'products_list' =\u003e [\n    // ... egyéb beállítások ...\n    'post_processor' =\u003e App\\Scrapers\\Processors\\ProductPostProcessor::class,\n],\n```\n\nClosure példa közvetlen használatra (ha a csomag támogatja):\n\n```php\n'post_processor' =\u003e function(array $items, array $meta = []) {\n    // módosítsd $items-et és térj vissza vele\n    return array_map(function($it) use ($meta) {\n        // például hozzáadjuk az aktuális URL-t\n        $it['scraped_from'] = $meta['current_url'] ?? null;\n        return $it;\n    }, $items);\n},\n```\n\n---\n\n## Kimenet / Mit várhatsz vissza\n\nA `scrape()` metódus általában egy asszociatív tömböt (array) ad vissza. Két tipikus struktúra:\n\n- list típusú profil: tömb, amelyben minden elem egy asszociatív tömb (rekord), pl. tömb termékekről\n- item/objektum típus: egyetlen asszociatív tömb a lekért objektumról\n\nPélda list kimenetre (PHP tömb / JSON):\n\n```json\n[\n  {\n    \"title\": \"Awesome Sneakers\",\n    \"price\": 4999,\n    \"url\": \"https://example.com/products/1\",\n    \"in_stock\": true\n  },\n  {\n    \"title\": \"Running Shoes\",\n    \"price\": 6999,\n    \"url\": \"https://example.com/products/2\",\n    \"in_stock\": false\n  }\n]\n```\n\nMegjegyzések a kimenetről:\n- A mezők típuskonverziója a profilban megadott `type` alapján történik (pl. `money` → int/float, `bool` → boolean).\n- Alapértelmezés szerint a csomag tömböt ad vissza; ha JSON-t szeretnél, egyszerűen json_encode-olhatod vagy response()-\u003ejson()-t használhatsz.\n- Ha a profil `post_processor`-t ad meg, az módosíthatja a végső kimenetet.\n\n---\n\n## Implementáció (magas szint)\n\nA csomag komponensei röviden:\n\n- ScraperManager\n  - A csomag belépési pontja, ez koordinálja a profil betöltését, a letöltést és a HTML feldolgozást.\n- Contracts/ScraperProfile\n  - Interfész, amelyet egyedi profilok implementálhatnak. Meghatározza a szükséges metódusokat (pl. `getType()`, `getFields()`, `parse()` stb.).\n- Profiles/ConfigProfile\n  - Alap implementáció, amely config fájlból épít profilt (a tipikus esetet lefedi).\n- Http/Downloader\n  - Egységesen kezeli a kéréseket Laravel `Http` kliensen keresztül (timeout, retry beállítások). Hibakezelést delegál tovább.\n- Parsing/HtmlExtractor\n  - Fej vagy body HTML-ből CSS szelektorokkal adatot kinyerő komponens.\n- Facades/UniversalScraper\n  - Egy egyszerű facade, amely kényelmesen hívható a legtöbb helyről.\n- Exceptions/ProfileNotFoundException\n  - Hibakezelés akkor, ha a kért profil nem található.\n\nHa szeretnél egyedi viselkedést (pl. saját parsing vagy API-kliens), implementálj egy osztályt a `ScraperProfile` interfész alapján, és regisztráld a szolgáltatáson keresztül.\n\n---\n\n## Hibakezelés és edge-case-ek\n\nGyakori hibák és hogyan kezeld őket:\n\n- ProfileNotFoundException: a megadott profil név nem létezik a configban. Ellenőrizd a `config/universal-scraper.php` profil nevét.\n- Hálózati hibák (timeout, DNS, 5xx): a `Downloader` továbbadja a Laravel `Http` hibákat. Állíts be timeout-ot és retry-t a configban.\n- Üres vagy változó HTML struktúra: használj robusztusabb szelektorokat vagy post-processort a hiányzó mezők kezelése érdekében.\n- Relatív URL-ek: alapértelmezésben az `href`-eket nem mindig abszolutizálja; ha szükséges, add hozzá az alap URL-t a post-processing fázisban.\n\nEdge-case lista (amire figyelni kell):\n- Többszörös találatok egy selectorra (első vs. összes eredmény)\n- Dinamikus JS által generált tartalom (ez nem fog működni a szerver-side HTML parserekkel)\n- Oldalak, amelyek blokkolják a scraping-et (rate-limit, bot-detection)\n\n---\n\n## Kiterjesztés / Custom profil példa\n\nRövid példa, hogyan írj saját `ScraperProfile`-t:\n\n```php\nnamespace App\\Scrapers;\n\nuse BalintPethe\\UniversalScraper\\Contracts\\ScraperProfile;\n\nclass MyCustomProfile implements ScraperProfile\n{\n    // ...implementáld a szükséges metódusokat, pl. getType(), getFields(), parse()\n}\n```\n\nRegisztrálhatod a service containerben vagy konfigurációban hivatkozhatsz rá, ha a csomag ezt a hookot támogatja.\n\n---\n\n## Tesztelés és helyi fejlesztés\n\n- Használj fixture HTML fájlokat unit tesztekhez (minta HTML bemenet → várt tömb kimenet).\n- Ajánlott: PHPStan / Psalm és PHPUnit konfiguráció létrehozása a csomaghoz.\n\n---\n\n## Gyakori használati forgatókönyvek\n\n- Listázás (terméklista): `type: list`, `list_selector` + `fields`\n- Single item (termék oldal): `type: item`, egy objektum kinyerése\n- Paginalás: a profilban definiálhatod a következő oldal URL-jének logikáját, vagy a post-processor kezelheti\n\n---\n\n## Authentikált (loginos) scraping – AuthenticatedDownloader\n\nBizonyos esetekben szükség lehet arra, hogy a scraper **csak login mögötti oldalakhoz** férjen hozzá  \n(pl. saját admin felület, belső ERP, partner rendszer, ahova van jogosultságod).\n\nEhhez a profilhoz egy `auth` blokkot adhatsz, ekkor a csomag automatikusan\n`AuthenticatedDownloader`-t fog használni az adott profilhoz.\n\n```php\n'profiles' =\u003e [\n\n    'internal_admin_orders' =\u003e [\n        'base_url' =\u003e 'https://admin.my-internal-app.local',\n\n        'class' =\u003e \\Balintpethe\\LaravelUniversalScraper\\Profiles\\ConfigProfile::class,\n\n        'auth' =\u003e [\n            'type'      =\u003e 'form',   // jelenleg: 'form' támogatott\n            'login_url' =\u003e '/login',\n            'method'    =\u003e 'POST',\n\n            // A credential értékeket NEM írjuk a configba, csak env-ből olvassuk:\n            'credentials' =\u003e [\n                'email_env'    =\u003e 'SCRAPER_LOGIN_EMAIL',\n                'password_env' =\u003e 'SCRAPER_LOGIN_PASSWORD',\n            ],\n\n            // A form mezők nevei\n            'fields' =\u003e [\n                'email'    =\u003e 'email',\n                'password' =\u003e 'password',\n            ],\n        ],\n\n        'list' =\u003e [\n            'path'  =\u003e '/orders',\n            'item'  =\u003e '.order-row',\n            'fields' =\u003e [\n                'id' =\u003e [\n                    'selector' =\u003e '.order-id',\n                    'attr'     =\u003e 'text',\n                    'cast'     =\u003e 'int',\n                ],\n                'total' =\u003e [\n                    'selector' =\u003e '.order-total',\n                    'attr'     =\u003e 'text',\n                    'cast'     =\u003e 'float',\n                ],\n            ],\n        ],\n    ],\n\n];\n```\n### Env változók:\n\n```env\nSCRAPER_LOGIN_EMAIL=scraper@example.com\nSCRAPER_LOGIN_PASSWORD=super-secret-password\n```\n### A folyamat:\n\n- A profil tartalmaz auth blokkot → a csomag AuthenticatedDownloader-t hoz létre.\n\n- Az első get() hívás előtt a downloader:\n\n  - POST /login kérésben elküldi az email/jelszó párost.\n\n  - elmenti a szerver által adott session cookie-kat.\n\nA következő kérések ugyanezzel a sessionnel, authentikáltan futnak tovább.\n\n⚠️ Ha a profilban nincs auth blokk, akkor továbbra is a sima, “anonim” Downloader\nkerül használatra, tehát az auth egy teljesen opcionális extra.\n\n---\n\n## Contributing\n\nHa szeretnél hozzájárulni:\n\n- Nyiss issue-t, ha hibát találsz vagy feature-t javasolsz\n- Fork-olj és küldj PR-t; tartsd a kódot PSR-12 kompatibilisnek és adj hozzá teszteket\n\n---\n\n\n## Jogi / etikai megjegyzés a README-ben\n\n### Legal / etikai megjegyzés\n\nEz a csomag kizárólag olyan célokra készült, ahol a scraping **jogszerű és engedélyezett**.\n\n- Mindig tartsd be a célrendszer felhasználási feltételeit (ToS), a `robots.txt` előírásait\n  és az adatvédelmi / szerzői jogi szabályokat.\n- Authentikált scrapinget (login mögötti oldalakat) csak olyan fiókokkal és olyan rendszerekhez\n  használj, amelyekhez hivatalos, jogszerű hozzáférésed van.\n- A csomag **nem** a védelmek, paywallok, 2FA, CAPTCHA vagy egyéb biztonsági mechanizmusok\n  megkerülésére készült.\n\nA csomag készítője semmilyen felelősséget nem vállal a helytelen vagy jogsértő felhasználásért.\nA felelősség kizárólag a használót terheli.\n\n---\n\n## License \u0026 Copyright\n\n[MIT](https://github.com/balintpethe/laravel-universal-scraper/blob/master/LICENSE), (c) 2025-present Bálint Pethe and [contributors](https://github.com/balintpethe/laravel-universal-scraper/graphs/contributors)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbalintpethe%2Flaravel-universal-scraper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbalintpethe%2Flaravel-universal-scraper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbalintpethe%2Flaravel-universal-scraper/lists"}