joskfg / laravel-intelligent-scraper
一个无需了解HTML结构即可轻松抓取网页的服务。
Requires
- php: >= 8.0
- ext-dom: *
- ext-json: *
- fabpot/goutte: ^4.0
- illuminate/database: ^8.0 || ^9.0
- illuminate/events: ^8.0 || ^9.0
- psr/log: ^1.0 || ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.8
- laravel/legacy-factories: ^1.1
- mockery/mockery: ^1.0
- orchestra/database: ^6.0
- orchestra/testbench: ^6.18
- phpunit/phpunit: ^9.0
- rector/rector: ^0.12.18
- squizlabs/php_codesniffer: ^3
This package is auto-updated.
Last update: 2024-08-28 19:49:16 UTC
README
此包提供了一种不需要了解Web HTML结构的抓取解决方案,当检测到HTML结构发生变化时,它会自动配置。这允许你在长时间内无需手动干预继续抓取。
安装
要安装,请使用composer
composer require joskfg/laravel-intelligent-scraper
要发布抓取器配置,您可以
php artisan vendor:publish --provider="Joskfg\LaravelIntelligentScraper\ScraperProvider" --tag=config
数据库迁移已注册在服务提供器中,因此您可以执行迁移命令以创建所需的表。
php artisan migrate
依赖
此包依赖于goutte,它依赖于guzzle,因此您可以自定义客户端以满足您的需求。此包的唯一要求是您必须在处理堆栈中包含 http_error
中间件。
示例
<?php use GuzzleHttp\Handler\CurlHandler; use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use Goutte\Client as GoutteClient; use App\MyMiddleware; $client = new GoutteClient(); $stack = new HandlerStack(); $stack->setHandler(new CurlHandler()); $stack->push(MyMiddleware::getHandler(), 'my_middleware'); // Your custom middleware $stack->push(Middleware::httpErrors(), 'http_errors'); // Required middleware for the package $guzzleClient = new GuzzleClient(['handler' => $stack]); $client->setClient($guzzleClient);
默认堆栈已经包含了 http_errors 中间件,所以您只有在未使用默认堆栈的情况下才需要这样做。
配置
初始设置有两种不同的选项。该包可以使用数据集进行配置或使用Xpath进行配置。两种方式都会产生相同的结果,但根据您的Xpath知识,您可能会倾向于其中一种。我们建议使用使用Xpath进行配置的方法。
基于数据集的配置
第一步是了解您想从页面中获取哪些数据,因此您必须转到该页面,选择所有想要抓取的文本、图像、元数据等,并将它们标记出来,这样您就可以告诉抓取器您想要什么。
以微软商店为例
<?php use Joskfg\LaravelIntelligentScraper\Scraper\Models\ScrapedDataset; ScrapedDataset::create([ 'url' => 'https://test.c/p/my-objective', 'type' => 'Item-definition-1', 'data' => [ [ 'key' => 'title', 'value' => 'My title', ], [ 'key' => 'body', 'value' => 'This is the body content I want to get', ], [ 'key' => 'images', 'value' => [ 'https://test.c/images/1.jpg', 'https://test.c/images/2.jpg', 'https://test.c/images/3.jpg', ], ], ], ]);
在这个例子中,我们可以看到我们想要不同的字段,我们随意地标记了它们。其中一些有多个值,因此我们可以从页面中抓取项目的列表。
使用这个单一的数据集,我们将能够训练我们的抓取器,并能够抓取具有相同结构的任何页面。由于页面通常根据不同的变量具有不同的结构,您应该添加不同的数据集,以尝试覆盖尽可能多的页面变化。抓取器将无法抓取未包含在数据集中的页面变化。
一旦我们完成工作,一切就绪。只要数据集中有足够的数据来覆盖页面的所有新修改,您就无需关心更新,抓取器将实时重新计算修改。您可以通过查看如何工作来了解更多关于其内部的信息。
在下一节中,我们将更深入地探讨如何创建新的数据集以及有哪些选项可用。
数据集创建
数据集由 url
和 data
组成。
url
部分很简单,您只需要指示您从中获取数据的服务器地址。- 《type》部分为当前数据集提供项目名称。这允许您定义多个类型。
- 《variant》标识页面变体。标识符是基于获取数据所使用的xpath构建的sha1哈希。
- 《data》部分是您指示要获取哪些数据并分配所需标签的地方。数据可以是项目列表或单个项目。
一个基本示例可以是
<?php use Joskfg\LaravelIntelligentScraper\Scraper\Models\ScrapedDataset; ScrapedDataset::create([ 'url' => 'https://test.c/p/my-objective', 'type' => 'Item-definition-1', 'variant' => '8ed10778a83f1266e7ffed90205f7fb61ddcdf78', 'data' => [ [ 'key' => 'title', 'value' => 'My title', ], [ 'key' => 'body', 'value' => 'This is the body content I want to get', ], [ 'key' => 'images', 'value' => [ 'https://test.c/images/1.jpg', 'https://test.c/images/2.jpg', 'https://test.c/images/3.jpg', ], ], ], ]);
在这个数据集中,我们希望将文本 我的标题
标记为标题,我们还有一个我们想要标记为图像的图像列表。通过这种方式,我们可以灵活地逐个选择项目或以列表形式选择。
有时我们想要标记一些不干净的HTML文本,因为它可能包含不可见的字符,如 \r\n
。为了避免处理这些,数据集允许您添加正则表达式。
以 body
字段为正则表达式的示例
<?php use Joskfg\LaravelIntelligentScraper\Scraper\Models\ScrapedDataset; ScrapedDataset::create([ 'url' => 'https://test.c/p/my-objective', 'type' => 'Item-definition-1', 'variant' => '8ed10778a83f1266e7ffed90205f7fb61ddcdf78', 'data' => [ [ 'key' => 'title', 'value' => 'My title', ], [ 'key' => 'body', 'value' => regexp('/^Body starts here, but it is so long that.*$/si'), ], [ 'key' => 'images', 'value' => [ 'https://test.c/images/1.jpg', 'https://test.c/images/2.jpg', 'https://test.c/images/3.jpg', ], ], ], ]);
通过这个更改,我们将确保即使有隐藏字符也能检测到 body
。
重要爬虫尝试在所有标签中寻找文本,包括子标签,所以如果您定义了一个没有限制的正则表达式,例如 /.*Body starts.*/
,您将在 <html>
元素中找到文本,因为该文本位于 <html>
的某个子元素中。所以请小心定义正则表达式。
基于Xpath的配置
在从HTML收集所有Xpath之后,您只需创建配置模型。它们看起来像
<?php use Joskfg\LaravelIntelligentScraper\Scraper\Models\Configuration; Configuration::create([ 'name' => 'title', 'type' => 'Item-definition-1', 'xpaths' => '//*[@id=title]', 'optional' => false, 'default' => [], ]); Configuration::create([ 'name' => 'category', 'type' => 'Item-definition-1', 'xpaths' => ['//*[@id=cat]', '//*[@id=long-cat]'], 'optional' => true, 'default' => [], ]);
在定义中,您应该给出要爬取的字段的名称并标识其类型。xpaths字段可以包含一个字符串,或一个字符串数组。这是因为HTML可能因特定页面而异,您可以编写一个Xpath列表,按顺序检查,以找到第一个结果。
配置允许您将字段设置为可选,并在xpath未找到时设置默认值。这些字段不会触发重新配置过程。
用法
在配置爬虫后,您将能够使用 scrape
助手请求特定的爬取。
<?php scrape('https://test.c/p/my-objective', 'Item-definition-1');
有一个可选参数名为 context
,允许您为scrapeRequest设置上下文,这样您就可以在监听器中访问该上下文。这对于您需要一些额外的数据(超出爬取数据)在监听器中工作非常有用。
<?php scrape('https://test.c/p/my-objective', 'Item-definition-1', ['id' => 'my-objective']);
如果一切按预期进行,爬取将产生一个 Scraped
事件。因此,将监听器附加到该事件以接收数据。
/** @var \Joskfg\LaravelIntelligentScraper\Scraper\Events\Scraped $event */ $event->scrapeRequest->url; // Url scraped $event->scrapeRequest->type; // Request type $event->scrapeRequest->context; // Context $event->scrapedData; // Entity that contains all data scraped and the determined page variant.
所有输出字段都是数组,可以包含一个或多个结果。
如果爬取失败,将触发一个 ScrapeFailed
事件,其中包含爬取请求信息。
/** @var \Joskfg\LaravelIntelligentScraper\Scraper\Events\ScrapeFailed $event */ $event->scrapeRequest->url; // Url scraped $event->scrapeRequest->type; // Request type $event->scrapeRequest->context; // Context
要附加监听器,您可以使用Laravel监听器配置,如
// providers/EventServiceProvider protected $listen = [ Scraped::class => [ MyListener::class, ], ScrapeFailed::class => [ MyListenerFirFailedScrapes::class, ], ];
但是,所有类型的爬取都将发送到该监听器。为了简化监听器,并仅监听单个类型的爬取,在scraper.php中有一个可用的 listeners
配置,因此您可以根据更细粒度配置监听器。
return [ // config/scrapper.php 'listeners' => [ 'scraped' => [ 'my-type-1' => ListenerForTypeOne::class, 'my-type-2' => ListenerForTypeTwo::class, ], 'scrape-failed' => [ 'my-type-1' => ListenerFailedForTypeOne::class, ], ] ];
高级用法
还有一个名为 ConfigurationScraped
的事件,在重新配置步骤中自动完成爬取时触发。它与 Scraped
事件完全相同。它之所以命名不同,是因为通常它不感兴趣用于除内部更新数据集之外的用途。
可以使用 ConfigurationScraped
进行更新或了解配置过程的内部信息。
队列工作者
您需要一个工作者,一个用于默认队列,另一个用于 configure
队列。《configure》工作者应该是一个单独的工作者,以避免并行配置。
php artisan queue:work # As many as you want php artisan queue:work --queue=configure # Just one
测试
joskfg/laravel-intelligent-scraper
有一个 PHPUnit 测试套件和一个使用 PHP CS Fixer 的编码风格合规性测试套件。
要运行测试,请在项目文件夹中运行以下命令。
$ make tests
在开发环境中打开终端
$ make debug
它是如何工作的?
爬虫是自动可配置的,但需要一个初始数据集或添加配置。数据集告诉配置器你想要哪些数据以及如何标记它。
有三个服务具有独特的职责,并通过事件系统连接。
抓取
当系统接收到 \Joskfg\LaravelIntelligentScraper\Scraper\Events\ScrapeRequest
事件时,会触发此事件。可以使用我们的 scrape($url, $type)
辅助函数完成此操作。
# Powered by https://code2flow.com/app
Scrape Request 'https://test.c/p/my-onjective' using 'Item-definition-1';
try {
load configuration;
}
catch(Missing config) {
goto fail;
}
extract data using configuration;
// It could be produced by old configuration
if(Error extracting data) {
goto fail
}
fire Scraped Event;
return;
fail:
fire InvalidConfiguration Event;
更新数据集
为了可重新配置并保持数据集的新鲜度,爬虫会自动存储最新抓取的数据。
# Powered by https://code2flow.com/app
Receive Scraped event;
Remove oldest scraped data;
Store scraped data;
Scrape dataset updated;
配置爬虫
如果触发了 InvalidConfiguration 事件,系统会尝试计算一个新的配置以从 ScrapeRequest 获取信息。
# Powered by https://code2flow.com/app
Invalid Configuration for ScrapeRequest;
try {
calculate configuration;
}
catch(Cannot be reconfigured) {
goto fail;
}
extract data using configuration;
if(Error extracting data) {
goto fail;
}
Store new configuerion;
Scraped data;
return;
fail:
Fire ScrapeFailed Event;
No scraped data;
此过程可能产生两个不同的事件
- 抓取成功:所有操作都按预期进行,页面已成功抓取
- 抓取失败:重新计算配置后无法进行抓取,因此我们需要手动配置操作来修复它。
许可证
Apache 2.0 许可证。有关更多信息,请参阅 LICENSE。