response 返回图片_ReactPHP 爬虫实战:下载整个网站的图片

news/2024/5/20 21:51:35/文章来源:https://blog.csdn.net/weixin_39863759/article/details/110990613

7d0bb9f85b61c86998d48a92c7e514ed.png

什么是网页抓取?

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

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

64d480629bcfa80ec06acf02e72f585f.png

发起并发请求

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

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

composer require clue/buzz-react

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

<?phprequire __DIR__ . '/vendor/autoload.php';use ClueReactBuzzBrowser;$loop = ReactEventLoopFactory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$loop->run();

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

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

<?phprequire __DIR__ . '/vendor/autoload.php';use ClueReactBuzzBrowser;$loop = ReactEventLoopFactory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$client->get('https://www.pexels.com/photo/adorable-animal-baby-blur-177809/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$loop->run();

这里的代码含义如下:

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

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

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

<?phpuse ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;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 ClueReactBuzzBrowser;$loop = ReactEventLoopFactory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$loop->run();

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

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

<?phprequire __DIR__ . '/vendor/autoload.php';use ClueReactBuzzBrowser;$loop = ReactEventLoopFactory::create();$client = new Browser($loop);
$client->get('https://www.pexels.com/photo/kitten-cat-rush-lucky-cat-45170/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$client->get('https://www.pexels.com/photo/adorable-animal-baby-blur-177809/')->then(function(PsrHttpMessageResponseInterface $response) {echo $response->getBody();});$loop->run();

这里的代码含义如下:

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

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

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

<?phpuse ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;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 的图片页,右击图片并选择审查元素,你会看到一些东西,就像这样:

2b7a0af0f3588b7a3867395e8e6d0261.png

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

ef8eaa69e6cf552e8a3ca2840f4a400d.png

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

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

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

<?phpuse ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;
use SymfonyComponentDomCrawlerCrawler;final class Scraper
{// ...private function processResponse(string $html){$crawler = new Crawler($html);}
}

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

<?phpuse ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;
use SymfonyComponentDomCrawlerCrawler;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 ClueReactBuzzBrowser;$loop = ReactEventLoopFactory::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 ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;
use SymfonyComponentDomCrawlerCrawler;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 ClueReactBuzzBrowser;
use ReactFilesystemFilesystem;$loop = ReactEventLoopFactory::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 ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;
use ReactFilesystemFilesystemInterface;
use SymfonyComponentDomCrawlerCrawler;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/photos/4602/jumping-cute-playing-animals.jpg?auto=compress&cs=tinysrgb&h=650&w=940**https://images.pexels.com/photos/617278/pexels-photo-617278.jpeg?auto=compress&cs=tinysrgb&h=650&w=940

这些 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 ClueReactBuzzBrowser;
use PsrHttpMessageResponseInterface;
use ReactFilesystemFilesystemInterface;
use SymfonyComponentDomCrawlerCrawler;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 ClueReactBuzzBrowser;
use ReactFilesystemFilesystem;$loop = ReactEventLoopFactory::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 并下载相应图片。所有这些工作会快速地异步完成。

4cef6811e84c9f352f3eba256550b5fa.gif

结尾

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

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

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


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

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

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

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

相关文章

语文网站第十九周推荐博客

新叶子的博客  真正的幸福&#xff0c;在于对平淡生活的热爱!用真心交知心朋友与月有关的成语 相信你的孩子是最棒的 艾岚的博客  春来了&#xff0c;看花去&#xff1b;秋来了&#xff0c;扫落叶&#xff01; [情感天地]春风相送[班务日志]我与我的猴儿们&#xff08;5月…

接口做的好怎么形容_电商网站平台怎么做?

关于电商网站平台的开发问题&#xff0c;之前敖游已经分享了很多篇相关文章。电商网站平台怎么做&#xff1f;平台开发需要准备哪些资料&#xff1f;…… 这次我准备做一个总结性的发布。让准备做电商的朋友&#xff0c;看了这篇文章基本上可以找到想要的解决方法。一、个人可以…

绕过CDN查找网站真实IP方法

查找网站 源IP方法&#xff1a; 如果遇到需要绕过CDN&#xff0c;查找网站真实IP地址时&#xff0c;可以采用如下方法&#xff1a; 假设主站服务和邮件服务在同一台服务器&#xff1a; 1.在网站用QQ邮箱注册账号&#xff1b; 2.收取注册验证邮件&#xff1b; 3.查看邮件-->显…

ubuntu 自适应分辨率_Web—响应式网站和自适应网站区别

"做网页设计的时候总是混淆响应式设计和自适应设计&#xff0c;开始我以为这俩都是指根据屏幕大小自动进行适配。所以和前端小哥哥沟通时候&#xff0c;前端小哥哥蒙圈了&#xff0c;跟我强度这俩不是一个东西~~~~今天从设计角度和开发角度介绍下这俩的区别"“开篇”…

推荐:一款超强、超简单的在线免费照片特效网站

网址是&#xff1a;http://www.photofunia.com/只需先在此网站主页上列出的74个预设特效中选择一个喜欢的&#xff0c;然后再上传一张照片图片&#xff0c;等待20秒&#xff0c;OK&#xff0c;特效完成了。这是我得到的两个特效&#xff1a;

高清视频录播服务器网站,高清录播服务器——DDA RSS3000

高清录播服务器——DDA RSS3000DDA RSS3000完全采用H.323协议研制&#xff0c;具有超强处理能力&#xff0c;可支CIF、4CIF、720P、1080P等高清画面以及H.239双视频流的录制&#xff0c;并可同时录制多个会场的画面。DDA RSS3000具有超强的兼容性&#xff0c;可以配合所有标准H…

linux 启动一个网站_想体验各种各样的Linux发行版?这里在在线版的,免费的哟...

Linux 和 Unix 存在着各种各样的发行版本&#xff0c;有的界面美观&#xff0c;有的功能强大&#xff0c;想要尝试不同的 Linux 和 Unix 操作系统&#xff0c;你可能会烦于进行本地安装。不急&#xff0c;今天&#xff0c;我们来介绍一个强大的服务&#xff0c;DistroTest 。Di…

HTML5+CSS3 效果网站集合

1、jquery插件库 HTML5制作3D樱花漫天飞舞 http://www.jq22.com/jquery-info3547 2、17素材网 http://www.17sucai.com/pins/tag/532.html 3、jquery之家 http://www.htmleaf.com/html5/ 心怀梦想,勿忘初心转载于:https://www.cnblogs.com/sxz2008/p/6497828.html

在etherscan.io网站校验智能合约源码

有图有真相&#xff0c;https://etherscan.io/address/0x95b0bf320f6e51ef3439aa673a7ca7b5b03746fd#code 第一步&#xff1a;打开校验合约源码的网址&#xff1a;https://etherscan.io/verifyContract2 第二步&#xff1a;检查网页是否正常&#xff0c;这一步对国内的同学们…

解决谷歌浏览器无法从该网站添加应用、拓展程序和用户脚本

今天将谷歌浏览器升级到了最新的版本&#xff0c;在安装拓展应用的时候&#xff0c;却发现无法添加应用、拓展程序和用户脚本&#xff0c;让我很是郁闷&#xff0c;现整理解决方法如下&#xff1a; 1.在Google Chrome浏览器的桌面快捷方式上鼠标右键&#xff0c;选择属性(R)&am…

让个人pc上的iis网站可以在Internet上访问

背景&#xff1a; 相信很多人&#xff08;学生、工作的程序员&#xff09;都遇到过这种情况&#xff0c;自己做了个小网站&#xff08;给客户的演示项目、小的测试网站等&#xff09;&#xff0c;想让让局域网意外的人可以通过Internet访问你的网站&#xff0c;想让他们给你提点…

Java实现电商网站的随机订单流水号

我们假设订单流水是由&#xff1a;年月日时分秒8位随机数所构成 第一种&#xff1a;使用SimpleDateFormat对当前时间进行格式化&#xff0c;利用Math.random()获取八位随机数 import java.text.SimpleDateFormat; import java.util.Date;public class test {public static vo…

如何把mysql数据库放在阿里云_将网站文件及MySql数据库存放在阿里云挂载盘内...

本站使用了阿里云服务器以及LNMP1.0&#xff0c;图片储存使用了又拍云&#xff0c;之前介绍过阿里云的配置&#xff0c;因为考虑网站安全问题&#xff0c;所以多选择了10G的数据盘&#xff0c;为了将网站程序文件以及数据库数据放在数据盘里&#xff0c;保证数据不丢失&#xf…

云服务器 可以部署几个网站,云服务器 可以部署几个网站

云服务器 可以部署几个网站 内容精选换一换当防护网站部署模式为云模式时&#xff0c;您可以配置防护域名流量经过的线路。如果您已开通企业项目&#xff0c;您可以在企业项目下拉列表中选择您所在的企业项目&#xff0c;为该企业项目下的域名设置线路。防护网站的部署模式为云…

国外网站设计欣赏:30个优秀的国外咖啡网站设计

梦想天空博客向大家分享各种类型的优秀网站作品&#xff0c;有电子商务网站设计、旅游网站设计、餐馆网站设计、设计工作室网站设计、大学网站设计以及各种色系&#xff0c;各种风格的网站设计作品。今天这篇文章给大家带来30个优秀的国外咖啡网站设计作品&#xff0c;一起欣赏…

怎样禁止自己的网站在访问某目录时直接列出网站目录

2019独角兽企业重金招聘Python工程师标准>>> 怎样禁止自己的网站在访问某目录时直接列出网站目录---本文针对使用apache主机的网站。 有时候访问一个不应当被访问的网站目录时网站会列出该目录下的所有文件&#xff0c;这很不安全&#xff0c;尤其是我们希望我们自己…

使用django建站系列之登录页面(一)

先秀一下我的登录页&#xff1a;废话没有&#xff0c;直接上操作步骤。1&#xff09;建立工程#django-admin.py startproject MyWeb2)建立应用#cd MyWeb/ #django-admin.py startapp app51cto3)修改settings.py添加appvim MyWeb/settings.pyINSTALLED_APPS (django.contrib.au…

iptables的conntrack表满了导致访问网站很慢

为什么80%的码农都做不了架构师&#xff1f;>>> 现象&#xff1a;突然发现访问网站很慢&#xff0c;服务器的cpu、内存和磁盘使用率都正常 分析过程及解决方案&#xff1a;查询/var/log/message日志发现有这样的记录“ip_conntrack table full dropping packet”。…

关于配置Apache网站根目录以及默认页面

今天然想在自己的服务器上上传一个静态页面&#xff0c;并通过IP地址访问&#xff0c;于是在网上查了很多资料&#xff0c;大部分情况都是如下&#xff1a; 修改根目录&#xff1a; 在 /etc/apache2/sites-available 中修改 000-default.conf 中的DocumentRoot /var/www/ 修改为…