互联网是一个庞大的世界,拥有超过47亿用户,且规模仍在不断扩大。换个角度来看,截至2018年,全球超过50%的人口都在使用互联网。
当然,用户数量的增加也意味着数据量的激增。目前,互联网规模如此庞大,据估计仅谷歌、亚马逊、微软和脸书四家公司的数据存储量就高达约120万太字节。
即便只是其中一小部分信息,也能创造新的商业机会。考虑到这一点,网络爬虫技术之所以如此流行也就不足为奇了。

互联网是一个庞大的世界,拥有超过47亿用户,且规模仍在不断扩大。换个角度来看,截至2018年,全球超过50%的人口都在使用互联网。
当然,用户数量的增加也意味着数据量的激增。目前,互联网规模如此庞大,据估计仅谷歌、亚马逊、微软和脸书四家公司的数据存储量就高达约120万太字节。
即便只是其中一小部分信息,也能创造新的商业机会。考虑到这一点,网络爬虫技术之所以如此流行也就不足为奇了。
互联网海量的数据分散在数十亿个网站中,分布极其零散。因此,开发者需要一种方法来收集和处理这些数据,从而为用户提供创新产品。然而,手动收集信息绝非良策,因为数据量往往过于庞大,且数据内容持续更新。
解决方案就是自动提取数据。简而言之,这就是网络爬虫的作用。
信息越多,意味着越多的创意、机遇和收益。经过处理后,这些数据对您或您的客户而言将具有不可估量的价值。以下仅列举网页抓取的几种应用场景:
例如,一个可能的应用场景是开发一款营养追踪应用,允许用户添加自己的餐食。理想情况下,用户只需打开应用,搜索自己吃过的食品,将其添加到工具中,并追踪之后还能摄入多少卡路里。
然而,该工具需要提供一份涵盖所有可能食品及其营养成分的详尽清单。这份清单可以通过从多个网站抓取营养信息来创建并自动更新。
虽然网页抓取对使用爬虫的用户非常方便,但有时网站并不乐意分享其内容,并可能试图阻止你。他们可能采取的措施包括:
克服这些挑战可能需要大量工作,但仍有解决方案。为此,我们开发了 WebscrapingAPI,它不仅能解决所有这些问题,还能助您更快地构建解决方案,并减少不必要的麻烦。
每当互联网用户访问网站时,浏览器都会发起一个 HTTP(超文本传输协议)请求。您可以将请求视为客户端(用户的计算机)向服务器(网站所在的计算机)发送的一条消息,其中客户端会指定其希望接收的内容。
每次发送请求后,您都会收到一个响应。响应可能是成功的,也可能是错误的,例如著名的“404 页面未找到”错误代码。网站的内容通常位于从服务器接收到的响应正文中。
请求和响应都包含用于交换信息的头部和主体。此外,请求可以采用多种方法,最常见的是 GET(用于访问网页)。这些方法表明了客户端想要执行的操作。
例如,当您在网站上注册或更新密码时,希望数据在浏览器中保持隐藏,此时网站可以使用 POST 或 PUT 方法来处理此类请求。
请求的头部包含多个属性。让我们来了解最重要的几个:
不过,请求不仅限于网页。它们也用于加载图片、样式和 JavaScript 代码,且独立于页面本身。您可以在访问网页时按下 F12 键,选择“网络”选项卡并刷新当前页面,从而一览 Google Chrome 浏览器发出的所有请求。您应该会在底部看到类似以下内容:
PHP 是用于应用程序后端的最古老且最流行的 Web 编程语言之一。它自 1995 年问世以来,现已发展到第 8 个版本。
程序员选择这种编程语言是因为其语法简单且运行便捷——运行 PHP 代码仅需一台安装了 PHP 的机器即可。此外,由于它问世已久,因此拥有丰富的资源和支持,可用于解决和调试 PHP 错误。
基于 PHP 构建的流行框架和 CMS(内容管理系统)也层出不穷,其中著名的例子包括 WordPress、Drupal、Magento 和 Laravel。
不过,它也存在一些缺点。例如,与 Python 或 JavaScript 相比,PHP 抓取动态内容更为困难。但若您仅需从简单页面获取信息,PHP 无疑是理想的选择,且能更轻松地保存或存储抓取到的数据。
目前一切顺利吗?准备好创建你的第一个网页爬虫了吗?开始之前,你需要一个运行 PHP 代码的环境。你可以选择安装了 PHP 的 Apache/Nginx 服务器,直接在浏览器中运行代码,或者通过命令行运行代码。
为了简化操作,我们可以使用库来处理抓取的内容。一些流行的 PHP 抓取库包括 Goutte、Simple HTML DOM、Panther 和 htmlSQL。此外,你也可以选择使用正则表达式来处理内容。
在本指南中,我们将使用 Simple HTML DOM。不过,对于更复杂的请求,我们还将使用名为 CURL 的 PHP 库。
Simple HTML DOM 是一个专为 PHP 5.6 及以上版本开发的库,它允许我们通过选择器更轻松地访问页面内容。您可以从这里下载该库,并建议您阅读相关文档。
从下载链接中的 zip 文件中,您只需提取 simple_html_dom.php 文件,并将它放置在您编写爬虫代码的同一文件夹内。
要在代码中引入该库,只需添加以下这一行代码:
include 'simple_html_dom.php'; // If the library is in another folder you should do include 'path_to_library/simple_html_dom.php'
虽然并非总是必要,但在进行更复杂的请求时,您需要发送不同的请求头。使用 PHP-CURL 库将对此有所帮助。
要在 Ubuntu 系统上安装它,可以使用以下命令:
sudo apt-get install php-curl
安装库后,请勿忘记重启 Apache/Nginx 服务器。
现在我们已具备所需的一切,是时候开始提取数据了!首先,您需要确定要抓取的网站及其内容。本文将以IMDB的“最高评分电影榜单”为例进行数据抓取。
大多数网页内容都是通过 HTML 显示的。由于我们需要从 HTML 源代码中提取特定内容,因此也需要理解 HTML。首先,我们需要查看页面源代码的样子,以确定要从页面中提取哪些元素。
在 Google Chrome 中,你可以右键点击想要提取的元素,然后选择“检查元素”。这会在浏览器中打开一个窗口,显示页面源代码以及元素的渲染样式。在这个窗口中,我们只需要查看“元素”选项卡,它将向我们展示页面 HTML DOM 的结构。
例如,上图中的页面包含一个带有“chart”和“full-width”类名的表格。在这个表格中,每个单元格都有自己的类名(如posterColumn、titleColumn等),我们可以利用这些类名来创建选择器。这样,我们就能精准获取所需的数据。
感到困惑?别担心,接下来的步骤会为您厘清思路。
在此情境下,发送请求基本上意味着通过 PHP 代码直接访问页面的 HTML。实现方式有两种。
首先,我们可以使用 PHP-CURL 库,它还允许我们修改请求中发送的头部和正文。
<?php
header("Content-Type: text/plain"); // We choose to display the content as plain text
$ch = curl_init("https://www.imdb.com/chart/top/");
curl_setopt($ch, CURLOPT_HEADER, 0);
$response = curl_exec($ch); // Running the request
if (curl_error($ch)) {
echo curl_error($ch); // Displaying possible errors from the request
} else {
echo $response; // Displaying the content of the response
}
curl_close($ch);
?>另一种选择是使用 file_get_contents($url) 方法实现一行代码完成,但在某些情况下这可能不够用。若要向该请求发送头部信息,你需要使用通过 stream_context_create 方法创建的上下文。
<?php
header("Content-Type: text/plain"); // We choose to display the content as plain text
echo file_get_contents('https://www.imdb.com/chart/top/'); // We retrieve and display the contents of the response in a single line?>
您应根据要构建的爬虫程序的复杂程度来决定采用哪种方法。
上面的两段代码将显示我们正在抓取的页面的 HTML 源代码,这与您检查网站时所看到的源代码相同。我们将使用第一行代码将结果显示为 text/plain 格式。否则,它将直接作为 HTML 渲染。
如果 HTML 结构存在差异,则说明网站上运行着一段 JavaScript 代码,并在用户访问时动态更改了内容。本文后文将提供应对此类情况的处理技巧。
从选定的页面中,我们将仅提取电影的标题及其对应的评分。如前所述,内容以表格形式呈现,每个单元格都有其对应的类名。
利用这一点,我们可以选择提取表格中的所有行。然后,我们遍历每一行,查找我们感兴趣的单元格。
以下代码片段可实现这一功能:
<?php
header("Content-Type: text/plain"); // We choose to display the content as plain text
include 'simple_html_dom.php';
$html_dom = file_get_html('https://www.imdb.com/chart/top/'); // We retrieve the contents using file_get_html from simple_html_dom
$table_rows = $html_dom->find('table.chart tbody tr'); // Getting all of the table rows
foreach($table_rows as $table_row) {
$title_element = $table_row->find('.titleColumn a', 0);
$rating_element = $table_row->find('.ratingColumn strong', 0);
if (!is_null($title_element) && !is_null($rating_element)) { // Checking if the row has a title and a rating column
echo $title_element->innertext . ' has rating ' . $rating_element->innertext . PHP_EOL; // If it does then we print it
}
}
?>您会注意到,我们使用了“table.chart tbody tr”选择器来提取表格的所有行。建议使用尽可能具体的选择器,以便将所需元素与其他元素区分开来。
获取行数据后,我们通过循环遍历,查找具有 class="titleColumn" 或 class="ratingColumn" 的元素。若代码找到此类元素,便会显示其 innerText 属性。
需要注意的是,本示例中我们使用了 file_get_html 而不是 file_get_contents。这是因为该函数来自 simple_html_dom 库,它作为 file_get_contents 函数的封装层而工作。
在上述示例中,我们收集了网站数据并直接在屏幕上显示。不过,在 PHP 中保存数据也非常简单。
您可以将抓取的数据保存为 .txt 文件、JSON 格式、CSV 格式,甚至直接发送至数据库。PHP 在这方面表现非常出色。我们只需将数据存储在数组中,并将数组内容写入新文件即可。
<?php
include 'simple_html_dom.php';
$scraped_data = [];
$html_dom = file_get_html('https://www.imdb.com/chart/top/'); // We retrieve the contents using file_get_html from simple_html_dom
$table_rows = $html_dom->find('table.chart tbody tr'); // Getting all of the table rows
foreach($table_rows as $table_row) {
$title_element = $table_row->find('.titleColumn a', 0);
$rating_element = $table_row->find('.ratingColumn strong', 0);
if (!is_null($title_element) && !is_null($rating_element)) { // Checking if the row has a title and a rating column
$scraped_data[] = [
'title' => $title_element->innertext,
'rating' => $rating_element->innertext,
];
}
}
file_put_contents('file.json', json_encode($scraped_data)); // Saving the scraped data in a .json file
// Saving the scraped data as a csv
$csv_file = fopen('file.csv', 'w');
fputcsv($csv_file, array_keys($scraped_data[0]));
foreach ($scraped_data as $row) {
fputcsv($csv_file, array_values($row));
}
fclose($csv_file);
?>上面的代码使用我们之前提取的相同内容,创建了两个文件:一个 CSV 文件和一个 JSON 文件,其中包含所有高评分电影及其评分。
在 PHP 编程中,当从可能随时变化的网站抓取数据时,出现错误是常有的事。以下三行代码是用于调试的实用代码,可将其置于任何 PHP 脚本的开头:
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);这些代码将帮助您更快地识别代码中的问题,并在必要时更新脚本。
有时在发起请求时,您可能还需要发送一些请求头。例如,在调用 API 时,可能需要授权令牌,或者您希望内容以 JSON 格式而非纯文本形式返回。您既可以通过 curl,也可以通过 file_get_contents 来添加请求头。以下是使用 curl 的方法:
$ch = curl_init("http://httpbin.org/ip");
curl_setopt($ch, CURLOPT_HEADER, [
'accept: application/json'
]);
$response = curl_exec($ch); // Running the request而使用 file_get_contents 的方法如下:
$opts = [
"http" => [
"method" => "GET",
"header" => "accept: application/json\r\n"
]
];
$context = stream_context_create($opts);
$result = file_get_contents("http://httpbin.org/ip", false, $context);在从 IMDB 提取内容时,我们使用了 simple_html_dom 中的 file_get_html 函数进行抓取。这种方法适用于简单的请求,但未必适用于更复杂的请求。如果你需要发送头部信息,最好使用前一个技巧中提到的方法之一。
若要用它们代替 file_get_html,只需先提取内容,然后使用 str_get_html 将其转换为 DOM 对象,如下所示:
$opts = [
"http" => [
"method" => "GET",
"header" => "accept: text/html\r\n"
]
];
$context = stream_context_create($opts);
$result = file_get_contents("https://www.imdb.com/chart/top/", false, $context);
$html_dom = str_get_html($result);此外,请注意 simple_html_dom 默认存在一些限制(可在 simple_html_dom.php 文件中查阅)。例如,网站内容的长度上限为 600,000 个字符。若需修改此限制,只需在包含 simple_html_dom 库之前,在代码开头进行定义:
define('MAX_FILE_SIZE', 999999999);
若要抓取动态网站,你需要像浏览器那样访问该网站。否则,你将无法提取实际数据,而是会获取到 JavaScript 代码。
您需要安装浏览器驱动程序,例如 chromium-chromedriver 或 firefox-geckodriver。在 PHP 中提取动态内容属于进阶内容,但如果您感兴趣,可以阅读 panther 库的文档尝试实现。
或者,一个更简单的解决方案是使用 WebScrapingAPI,它能解决大多数问题。该 API 通过我们的代理网络绕过 IP 封锁和验证码,同时还能解析 JavaScript。结果:您立即拥有一个高级爬虫,大大缩短了开发和等待时间。
以下是一个代码示例,它将通过我们的 API 直接在 PHP 中显示 https://httpbin.org/ip 上的内容:
$content =
file_get_contents("https://api.webscrapingapi.com/v1?pi_key=YOUR_API_KEY&url=". urlencode('https://httpbin.org/ip'));
echo $content;恭喜您读到最后!现在您应该已经掌握了使用 PHP 构建 Web 爬虫所需的一切。虽然本文仅探讨了 simple_html_dom 库,但您也可以尝试其他流行的库,亲自体验哪一种更适合您。
请记住,网站内容时刻都在变化,数据可能一夜之间更新。为应对这种情况,您可以使用更具体的选择器。当然,这并不能保证您的爬虫能永远正常运行,但这毕竟是一个开始。正因如此,Web爬虫才需要持续且耗时的更新维护。
如果您不想花费大量时间研究和调整代码,随时可以尝试 WebScrapingAPI 的免费试用版!

索林·马里卡(Sorin Marica)是 WebScrapingAPI 的全栈及 DevOps 工程师,负责开发产品功能并维护确保平台平稳运行的基础设施。