in

使用PHP和代理IP进行网页抓取的详细教程

使用PHP和代理IP进行网页抓取的详细教程

使用PHP进行网页抓取是入门抓取的最简单方法。抓取网站有很多好处,比如你可以监控竞争对手和供应商,检查价格变化,并通过观察数据趋势生成洞察。但首先你需要收集这些信息。

根据W3Techs的数据,WordPress支持了43%的网站。这意味着全球至少43%的网站依赖于PHP。因此,与其学习新语言、设置新环境、使用不同的服务器并从零开始解决各种问题,不如直接在你的网站旁运行一个PHP抓取器,这样要容易得多。

但即使使用PHP,也有不少挑战。首先,最大的挑战是正确渲染页面(包括JS代码)。其次,你需要找到避免被封锁的方法。最后,如何提取数据和与页面交互也存在一些挑战。

今天我们的目标是学习使用PHP进行网页抓取。你将学到以下内容:

  • 网页抓取的基础知识
  • 如何选择合适的PHP网页抓取工具
  • 如何在不被封锁的情况下进行抓取
  • 如何从页面中提取数据
  • 使用PHP抓取器与页面交互
  • 常见技术问题的FAQ

让我们开始吧!


PHP适合做网页抓取吗?

PHP并不是进行网页抓取的完美解决方案,但它非常容易学习和上手。对于大多数应用来说,它已经足够用了,而且入门门槛非常低。你只需要一个PHP服务器,这几乎可以从任何共享虚拟主机提供商那里获得。

不过要记住,它并不完美。PHP可能有点慢,而且你无法像在NodeJS或Python中那样运行大量并发的抓取任务。无论如何,如果你只是刚开始或者只是为自己的业务运行抓取任务,PHP已经绰绰有余了。


步骤一 – 选择合适的PHP抓取工具

如果选择了合适的工具,你99%的问题都能解决。想象一下,如果你在喝汤,你会选择叉子还是勺子?开发工具也是一样。经常会有一些工具可以用来完成任务,但它们的功能可能非常有限,使用这些工具可能不值得花费精力。

有很多工具可以选择,比如Guzzle、PHP-PhantomJS、Mink、Regex、Symfony/Panther、Chrome PHP等。幸运的是,这些工具大致可以分为四类,这使得分析它们变得更简单。以下是这四类工具:

1. 使用原生PHP函数(如Regex)从HTML代码中提取数据

经常有人建议获取页面的HTML代码,然后使用正则表达式或其他函数从中提取数据。通过这些函数,你可以查找特定的标签,然后读取数据。这听起来不错,对吧?

正则表达式在某些方面很出色,但网页抓取并不是其中之一。正则表达式就像是喝汤时用的叉子。在非常特定的场景下它能工作,但在大多数情况下会出问题。任何未闭合的标签、自闭合标签、动态元素,甚至有时一个点的位置不对,都会导致它失效。

如果你想学习正则表达式,那当然可以。但如果你想抓取一些页面,我们需要一个更强大的解决方案。

2. HTML解析器

这也是一个常见的建议。一些库会获取HTML代码并模拟渲染页面。它比正则表达式好一些,但仍然非常有限。你受限于解析器的能力。如果解析器没有实现特定的浏览器功能,你就无法获得所需的结果。很难证明投资时间在一个一开始就过时的解决方案上是合理的。

3. 加载非HTML请求(API、XHR)

这是一种聪明的方法,可以在不使用网页抓取器的情况下抓取元素。在这种方法中,你检查目标网站是否有API,或者通过查看XHR请求来确定它是否动态加载数据。

听起来很复杂,但其实很简单。打开目标网站,然后打开开发者工具并检查网络标签。只选择XHR请求(你需要重新加载页面以查看所有请求):

reddit.com trending today

这里是reddit.com。但请注意“今日趋势”选项卡实际上是如何作为JSON对象加载的。因此,如果希望抓取特定的数据,只需要重复这个XHR请求。所以,如果你浏览https://reddit.com/api/trending_searches_v1.json?withAds=1&subplacement=tile&raw_json=1&gilding_detail=1

将获得针对当前日期更新的相同JSON对象。这同样适用于大多数网站,甚至是你意想不到的网站。任何WordPress站点都默认启用了JSON API。因此,如果想要抓取它,只需要访问它的端点。

例如,你可以在这里看到TechCrunch的最新帖子: https://techcrunch.com/wp-json/wp/v2/posts

大多数自定义文章类型(如页面和产品)也是如此。所以,如果你想监控一个WooCommerce商店的价格,你可以直接从目标的API中加载这些信息。

说到实际使用PHP加载这些数据,只需要一个函数:cURL。不过我们不会在这里深入探讨cURL,因为我们有一个更好的工具推荐给你——而且你还可以用它来加载XHR请求。

现在是时候介绍你进行PHP网页抓取的最佳伙伴了:无头浏览器。

4. 无头浏览器

归根结底,抓取基本上是通过代码模拟用户行为,而无头浏览器可以让你做到这一点。使用无头浏览器,你可以通过代码打开并控制浏览器的操作。

这是因为像Firefox和Chrome这样的浏览器提供了一个API,允许开发者与它们进行交互。因此,你可以做任何你想做的事情。你可以填写表单、按键、移动鼠标光标、点击、截屏(包括全页或裁剪后的截图)。你甚至可以运行任意的JS代码,这为实现你能想象到的任何操作打开了大门。

在本教程中,我们使用的是Chrome PHP,这是一个维护得非常好的库,用于控制基于Chromium的浏览器。如果你想跟着一起操作,可以使用Composer来安装它:

composer require chrome-php/chrome

如果你从未使用过Composer,它可以帮助你管理代码库,加载所有的依赖项。因此,当你下载chrome-php/chrome时,会创建多个文件夹,包括Symfony、evenement、monolog等。

如果你完全不想使用Composer,你可以使用https://php-download.com/或者https://github.com/Wilkins/composer-file-loader。一旦你安装了ChromePHP,你可以从命令行运行它,或者创建一个类似这样的文件:

<?php

use HeadlessChromium\BrowserFactory;

//don't forget to load the library and dependencies
require_once 'vendor/autoload.php';

$browserFactory = new BrowserFactory();

// starts headless chrome
$browser = $browserFactory->createBrowser();

try {
    // creates a new page and navigates to an URL
    $page = $browser->createPage();
    $page->navigate('http://ipv4.icanhazip.com')->waitForNavigation();

    // get the IP from a  tag
    $el = $page->dom()->querySelector('pre');

    //get the text from the element
    $text = $el->getText();

    //output the result
    echo "Your IP Address is $text";
} finally {
    // close the connection
    $browser->close();
}
?>

然后在浏览器中打开这个文件,一切都设置好了。


步骤二- 如何避免PHP抓取被封锁

检测网站抓取的方法有很多种。无论你是加载XHR请求、使用API,还是使用无头浏览器,如果不小心,你都会被封锁。因此,你需要使用代理来混淆身份,避免被发现。

在抓取过程中,最简单的实现方法是使用住宅代理服务。通过它们,你可以使用住宅IP地址加载网站,每次都会轮换地址。

这样,当你加载第一个页面时,网站会认为你来自美国,而在下一个页面,目标网站会认为你在西班牙。对他们来说,这就像是两个不同的用户在查看两个不同的页面。因此,他们无法封锁你。

比如我们以IPRoyal为例,如果你已经购买了它的住宅IP,就可以访问客户端区域,在那里你可以看到你的凭证:

IPRoyal residential proxies dashboard

代理认证怎么办?

现在是时候介绍第一个大问题了。Chrome不允许你使用代理认证头。这意味着,当你启动浏览器时,你可以设置代理服务器的IP地址,像这样:

$browser = $browserFactory->createBrowser([
    'customFlags' => [ '--proxy-server=http://geo.iproyal.com:12321' ]
]);

但你无法将用户名/密码信息传递给代理服务器。但有两种解决方案——一种是简单快捷的方法,另一种是比较繁琐的方法。

简单快捷的方法是将你的IP地址列入IPRoyal的白名单。点击白名单标签,然后在那里添加你的IP地址。来自这个IP地址的所有连接都不需要凭证。

iproyal dashboard IP whitelisting

如果你有固定IP或者可以在开始抓取之前手动更新这些信息,这种方法效果很好。如果这不可能,你可以使用mitmproxy。通过它,你基本上可以设置一个“桥接”代理。你不会直接连接到IPRoyal,而是连接到这个桥接代理,然后它会带着你的凭证连接到IPRoyal。

你甚至可以在自己的电脑上安装mitmproxy,这样所有连接到你本地主机特定端口的请求都会转发到IPRoyal。因此,你的PHP代码会像这样:

$browser = $browserFactory->createBrowser([
    'customFlags' => [ '--proxy-server=http://localhost:3128' ]
]);

这个3128端口是使用您的凭证连接到http://geo.iproyal.com:12321的端口。


步骤三 – 使用PHP提取抓取的数据

现在你可以安全地连接到任何网站,是时候开始抓取了!你可以使用多种方法来提取数据,但有三种方法能够涵盖大部分需求:

1. 截图 / 生成PDF

一旦你连接到一个页面,你可以使用以下代码进行截图:

$page->screenshot()->saveToFile('/foo/bar.png');

你可以用下面的代码保存pdf文件:

$page->pdf(['printBackground' => false])->saveToFile('/foo/bar.pdf');

每一种方法都有很多选择。以下是截图选项的总结:

  • 在启动时调整浏览器窗口的大小:
    $browser = $browserFactory->createBrowser([ 'windowSize' => [1920, 1000] ]);

     

  • 截图格式(默认为png):
    $screenshot = $page->screenshot([ 'format'  => 'jpeg' ]);

     

  • 图像质量:
    $screenshot = $page->screenshot([ 'quality' => 90 ]);

     

  • 裁剪截图区域:
    $clip = new Clip($x, $y, $width, $height); $screenshot = $page->screenshot([ 'clip'  => $clip ]);

     

  • 全页截图:
    $screenshot = $page->screenshot([ 'captureBeyondViewport' => true, 'clip' => $page->getFullPageClip() ]);

     

这些是PDF选项:

$options = [
    'landscape'           => true,             // default to false
    'printBackground'     => true,             // default to false
    'displayHeaderFooter' => true,             // default to false
    'preferCSSPageSize'   => true,             // default to false (reads parameters directly from @page)
    'marginTop'           => 0.0,              // defaults to ~0.4 (must be a float, value in inches)
    'marginBottom'        => 1.4,              // defaults to ~0.4 (must be a float, value in inches)
    'marginLeft'          => 5.0,              // defaults to ~0.4 (must be a float, value in inches)
    'marginRight'         => 1.0,              // defaults to ~0.4 (must be a float, value in inches)
    'paperWidth'          => 6.0,              // defaults to 8.5 (must be a float, value in inches)
    'paperHeight'         => 6.0,              // defaults to 8.5 (must be a float, value in inches)
    'headerTemplate'      => '<div>foo</div>', // valid HTML code
    'footerTemplate'      => '<div>foo</div>', // valid HTML code
    'scale'               => 1.2,              // defaults to 1.0 (must be a float)
];

2. 输出 / 保存到变量

另一种处理变量的方法是输出或保存它们。你可以使用方法来查询CSS选择器或xPath,然后获取该标签的文本内容。下面是一个示例:

//CSS selector
$el = $page->dom()->querySelector('pre');
//get the text from the element
$text = $el->getText();
//output the result
echo "Your IP Address is $text";

You can do the same with xPath:

$elem = $page->dom()->search('//div/*/a');

And even get attributes instead of the text contents:
$attr = $elem->getAttribute('class');

3. 执行JavaScript函数

你可以将PHP抓取与JavaScript函数结合使用。这非常方便!这是因为无头浏览器会执行一段JS代码,然后将结果返回,就像你在检查控制台日志一样。下面是一个简单的示例:

//connecting to a URL
page = $browser->createPage();
$page->navigate('https://en.wikipedia.org/wiki/Operation_Sandwedge')->waitForNavigation();
//run JS code to get a tag by ID
$evaluation = $page->evaluate('document.getElementById("firstHeading").textContent');
$value = $evaluation->getReturnValue();
// output it
echo $value;

这段代码加载了一个维基页面,然后返回一个ID的内容。如果你想预处理数据,可以使用类似的方法。

例如,你可以在页面上运行一段JS代码来获取价格列表中的最低价格。这可以节省后续的处理时间,并且如果价格高于某个阈值,你可以忽略该页面。

就是这样。通过这三个步骤,你基本上已经准备好设置一个无头浏览器,安全地连接到目标页面并提取数据。现在让我们来看一些额外内容。


如何与目标页面进行交互

有时候,仅仅加载一个页面不足以成功使用PHP抓取页面。也许你需要填写一个表单。有时你需要向下滚动以触发懒加载脚本。无论你需要什么,有一些Chrome PHP方法可以帮助你。

你可以使用精确的命令与鼠标进行交互:

$page->mouse()
    ->move(20, 40)    // Moves mouse to position x=20; y=40
    ->click()    // left-click on position set above
    ->click(['button' => Mouse::BUTTON_RIGHT]; // right-click on position set above
    ->scrollUp(100);    // scroll up 100px

或者你可以选择一个特定的元素并点击它:

$page->mouse()->find('#myID')->click(); // find and click on an element with id "myID"
$page->mouse()->find('.myclass', 5); // find and click on the 5th (or last) element with class "myclass"

也有使用键盘的方法:

$page->keyboard()
    ->typeRawKey('Tab') // type a key, such as Tab
    ->typeText('test');  // type the text "test"

你可以通过点击表单字段,然后输入:

$elem->click();
$elem->sendKeys('[email protected]');

常见问题解答 (FAQ)

致命错误:未捕获的 HeadlessChromium\Exception\OperationTimedOut

有时Chromium应用加载时间过长。如果每次执行抓取程序时都发生这种情况,尝试增加其时间限制。你可以在浏览器调用时这样设置:

$browser = $browserFactory->createBrowser([

'startupTimeout' => 60 // time in seconds

]);

 

致命错误:在 vendor\chrome-php\chrome\src\Browser\BrowserProcess.php 中的浏览器进程

这通常发生在应用未安装或你使用了不同的应用名称时。你可以在启动浏览器工厂时提供正确的应用名称来解决这个问题:

$browserFactory = new BrowserFactory( chromeBinary: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' );

未捕获的 RuntimeException:Chrome 进程在启动完成前停止

这可能是上述相同的问题。也许应用名称错误。此外,值得检查你当前的Chrome版本是否与Chrome PHP兼容。他们声称支持Chrome 65+。

致命错误:未找到 HeadlessChromium\BrowserFactory

不要忘记在项目中包含ChromePHP文件。你可以使用以下代码行来实现:

require_once 'vendor/autoload.php';

错误的对象类型或未捕获的错误:在空对象上调用成员函数 getText()

如果你看到关于对象类型的致命错误或奇怪的消息,检查网页抓取过程是否正常工作。并检查选择器是否返回有效元素。有时数据为空,这意味着在加载页面或元素时出现了问题。


结    论

现在,你学会了如何使用PHP进行网页抓取。你从选择合适的工具开始,逐步了解了复杂页面交互的过程。同时,你也学会了使用住宅代理的重要性,以避免被封锁。

到今天结束时,你应该能够使用PHP从任何网站提取数据。此外,由于PHP在Web开发中被广泛使用,你可以将这些数据拉取到自己的服务器上。你还可以通过将Node.js抓取工具与其他PHP库(如WordPress插件)结合使用,来增加趣味性。

希望你喜欢这次学习,我们下次再见!

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。