zymawy/laravel-intelligent-scraper

提供一种简单抓取网页服务,无需了解其HTML结构。

dev-master 2023-03-10 18:11 UTC

This package is auto-updated.

Last update: 2024-09-10 22:09:33 UTC


README

Latest Version Software License Build Status Total Downloads Average time to resolve an issue Percentage of issues still open

此包提供了一种抓取解决方案,无需了解网页的HTML结构,并在检测到HTML结构发生变化时自动配置。这允许您在长时间内无需手动干预继续抓取。

安装

要安装,请使用composer

composer require joskfg/laravel-intelligent-scraper

要发布抓取器配置,您可以使用

php artisan vendor:publish --provider="Joskfg\LaravelIntelligentScraper\ScraperProvider" --tag=config

数据库迁移已注册在服务提供者中,因此您可以使用migrate命令创建所需的表。

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配置方法。

基于数据集的配置

第一步是要知道您想要从一个页面中获取哪些数据,因此您必须访问该页面并选择所有想要抓取的文本、图像、元数据等,并对其进行标记,以便您可以告诉抓取器您想要什么。

以下是从Microsoft Store的示例

<?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',
            ],
        ],
    ],
]);

在这个例子中,我们可以看到我们想要不同的字段,我们随意对其进行标记。其中一些有多个值,因此我们可以从页面中抓取物品列表。

使用此单个数据集,我们将能够训练我们的抓取器,并能够抓取任何具有相同结构的页面。由于页面通常根据不同的变量具有不同的结构,因此您应该添加不同的数据集,以尝试涵盖尽可能多的页面变化。抓取器将无法抓取未包含在数据集中的页面变化。

一旦我们完成了工作,所有准备工作都已就绪。只要数据集中有足够的数据来覆盖页面的所有新修改,您就不必担心更新,因为抓取器会动态地重新计算修改。您可以通过查看如何工作来了解更多的内部信息。

在下一节中,我们将更深入地探讨如何创建新的数据集以及可用的选项。

数据集创建

数据集由urldata组成。

  • url部分很简单,您只需指定从何处获取数据。
  • type部分为当前数据集提供项目名称。这允许您定义多个类型。
  • variant标识页面变体。标识符是基于用于获取数据的xpath构建的sha1哈希。
  • 数据部分是指明所需数据和分配的标签的地方。数据可以是一系列项目或单个项目。

一个基本示例可以是

<?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',
            ],
        ],
    ],
]);

在这个数据集中,我们希望文本我的标题被标记为标题,同时我们还有一张我们希望被标记为图像的图片列表。这样,我们可以灵活地逐个选择项目或以列表形式选择。

有时我们希望标记一些不干净的文本,因为它们可能包含不可见的字符,如\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的可选参数,允许您为爬取请求设置上下文,这样您就可以在监听器中访问该上下文。如果您需要在监听器中处理除爬取数据之外的其他数据,这将非常有用。

<?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) 辅助函数来完成。

Scrape process

# 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;

更新数据集

为了可重新配置并保持数据集的新鲜性,爬虫会自动存储最新爬取的数据。

Updatge dataset process

# Powered by https://code2flow.com/app
Receive Scraped event;
Remove oldest scraped data;
Store scraped data;
Scrape dataset updated;

配置爬虫

如果触发无效配置事件,系统会尝试计算新的配置以从 ScrapeRequest 获取信息。

Configuration process

# 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