PHP 爬取需要运行 JS 的页面 (Run JS While Grabing Web Page With PHP)

初衷#

近日在学习爬虫的时候遇到一个小问题,当在抓取某些网页的时候,在线测试通过的正则匹配在用 PHP 抓取时却发现只能抓取某些非关键元素。
经过排查,才发现在抓取该页面(是一个电商页面)时,该页面的详情页面是通过 JS 二次请求动态添加上去的,而 PHP (通过 curl 函数库的方式) 只是将其静态页面抓下,所以正则匹配的不是整个渲染好的完整页面,而是一个隐藏了详情板块的页面。

解决方案#

大致涉猎了一下,一般业界的解决方法有二:

  1. 分析 JS 文件,模拟 JS 中的请求
  2. 想方法运行 JS,抓取 JS 运行渲染完毕后的页面 (本文讲述的方法)

phantomjs#

file
phantomjs 基于 WebKit、开源的服务器端 JavaScript API, 采用了 WebKit 内核的 phantomjs 可以模拟浏览器运行网页,可以浅显的把它理解为除了把访问的页面显示出来。
除此之外,其他浏览器具备的功能它都有了 (DOM handling, CSS selector, JSON, Canvas, and SVG),所以可以通过调用它来运行含有 JS 文件且需要运行的 html 页面,当然它的用处肯定不止这些,web 测试,页面截图,网络监控等等 (详见官网文档)。

解决步骤#

Step.1 下载 (编译) phantomjs 文件#

这里有两种方式:
1. 直接从官网下载对应系统编译好的可执行文件,解压后移动到 bin 目录下即可

2. 从官方 Github 下载源码后编译为可执行文件。

我这里向大家介绍比较通用的法一:
如图在官网下载对应你服务器系统的版本,

file

以 CentOS 为例,下载 Linux 64-bit 版本 (32/64 区分好)

curl -O https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2

解压文件

tar xvf phantomjs-2.1.1-linux-x86_64.tar.bz2

移动文件到 bin 目录下

cp phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin

到这里就 phantomjs 的 install 过程已完成,不过最好测试一下能否成功运行。
随意写一个测试的 js 文件,运行下看是否成功

phantomjs helloworld.js

若是不成功,按提示安装缺失的 libraries 后再运行。
若还是不行,可以尝试用法二来获取 phantomjs 文件。

Step.2 通过 PHP 调用 phantomjs#

正常来说到这里的话,我们应该先用 PHP 获取到对应页面的 URL,然后用 phantomjs 执行后,获取返回的内容,再对其进行正则匹配 (替代了原来的 curl 操作)。
我在 Github 发现了有朋友已经封装了一个基于 PHP-phantomjs 的包,还写了非常健全的文档,已为他献上 Star。

file

由于文档是全英,我这里简单的介绍下关键步骤
1) 通过 Composer 安装

composer require "jonnyw/php-phantomjs:4.*"

2) 初始化 JonnyW\PhantomJs\Client 类

$client = Client::getInstance();
//这一步非常重要,务必跟服务器的phantomjs文件路径一致
$client->getEngine()->setPath('/usr/local/bin/phantomjs');

3) 简单的使用

$request  = $client->getMessageFactory()->createRequest();
$response = $client->getMessageFactory()->createResponse();

//设置请求方法
$request->setMethod('GET');
//设置请求连接
$request->setUrl($link);
//发送请求获取响应
$client->send($request, $response);

if($response->getStatus() === 200) {
    //输出抓取内容
    echo $response->getContent();
    //获取内容后的处理
}

4) 加载完整 JS 的用法

$client = Client::getInstance();
$client->isLazy(); // 让客户端等待所有资源加载完毕

$request = $client->getMessageFactory()->createRequest();
$request->setTimeout(5000); // 设置超时时间(超过这个时间停止加载并渲染输出画面)

......

总结#

最近在看《数学之美》的时候吴军博士在 “图论和网络爬虫” 一章中提过,如今的网页很多是用 Javascript 生成,在面对这些网页时,网络爬虫需要模拟浏览器去运行。
我也是在看完这一章后对这个点有所印象,这次遇到类似问题就朝这个方向去解决了。希望能给大家带来一点帮助和启发。

Contact me#

如果有什么错误或者建议 OR 如果需要请教关于本主题的相关问题
欢迎来邮与我交流和讨论!
Email:atrovervan@gmail.com
& My blog: ROVERVAN

本帖已被设为精华帖!
本帖由 Summer 于 8年前 加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

:thumbsup: 一直用 phantomjs 来抓取 js 渲染的网页 除了使用 PHP-phantomjs 包外 还可以借助 symfony/process 来运行命令行
需要性能效率的话 还能借助 server 模块 搭建 web 服务 开多进程 用 nginx 作负载均衡

8年前 评论

:thumbsup: 一直用 phantomjs 来抓取 js 渲染的网页 除了使用 PHP-phantomjs 包外 还可以借助 symfony/process 来运行命令行
需要性能效率的话 还能借助 server 模块 搭建 web 服务 开多进程 用 nginx 作负载均衡

8年前 评论
hainuo

想问一下为什么很少人用 v8js 扩展呢?

8年前 评论

@hainuo v8js 和浏览器的环境变量不同

8年前 评论

public function test(){
        $client = Client::getInstance();
        $client->getEngine()->setPath('/usr/local/bin/phantomjs');
        $request  = $client->getMessageFactory()->createRequest();
        $response = $client->getMessageFactory()->createResponse();

        $request->setMethod('GET');
        $request->setUrl('http://www.baidu.com');

        $client->send($request, $response);

        if($response->getStatus() === 200) {
            echo $response->getContent();
        }
    }

Error when executing PhantomJs procedure - Undefined variable: pipes
报这个错,不知道是什么原因?

7年前 评论

哎,都用 phantomjs 了,为啥不直接编写 js 脚本呢.

7年前 评论
chongyi

@Clarencep 是的,我觉得这样的话不如直接用 nodeJS 了。。。

7年前 评论