如何使用PHP进行网页数据抓取
Web 抓取越来越流行,现在已成为 IT 社区中的热门话题。因此,有几个库可以帮助您从网站上抓取数据。在这里,您将学习如何使用最流行的网络抓取库之一在 PHP 中构建网络抓取工具。
在本教程中,您将学习使用 PHP 进行网页抓取的基础知识。然后如何绕过最流行的反抓取系统并学习更高级的技术和概念,例如并行抓取和无头浏览器。
遵循本教程,成为使用 PHP 进行网页抓取的专家!让我们不要浪费更多时间用 PHP 构建我们的第一个爬虫。
准备工作
这是简单的爬虫工作所需的先决条件列表:
然后,您还需要以下 Composer 库:
voku/simple_html_dom
>= 4.8 您可以使用以下命令将其添加到项目的依赖项中:
composer require voku/simple_html_dom
此外,您还需要内置的cURL
PHP 库。cURL
带有curl-ext
PHP 扩展,它在大多数 PHP 包中自动存在并启用。如果您安装的 PHP 包不包含curl-ext
,您可以安装它,如本指南中所述。
现在让我们更多地了解这里提到的依赖项。
介绍
voku/simple_html_dom
是Simple HTML DOM Parser项目的一个分支,它用DOMDocument和其他现代 PHP 类替换字符串操作。拥有近 200 万次安装,voku/simple_html_dom
是一个快速、可靠且简单的库,用于解析 HTML 文档和在 PHP 中执行网络抓取。
curl-ext
是一个 PHP 扩展,它在 PHP 中启用cURL HTTP 客户端,它允许您在 PHP 中执行 HTTP 请求。
您可以在此 GitHub 存储库中找到演示网络抓取工具的代码。克隆它并使用以下命令安装项目的依赖项:
git clone https://github.com/Tonel/simple-scraper-php cd simple-scraper-php composer update
按照本教程学习如何使用 PHP 构建网络抓取应用程序!
PHP 中的基本 Web 抓取
在这里,您将看到如何在 < https://scrapeme.live/shop/
> 上执行网络抓取,这是一个设计为抓取目标的网站。
如您所见,scrapeme.live
无非是一个分页的 Pokemon 启发产品列表。让我们用 PHP 构建一个简单的网络抓取工具,用于抓取网站并从所有这些产品中抓取数据。
首先,您需要下载要抓取的页面的 HTML。您可以使用 PHP 下载 HTML 文档,cURL
如下所示:
// initialize the cURL request $curl = curl_init(); // set the URL to reach with a GET HTTP request curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/"); // get the data returned by the cURL request as a string curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // make the cURL request follow eventual redirects, // and reach the final page of interest curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); // execute the cURL request and // get the HTML of the page as a string $html = curl_exec($curl); // release the cURL resources curl_close($curl);
您现在已将页面的 HTMLhttps://scrapeme.live/shop/
存储在$html
变量中。将其加载到具有以下功能HtmlDomParser
的实例中:str_get_html()
require_once __DIR__ . "../../vendor/autoload.php"; use vokuhelperHtmlDomParser; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); $html = curl_exec($curl); curl_close($curl); // initialize HtmlDomParser $htmlDomParser = HtmlDomParser::str_get_html($html);
您现在可以使用HtmlDomParser
浏览 HTML 页面的 DOM 并开始数据提取。
现在让我们检索所有分页链接的列表以抓取整个网站部分。右键单击页码 HTML 元素并选择“检查”选项。
选择“检查”选项以打开 DevTools 窗口
此时,浏览器应打开一个 DevTools 窗口或突出显示 DOM 元素的部分,如下所示:
选择页码 HTML 元素后的 DevTools 窗口
在 WebTools 窗口中,您可以看到page-numbers
CSS 类标识分页 HTML 元素。请注意,一个 CSS 类并不能唯一地标识一个 HTML 元素,许多节点可能具有相同的类。这正是页面page-numbers
中发生的事情scrapeme.live
。
因此,如果你想使用CSS 选择器来选择 DOM 中的元素,你应该将 CSS 类与其他选择器一起使用。特别是,您可以使用HtmlDomParser
CSS.page-numbers a
选择器来选择页面上的所有分页 HTML 元素。然后,遍历它们以从href
属性中提取所有必需的 URL,如下所示:
// retrieve the HTML pagination elements with // the ".page-numbers a" CSS selector $paginationElements = $htmlDomParser->find(".page-numbers a"); $paginationLinks = []; foreach ($paginationElements as $paginationElement) { // populate the paginationLinks set with the URL // extracted from the href attribute of the HTML pagination element $paginationLink = $paginationElement->getAttribute("href"); // avoid duplicates in the list of URLs if (!in_array($paginationLink, $paginationLinks)) { $paginationLinks[] = $paginationLink; } } // print the paginationLinks array print_r($paginationLinks);
请注意,该find()
函数允许您根据 CSS 选择器提取 DOM 元素。另外,考虑到分页元素在网页中放置了两次,需要定义自定义逻辑,避免数组中出现重复的元素$paginationLinks
。
如果执行,该脚本将返回:
Array ( [0] => https://scrapeme.live/shop/page/2/ [1] => https://scrapeme.live/shop/page/3/ [2] => https://scrapeme.live/shop/page/4/ [3] => https://scrapeme.live/shop/page/46/ [4] => https://scrapeme.live/shop/page/47/ [5] => https://scrapeme.live/shop/page/48/ )
如图所示,所有 URL 都遵循相同的结构,并以指定页码的最终数字为特征。如果你想遍历所有页面,你只需要与最后一页相关联的数字。检索它如下:
// remove all non-numeric characters in the last element of // the $paginationLinks array to retrieve the highest pagination number $highestPaginationNumber = preg_replace("/D/", "", end($paginationLinks));
$highestPaginationNumber
将包含“48”。
现在,让我们检索与单个产品关联的数据。同样,右键单击一个产品,然后使用“检查”选项打开 DevTools 窗口。这就是你应该得到的:
选择产品 HTML 元素后的 DevTools 窗口
如您所见,产品由包含URL、图像、名称和价格的li.product
HTML 元素组成。此产品信息分别放置在、、和HTML 元素中。您可以使用以下方法提取此数据:a
mg
h2
span
HtmlDomParseras
$productDataLit = array(); // retrieve the list of products on the page $productElements = $htmlDomParser->find("li.product"); foreach ($productElements as $productElement) { // extract the product data $url = $productElement->findOne("a")->getAttribute("href"); $image = $productElement->findOne("img")->getAttribute("src"); $name = $productElement->findOne("h2")->text; $price = $productElement->findOne(".price span")->text; // transform the product data into an associative array $productData = array( "url" => $url, "image" => $image, "name" => $name, "price" => $price ); $productDataList[] = $productData; }
如您所见,产品由包含URL、图像、名称和价格的li.product
HTML 元素组成。此产品信息分别放置在、、和HTML 元素中。您可以使用以下方法提取此数据:a
mg
h2
span
HtmlDomParseras
$productDataLit = array(); // retrieve the list of products on the page $productElements = $htmlDomParser->find("li.product"); foreach ($productElements as $productElement) { // extract the product data $url = $productElement->findOne("a")->getAttribute("href"); $image = $productElement->findOne("img")->getAttribute("src"); $name = $productElement->findOne("h2")->text; $price = $productElement->findOne(".price span")->text; // transform the product data into an associative array $productData = array( "url" => $url, "image" => $image, "name" => $name, "price" => $price ); $productDataList[] = $productData; }
此逻辑提取一页上的所有产品数据并将其保存在$productDataList
数组中。
现在,您只需遍历每个页面并应用上面定义的抓取逻辑:
// iterate over all "/shop/page/X" pages and retrieve all product data for ($paginationNumber = 1; $paginationNumber <= $highestPaginationNumber; $paginationNumber++) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "https://scrapeme.live/shop/page/$paginationNumber/"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $pageHtml = curl_exec($curl); curl_close($curl); $paginationHtmlDomParser = HtmlDomParser::str_get_html($pageHtml); // scraping logic... }
如果您想查看脚本的完整代码,可以在此处找到。运行它,您将检索以下数据:
[ { "url": "https://scrapeme.live/shop/Bulbasaur/", "image": "https://scrapeme.live/wp-content/uploads/2018/08/001-350x350.png", "name": "Bulbasaur", "price": "£63.00" }, ... { "url": "https://scrapeme.live/shop/Blacephalon/", "image": "https://scrapeme.live/wp-content/uploads/2018/08/806-350x350.png", "name": "Blacephalon", "price": "£149.00" } ]
避免被阻止
上面的示例使用了一个专为抓取而设计的网站。提取所有数据是小菜一碟,但不要被这个愚弄了!抓取网站并不总是那么容易,您的脚本可能会被拦截和阻止。了解如何防止这种情况发生!
有几种可能的防御机制可以防止脚本访问网站。这些技术尝试根据非人类或恶意用户的行为识别来自非人类或恶意用户的请求,然后阻止它们。
绕过所有这些反抓取系统并不总是那么容易。但是,您通常可以使用两种解决方案来避免其中的大部分问题:通用HTTP 标头和 Web 代理。现在让我们仔细看看这两种方法。
1. 使用通用 HTTP 标头模拟真实用户
许多网站会阻止看似不是来自真实用户的请求。另一方面,浏览器设置了一些 HTTP 标头。确切的标头因供应商而异。因此,这些反抓取系统希望这些标头存在。因此,您可以通过设置适当的 HTTP 标头来避免阻塞。
具体来说,您应该始终设置的最关键的标头是User-Agent标头(以下称为 UA)。它是一个字符串,用于标识发起 HTTP 请求的应用程序、操作系统、供应商和/或应用程序版本。
默认情况下,cURL
发送curl/XX.YY.ZZ
UA 标头,使请求可识别为脚本。您可以手动设置 UA 标头,cURL
如下所示:
curl_setopt($curl, CURLOPT_USERAGENT, "<USER_AGENT_STRING>");
例子:
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36")
这行代码设置了当前最新版谷歌浏览器使用的UA。它使 cURL 请求更难被识别为来自脚本。
您可以在线找到有效的、最新的和受信任的 UA 标头列表。在大多数情况下,设置 HTTP UA 标头就足以避免被阻止。如果这还不够,您可以发送其他 HTTP 标头,cURL
如下所示:
curl_setopt($curl, CURLOPT_HTTPHEADER, array( "<HTTP_HEADER_1>: <HEADER_VALUE>", "<HTTP_HEADER_2>: <HEADER_VALUE>", // ... ) );
例子:
// set the Content-Language and Authorization HTTP headers curl_setopt($curl, CURLOPT_HTTPHEADER, array( "Content-Language: es", "Authorization: 32b108le1HBuSYHMuAcCrIjW72UTO3p5X78iIzq1CLuiHKgJ8fB2VdfmcS", ) );
2. 使用 Web 代理隐藏您的 IP
反抓取系统往往会阻止用户在短时间内访问许多页面。主要检查查看请求来自的 IP。同一个IP短时间内多次请求会被屏蔽。换句话说,要防止对某个 IP 的封禁,您必须找到隐藏它的方法。
最好的方法之一是通过代理服务器。Web 代理是您的机器和互联网上其他计算机之间的中间服务器。通过代理执行请求时,目标网站将看到代理服务器的 IP 地址,而不是您的 IP 地址。
网上有几种免费代理,但大多数都是短暂的、不可靠的,而且经常不可用。您可以使用它们进行测试。但是,您不应该依赖它们来制作生产脚本。
另一方面,付费代理服务更可靠,通常带有 IP 轮换。这意味着代理服务器公开的 IP 会随着时间或每次请求而频繁更改。这使得该服务提供的每个 IP 更难被禁止,即使发生这种情况,您也会很快获得一个新 IP。
cURL
您可以使用以下方式设置 Web 代理:
curl_setopt($curl, CURLOPT_PROXY, "<PROXY_URL>"); curl_setopt($curl, CURLOPT_PROXYPORT, "<PROXY_PORT>"); curl_setopt($curl, CURLOPT_PROXYTYPE, "<PROXY_TYPE>");
例子:
curl_setopt($curl, CURLOPT_PROXY, "102.68.128.214"); curl_setopt($curl, CURLOPT_PROXYPORT, "8080"); curl_setopt($curl, CURLOPT_PROXY, CURLPROXY_HTTP);
对于大多数 Web 代理,在第一行设置代理的 URL 就足够了。CURLOPT_PROXYTYPE
可以采用以下值:(默认CURLPROXY_HTTP
)CURLPROXY_SOCKS4
、、、、CURLPROXY_SOCKS5
或。CURLPROXY_SOCKS4A
CURLPROXY_SOCKS5_HOSTNAME
您刚刚学会了如何避免被阻止。现在让我们深入研究如何使您的脚本更快!
并行抓取
在 PHP 中处理多线程是复杂的。有几个库可以为您提供支持,但在 PHP 中执行并行抓取的最有效解决方案并不需要它们中的任何一个。
这种并行抓取方法背后的想法是让抓取脚本准备好在多个实例上运行。可以使用 HTTP GET 参数。
考虑前面介绍的分页示例。您可以修改脚本以处理较小的块,然后并行启动脚本的多个实例,而不是让脚本遍历所有页面。
您所要做的就是将一些参数传递给脚本以定义块的边界。
您可以通过引入两个 GET 参数来完成此操作,如下所示:
$from = null; $to = null; if (isset($_GET["from"]) && is_numeric($_GET["from"])) { $from = $_GET["from"]; } if (isset($_GET["to"]) && is_numeric($_GET["to"])) { $to = $_GET["to"]; } if (is_null($from) || is_null($to) || $from > $to) { die("Invalid from and to parameters!"); } // scrape only the pagination pages whose number goes // from "$from" to "$to" for ($paginationNumber = $from; $paginationNumber <= $to; $paginationNumber++) { // scraping logic... } // write the data scraped to a database/file
现在,您可以通过在浏览器中打开这些链接来启动多个脚本实例:
https://your-domain.com/scripts/scrapeme.live/scrape-products.php?from=1&to=5 https://your-domain.com/scripts/scrapeme.live/scrape-products.php?from=6&to=10 ... https://your-domain.com/scripts/scrapeme.live/scrape-products.php?from=41&to=45 https://your-domain.com/scripts/scrapeme.live/scrape-products.php?from=46&to=48
这些实例将并行运行并同时抓取网站。您可以在此处找到这个新版本的抓取脚本的完整代码。
你有它!您刚刚学会了通过网络抓取从网站并行提取数据。
先进技术
请记住,并非网页上所有感兴趣的数据都直接显示在浏览器中。网页还包含元数据和隐藏元素。要访问此数据,请右键单击网页的空白部分,然后单击“查看页面源代码”
在这里您可以看到网页的完整 DOM,包括隐藏的元素。详细信息,您可以在meta HTML tags
. 此外,重要的隐藏数据可能存储在<input type="hidden"/>
elements中。
同样,一些数据可能已经通过隐藏的 HTML 元素出现在页面上。并且它仅在特定事件发生时由 JavaScript 显示。即使您看不到页面上的数据,它仍然是 DOM 的一部分。因此,您可以HtmlDomParser
像处理可见节点一样检索这些隐藏的 HTML 元素。
另外,请记住网页不仅仅是其源代码。网页可以在浏览器中发出请求,通过 AJAX 异步检索数据并相应地更新它们的 DOM。这些 AJAX 调用通常提供有价值的数据,您可能需要从网络抓取脚本中调用它们。
要嗅探这些调用,您需要使用浏览器的 DevTools 窗口。右键单击网站的空白部分,选择“检查”,然后到达“网络”选项卡。在“Fetch/XHR”选项卡中,您可以看到网页执行的 AJAX 调用列表,如下例所示。
演示页面执行的 POST AJAX 调用
浏览所选 AJAX 请求的所有内部选项卡以了解如何执行 AJAX 调用。cURL
具体来说,您可以使用以下方法复制此 POST AJAX 调用:
$curl = curl_init($url); curl_setopt($curl, CURLOPT_URL, "https://www.w3schools.com/jquery/demo_test_post.asp"); // specify that the cURL request is a POST curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // define the body of the request curl_setopt($curl, CURLOPT_POSTFIELDS, // http_build_query is required to simulate // a FormData request. Ignore it on a JSON request http_build_query( array( "name" => "Donald Duck", "city" => "Duckburg" ) ) ); // define the body of the request curl_setopt($curl, CURLOPT_POSTFIELDS, array( "name" => "Donald Duck", "city" => "Duckburg" ) ); // replicate the AJAX call $result = curl_exec($curl);
无头浏览器
嗅探和复制 AJAX 调用有助于以编程方式从因用户交互而加载的网站中检索数据。此数据不是网页源代码的一部分,无法在从标准 GETcURL
请求获得的 HTML 元素中找到。
但是,复制所有可能的交互、嗅探 AJAX 调用并在脚本中调用它们是一种麻烦的方法。有时,您需要定义一个可以像人类用户一样通过 JavaScript 与页面交互的脚本。您可以使用无头浏览器实现这一点。
如果您不熟悉这个概念,无头浏览器是一种没有图形用户界面的 Web 浏览器,可通过代码自动控制网页。提供无头浏览器功能的 PHP 中最流行的库是chrome-php
` 和Selenium WebDriver。有关更多信息,请访问我们关于使用 Selenium 进行网页抓取的指南。
其他图书馆
在网络抓取方面,您可能会采用的其他有用的 PHP 库是:
- Guzzle:一种高级 HTTP 客户端,可以发送 HTTP 请求并且很容易与 Web 服务集成。您可以将其用作
cURL
. - Goutte:一个网络抓取库,提供高级 API 用于抓取网站并从其 HTML 网页中提取数据。
voku/simple_html_dom
由于它还包括一个 HTTP 客户端,您可以将它用作和 的替代品cURL
。
结论
在这里,您了解了有关使用 PHP 执行网络抓取的所有知识,从基本抓取到高级技术。如上所示,用 PHP 构建一个可以爬取网站并自动提取数据的网络爬虫并不难。
您所需要的只是正确的库,这里我们介绍了一些最流行的库。
此外,您的网络抓取工具应该能够绕过反抓取系统,并且可能必须检索隐藏数据或像人类用户一样与网页交互。在本教程中,您还学习了如何执行所有这些操作。
如果您喜欢这个,请查看JavaScript 网络抓取指南。