ReactPHP 爬虫实战:下载整个网站的图片

news/2024/4/27 9:23:51/文章来源:https://blog.csdn.net/weixin_34153893/article/details/88666247

什么是网页抓取?

你是否曾经需要从一个没有提供 API 的站点获取信息? 我们可以通过网页抓取,然后从目标网站的 HTML 中获得我们想要的信息,进而解决这个问题。 当然,我们也可以手动提取这些信息, 但手动操作很乏味。 所以, 通过爬虫来自动化来完成这个过程会更有效率。

在这个教程中我们会从 Pexels 抓取一些猫的图片。这个网站提供高质量且免费的素材图片。他们提供了API, 但这些 API 有 200次/小时 的请求频率限制。

[](https://user-gold-cdn.xitu.io...

file

发起并发请求

在网页抓取中使用异步 PHP (相比使用同步方式)的最大好处是可以在更短的时间内完成更多的工作。使用异步 PHP 使得我们可以立刻请求尽可能多的网页而不是每次只能请求单个网页并等待结果返回。 因此,一旦请求结果返回我们就可以开始处理。

首先,我们从 GitHub 上拉取一个叫做 buzz-react  的异步 HTTP 客户端的代码 -- 它是一个基于 ReactPHP 的简单、致力于并发处理大量 HTTP 请求的异步 HTTP 客户端:

composer require clue/buzz-react

现在, 我们就可以请求 pexels 上的图片页面 了:

<?phprequire __DIR__ . '/vendor/autoload.php';use Clue\React\Buzz\Browser;$loop = \React\EventLoop\Factory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$loop->run();

我们创建了 Clue\React\Buzz\Browser 的实例, 把它作为 HTTP client 使用。上面的代码发起了一个异步的 GET 请求来获取网页内容(包含一张小猫们的图片)。 $client->get($url) 方法返回了一个包含 PSR-7 response 的 promise 对象。

客户端是异步工作的,这意味着我们可以很容易地请求几个页面,然后这些请求会被同步执行:

<?phprequire __DIR__ . '/vendor/autoload.php';use Clue\React\Buzz\Browser;$loop = \React\EventLoop\Factory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$client->get('https://www.pexels.com/photo/adorable-animal-baby-blur-177809/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$loop->run();

这里的代码含义如下:

  • 发起一个请求
  • 获取响应
  • 添加响应的处理程序
  • 当响应解析完毕就处理响应

所以,这个逻辑可以提取到一个类里,这样我们可以很容易地请求多个 URL 并添加相同的响应处理程序。让我们基于Browser创建一个包装器。

用下面的代码创建一个名为Scraper的类:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;final class Scraper
{private $client;public function __construct(Browser $client){$this->client = $client;}public function scrape(array $urls){foreach ($urls as $url) {$this->client->get($url)->then(function (ResponseInterface $response) {$this->processResponse((string) $response->getBody());});}}private function processResponse(string $html){// ...}
}

我们把Browser作为依赖项注入到构造函数并提供一个公共方法scrape(array $urls)。接着对每个指定的 URL 发起一个GET请求。当响应完成时,我们调用一个私有方法processResponse(string $html)。这个方法负责遍历 HTML 代码并下载图片。下一步是审查收到的 HTML 代码,然后从里面提取图片。

发起并发请求

在网页抓取中使用异步 PHP (相比使用同步方式)的最大好处是可以在更短的时间内完成更多的工作。使用异步 PHP 使得我们可以立刻请求尽可能多的网页而不是每次只能请求单个网页并等待结果返回。 因此,一旦请求结果返回我们就可以开始处理。

首先,我们从 GitHub 上拉取一个叫做 buzz-react  的异步 HTTP 客户端的代码 -- 它是一个基于 ReactPHP 的简单、致力于并发处理大量 HTTP 请求的异步 HTTP 客户端:

composer require clue/buzz-react

现在, 我们就可以请求 pexels 上的图片页面 了:

<?phprequire __DIR__ . '/vendor/autoload.php';use Clue\React\Buzz\Browser;$loop = \React\EventLoop\Factory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$loop->run();

我们创建了 Clue\React\Buzz\Browser 的实例, 把它作为 HTTP client 使用。上面的代码发起了一个异步的 GET 请求来获取网页内容(包含一张小猫们的图片)。 $client->get($url) 方法返回了一个包含 PSR-7 response 的 promise 对象。

客户端是异步工作的,这意味着我们可以很容易地请求几个页面,然后这些请求会被同步执行:

<?phprequire __DIR__ . '/vendor/autoload.php';use Clue\React\Buzz\Browser;$loop = \React\EventLoop\Factory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$client->get('https://www.pexels.com/photo/adorable-animal-baby-blur-177809/')->then(function(\Psr\Http\Message\ResponseInterface $response) {echo $response->getBody();});$loop->run();

这里的代码含义如下:

  • 发起一个请求
  • 获取响应
  • 添加响应的处理程序
  • 当响应解析完毕就处理响应

所以,这个逻辑可以提取到一个类里,这样我们可以很容易地请求多个 URL 并添加相同的响应处理程序。让我们基于Browser创建一个包装器。

用下面的代码创建一个名为Scraper的类:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;final class Scraper
{private $client;public function __construct(Browser $client){$this->client = $client;}public function scrape(array $urls){foreach ($urls as $url) {$this->client->get($url)->then(function (ResponseInterface $response) {$this->processResponse((string) $response->getBody());});}}private function processResponse(string $html){// ...}
}

我们把Browser作为依赖项注入到构造函数并提供一个公共方法scrape(array $urls)。接着对每个指定的 URL 发起一个GET请求。当响应完成时,我们调用一个私有方法processResponse(string $html)。这个方法负责遍历 HTML 代码并下载图片。下一步是审查收到的 HTML 代码,然后从里面提取图片。

爬取网站

此刻我们只是获取到了响应页面的 HTML 代码。现在需要提取图片 URL。为此,我们需要审查收到的 HTML 代码结构。前往 Pexels 的图片页,右击图片并选择审查元素,你会看到一些东西,就像这样:

[](https://user-gold-cdn.xitu.io...

file

我们可以看到img标签有个image-section__image类名。我们要使用这个信息从收到的 HTML 中提取这个标签。图片的 URL 存储在src属性里:

[](https://user-gold-cdn.xitu.io...

file

为提取 HTML 标签,我们需要使用  Symfony 的 DomCrawler 组件。拉取需要的包:

composer require symfony/dom-crawler
composer require symfony/css-selector

DomCrawler 的适配组件 CSS-selector  允许我们使用类 - jQuery 的选择器遍历 DOM。当安装好一切之后,打开我们的Scraper类,在processResponse(string $html) 方法里书写一些代码。首先,我们需要创建一个Symfony\Component\DomCrawler\Crawler 类的实例,它的构造函数接受一个用于遍历的 HTML 代码字符串:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\DomCrawler\Crawler;final class Scraper
{// ...private function processResponse(string $html){$crawler = new Crawler($html);}
}

通过类 - jQuery 选择器查找任意元素时,请使用filter()方法。然后,attr($attribute)方法允许提取已过滤元素的某个属性:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\DomCrawler\Crawler;final class Scraper
{// ...private function processResponse(string $html){$crawler = new Crawler($html);$imageUrl = $crawler->filter('.image-section__image')->attr('src');echo $imageUrl . PHP_EOL;}
}

让我们只打印提取出的图片 URL,检查下我们的 scraper 是否如期工作:

<?php
// index.phprequire __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/Scraper.php';use Clue\React\Buzz\Browser;$loop = \React\EventLoop\Factory::create();$scraper = new Scraper(new Browser($loop));
$scraper->scrape(['https://www.pexels.com/photo/adorable-animal-blur-cat-617278/'
]);$loop->run();

当运行这个脚本时,将会输出所需图片的完整 URL。然后我们要使用这个 URL 下载该图片。 我们再次创建一个Browser实例,然后发起一个GET请求:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\DomCrawler\Crawler;final class Scraper
{// ...private function processResponse(string $html){$crawler = new Crawler($html);imageUrl = $crawler->filter('.image-section__image')->attr('src');$this->client->get($imageUrl)->then(function(ResponseInterface $response) {// 存储图片到磁盘上});}
}

到达的响应携带了请求的图片内容。现在我们需要把它保存到磁盘上。但是请花费一点时间,不要使用file_put_contents()。所有的原生 PHP 函数都在文件系统下阻塞式运行。这意味着一旦你调用了file_put_contents(),我们的应用就会停止异步行为。然后流程控制会被阻塞直到文件保存完毕。ReactPHP 有个专门的包可以解决这个问题。

异步保存文件

要以非阻塞方式异步处理文件的话,我们需要一个叫做 reactphp/filesystem 的包。拉取下来:

composer require react/filesystem

要异步使用文件系统,请创建一个Filesystem对象并把它作为依赖项提供给Scraper。此外,我们还需要提供一个目录存放下载的图片:

<?php
// index.phprequire __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/Scraper.php';use Clue\React\Buzz\Browser;
use React\Filesystem\Filesystem;$loop = \React\EventLoop\Factory::create();$scraper = new ScraperForImages(new Browser($loop), Filesystem::create($loop), __DIR__ . '/images'
);$scraper->scrape(['https://www.pexels.com/photo/adorable-animal-blur-cat-617278/'
]);$loop->run();

这是更新后Scraper的构造函数:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;
use React\Filesystem\FilesystemInterface;
use Symfony\Component\DomCrawler\Crawler;final class Scraper
{private $client;private $filesystem;private $directory;public function __construct(Browser $client, FilesystemInterface $filesystem, string $directory){$this->client = $client;$this->filesystem = $filesystem;$this->$directory = $directory;}// ...
}

好的,现在我们准备保存文件到磁盘上。首先,我们需要从 URL 提取文件名。图片的 URL 看起来就像这样:

https://images.pexels.com/pho...

这些 URL 的文件名是这样的:

jumping-cute-playing-animals.jpg\
pexels-photo-617278.jpeg

让我们使用正则表达式从 URL 里提取出文件名。为了给磁盘上的未来文件获取完整路径,我们用目录把名字串联起来:

<?phppreg_match('/photos\/\d+\/([\w-\.]+)\?/', $imageUrl, $matches); // $matches[1] 包含一个文件名
$filePath = $this->directory . DIRECTORY_SEPARATOR . $matches[1];

当我们有了一个文件路径,就可以用它创建一个 文件 对象:

<?php$file = $this->filesystem->file($filePath);

此对象表示我们要使用的文件。接着调用putContents($contents) 方法并提供一个响应体(response body)字符串:

<?php$file = $this->filesystem->file($filePath);
$file->putContents((string)$response->getBody());

就是这样。所有异步的底层魔法隐藏在一个单独的方法内。此 hook 会创建一个写模式的流,写入数据后关闭这个流。这是Scraper::processResponse(string $html)方法的更新版本:

<?phpuse Clue\React\Buzz\Browser;
use Psr\Http\Message\ResponseInterface;
use React\Filesystem\FilesystemInterface;
use Symfony\Component\DomCrawler\Crawler;final class Scraper
{// ...private function processResponse(string $html){$crawler = new Crawler($html);$imageUrl = $crawler->filter('.image-section__image')->attr('src');preg_match('/photos\/\d+\/([\w-\.]+)\?/', $imageUrl, $matches);$filePath = $matches[1];$this->client->get($imageUrl)->then(function(ResponseInterface $response) use ($filePath) {$this->filesystem->file($filePath)->putContents((string)$response->getBody());});}
}

我们传递了一个完整路径到响应的处理程序里。然后,我们创建了一个文件并填充了响应体。实际上,完整的Scraper只有不到 50 行的代码!

注意:在你想存储文件的位置先创建目录。putContents() 方法只创建文件,不会为指定的文件创建文件夹。

scraper 完成了。现在,打开你的主脚本,给scrape方法传递一个 URL 列表:

<?php
// index.php<?phprequire __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/ScraperForImages.php';use Clue\React\Buzz\Browser;
use React\Filesystem\Filesystem;$loop = \React\EventLoop\Factory::create();$scraper = new ScraperForImages(new Browser($loop), Filesystem::create($loop), __DIR__ . '/images'
);$scraper->scrape(['https://www.pexels.com/photo/adorable-animal-blur-cat-617278/','https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/','https://www.pexels.com/photo/adorable-animal-baby-blur-177809/','https://www.pexels.com/photo/adorable-animals-cats-cute-236230/','https://www.pexels.com/photo/relaxation-relax-cats-cat-96428/',
]);$loop->run();

上面的代码爬取 5 个 URL 并下载相应图片。所有这些工作会快速地异步完成。

[](https://user-gold-cdn.xitu.io...

file

结尾

在 上一个教程里,我们使用 ReactPHP 加速网站抓取过程并同时查询页面。但是,如果我们也需要同时保存文件呢?在异步的应用程序中,我们不能使用诸如file_put_contents()的原生 PHP 函数,因为它们会阻塞程序流程,所以在磁盘上存储图片不会有任何加速。想要在 ReactPHP 里以异步 - 非阻塞的方式处理文件时,我们需要使用 reactphp/filesystem 包。

所以,在上面 50 行的代码里,我们就能加速网站抓取并运行起来。这只是一个你也可以做的简洁例子。现在你有了怎样构建爬虫的基础知识,请尝试做一个自己的吧!

我还有一些用 ReactPHP 抓取网站的文章:如果你想 使用代理 或者 限制并发请求的数量,可以阅读一下。

        • *

你也可以从 GitHub 找到这篇文章的例子。

转自 PHP / Laravel 开发者社区 https://laravel-china.org/top...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_825311.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于jsp的团购网站代码_手把手教你做毕设计:基于JSP+MySQL的简单校园新闻网站(2)...

本篇教程是第二篇哈&#xff0c;现在给大家更新&#xff0c;大家跟上一期的第三篇调着看哈。不清楚的地方&#xff0c;可以留言。另外整理了一些视频学习教程&#xff0c;需要学习系统视频教程的伙伴&#xff0c;可以留言回复。我根据大家需求给大家发&#xff01;背景上一篇讲…

健康跑网站制作php,初跑者的完美进阶——李进教练

本帖最后由 花花助教 于 2021-3-1 17:07 编辑“运动教练指导你”是花粉俱乐部开设的专业运动知识栏目&#xff0c;以专业运动教练作为主要内容创作者&#xff0c;为广大花粉用户科普运健康知识&#xff0c;提倡广大花粉用户运动起来&#xff0c;更多详情请点击:我要成为运动教练…

excel教程自学网_5个相见恨晚的自学网站,个个精心挑选,爱学习的你正好需要!...

寒假到了&#xff0c;是不是觉得需要给自己充充电了&#xff1f;新学期才不会被同学甩一大截&#xff1f;上班族是不是经常找不到好的地方给自己补补专业知识&#xff1f;外面的培训课实在太贵了&#xff0c;今天教你在家就能轻松学习的方法&#xff0c;这五个相见恨晚的自学网…

云服务器建立第二个网站,云服务器第二个网站

云服务器第二个网站 内容精选换一换虚拟IP地址用于为网卡提供第二个IP地址&#xff0c;同时支持与多个云服务器的网卡绑定&#xff0c;从而实现多个云服务器之间的高可用性。登录管理控制台。单击管理控制台左上角的&#xff0c;选择区域和项目。选择“计算 > 云耀云服务器”…

seo代码优化工具_如何做好SEO优化方案中的四个主要环节?

我们应该知道&#xff0c;一个完整的SEO方案包含太多。网站优化方案主要包括网站前端制作、网站编辑、网站推广和数据分析四个主要环节。下面小编介绍一下关于SEO优化程序在四个主要环节如何做。第一部分是前端制造。在网站策划的前期&#xff0c;要做好网站的平面结构&#xf…

Mostafa结构清晰的商业网站设计(一)

Mostafa 1999年至2002年间自由职业&#xff0c;经营自己的设计公司Wayout&#xff0c;在2006年的时候加盟SilverKey做一名全职的设计师&#xff0c;设计范围包括网页设计、平面设计、LOGO设计等。   除此之外&#xff0c;Mostafa还非常喜欢摄影&#xff0c;目前是iStock…

信息产业部网站备案流程及相关注意事项

什么是网站备案? 网站备案是国家信息产业部对所有网站的一种管理方式&#xff0c;也是提倡良好的发展国家互联网的一个手段&#xff0c;有了备案就可以大量减少非法网站的出现&#xff0c;网站备案空间备案由信产部统一管理和审批.为了您和您用户的网站可以正常运营,也为了配…

网站建设报价表

网页设计价格体系网页设计费用分为套餐价和明细价&#xff1b;套餐适合小企业简单制作&#xff0c;明细计价适合设计要求高的企业和门户网站&#xff0c;特别是含动态内容的商务网站。网页制作套餐项目内容总价A经济套餐国际域名 50M WEB 10个页面制作&#xff08;大众化&…

韩国商业网站设计分析

一、页面结构韩国网站的页面结构相对来说比较简单&#xff0c;可以说几乎是统一的风格&#xff0c;顶部的左边是网站的logo&#xff0c;右边就是它的导航栏&#xff0c;和国内网站不一样的地方它甚少采用下拉菜单的样式&#xff0c;而是把各级栏目的下级内容放在导航栏的下面。…

10个设计最经典的web2.0网站

做好一个网站&#xff0c;不仅要在服务及功能上胜人一筹&#xff0c;而且网站的整体设计也是一个不可获缺的重要因素&#xff0c;一个设计漂亮的站点通常能在第一时间吸引人的眼球&#xff0c;用户回访机率也就更大。下面这十个web2.0网站的设计堪称经典&#xff0c;值得我们去…

html在不同电脑上排版不一样怎么办_认识html:实现网站页面是这么简单的一回事

互联网时代人们通过上网浏览信息&#xff0c;打开浏览器上网看到丰富的图文、视频、音乐等多媒体信息&#xff0c;一系列信息反馈和视觉冲击之后&#xff0c;您有没有想过&#xff0c;互联网这么发达的时代&#xff0c;您觉得花一点点时间学会做个网站页面不真香&#xff1f;概…

网站鉴赏(一)

Hostile Recordshttp://www.hostile.fm布局 网站的布局方式常见没有什么特别的地方!色彩 色彩采用了红色与灰度&#xff0c;使用的灰度表现出了钢铁质感&#xff0c;红色与灰度的对比给人留下了印象&#xff01;总体来讲色彩运用得当&#xff01;技术 FLASH的运用服从于设计&am…

2007最具流行的53种网站导航

在web2.0盛行的年代&#xff0c;随着web标准的概念的深入人心&#xff0c;导航的设计也随之发生变化。色彩明快&#xff0c;简洁&#xff0c;我们精选出53种最具代表性的网站导航供大家借鉴学习。摘自&#xff1a;视觉中国&#xff08;ChinaVisual.com&#xff09;1. Change.or…

亚马逊网站导航栏的变迁史

原文链接&#xff1a;http://www.lukew.com/ff/entry.asp?178 可以说是亚马逊网站普及了 Tab 式导航。这么多年来&#xff0c;他们一直在探索着一种用户界面&#xff0c;可以使他们越来越多的产品清晰的呈现在用户眼前。我们现在回头去看看亚马逊网站顶部导航的历史变迁也是一…

小论中英文网站的设计差异

你有没有被客户强迫照搬英文网站的经历&#xff1f; 你有没有被领导指责为比较“土”的作品&#xff1f; 你有没有感到疑惑&#xff0c;因为你搬过来的“英文”网站总是那么别扭&#xff1f; 如果有的话&#xff0c;这篇文章也许能帮你提供一些证据&#xff0c;至少让你自己能…

大型网站技术架构演进

转载&#xff1a;https://cloud.tencent.com/developer/article/1396863 【大型网站技术架构笔记】系列是阅读《大型网站技术架构核心原理与实践》一书的一些笔记&#xff0c;记录了原书的一些重要内容以及我的个人理解。其中很多内容网上都能找得到。其实整本书&#xff0c;我…

韩国网站构图和配色分析

我们先看看&#xff1a;韩国网站构图和配色分析&#xff0d;构图篇 [NextPage] 韩国网站构图和配色分析&#xff0d;配色篇

中国顶级品牌HONEST官方网站设计解析—道格瑞恩内部资料

中国顶级知名品牌 HONEST LIGHTERS 产品定位于&#xff1a;高档的打火机、名贵的雪茄剪与法式烟斗 销售定位与&#xff1a;主要出口欧洲与美洲国家的高档市场 网站作品浏览地址&#xff1a;http://218.30.101.85 http://www.0086.com设计与制作公司…

国际知名网站设计大师精品设计集

http://www.2advanced.com/ http://www.01-la.com/ http://www.preloaded.com/ http://www.aliveis.com/ http://www.linkdup.com/ http://www.mixdup.com/