让人欢喜让我忧的phantomjs

大名鼎鼎的phantomjs,目前开源社区里唯一一个无界面浏览器(headless browser),其他的还可以搜到的一些类似项目要么是他的各种绑定版,要么也是基于QtWebkit,在功能上没什么区别。

<占位:这里应该列出类似的项目>

说是让人欢喜,因为曾经听说过它的人,都以为找到了万能法宝,可以用程序控制,可以抓取页面,提取信息,截图……无所不能。甚至接到相关需求的时候,就因为知道phantomjs的存在 还会莫名其妙的小激动(“无所不能”!)

可是,真正到项目中应用……

让我痛苦了两个多月的phantomjs,我来总结一些经验:

正文:

首先说明我对它的期望

像普通浏览器一样访问任意网页;
可以方便的操作dom结构;
可以检查任意域下的js运行环境;
可以截图;
可以查看所有的网络请求;
可以完全控制行为(打开和关闭页面,调整窗口大小,滚动条,鼠标,甚至修改请求内容);
还有,最好能方便与Nodejs配合;

以上可能代表大多数人对phantomjs的第一印象,但是……

1. 基本实现,但 不支持flash,某些网页莫名其妙加载失败(目前没找到规律)
2. 只能通过page.evaluate()的方式在页面中执行一段js,然后返回结果<有坑>
3. 只能用switchToFrame()切换到指定frame中,用page.evalute()实现
4. 比较完美,二进制截图只能输出到本地文件,或者Base64字符串;只能整页输出,类似于打印,如果网页有浮窗,取不到分屏的效果;
5. 只能查看请求头信息,看不到response.body,也不能修改请求(受制于QtWebkit的Api)
6. 基本可用,但Api功能有限
7. 十分不方便

有力吐槽:

1. 运行不稳定,错误提示模糊:

碰到的第一个问题,莫名其妙的崩溃,错误提示像精神病一样不知道在说什么

2. webpage提供Api不够丰富,十分不方便:

因为他也只是QtWebkit的一个封装(半成品浏览器),我们为phantomjs写的程序,面对的是一堆page对象(类似于Chrome中标签的概念),page内部的BOM和DOM都无法直接在phantomjs程序中操作,只能page.evaluate()返回一个结果。

从page环境中返回信息只有两种途径:
page.evaluate():  只能同步返回数据,如果执行过程涉及异步操作,就无力了;
onCallback/onConsoleMessage: page环境中顶层window上有个callPhantom()可以触发这个onCallback,但是只有top上才有,iframe中没有

3. 在phantomjs中,没有一个root超级权限,跨越所有的的域限制。只能switchToChild/MainFrame()的方式切换到对应的iframe环境下再执行evaluate()。最让人无奈的是这几个方法接收的参数都是QtWebkit内部对frame的编号(可能是按照构建frame实例的顺序编号)跟Dom中的顺序不一致,所以需要费一点力气找到dom元素跟frame的对应关系;

需求是这样的:如果在某个frame中找到了我想要的dom节点,我想要知道这个节点在页面上的坐标……只能offsetTop/Left沿Dom树向上计算。

4. 虽然截图功能稳定,速度也不错……主要有两个问题:

除了可以选择输出格式和图片质量外,再没有其他控制选项。他是整页的(类似打印的)排版,如果有浮层,我们无法获得某一屏的截图状况(比如我想知道浮层广告在第一屏时是否遮住了主要内容)。
page.render(“test.jpg”, { format: “jpg”, quality: 50 });

有时,某些区块死活渲染不出来,不管setTimeout等多长时间,那一块都是空白的,至今没找到规律。

5. 请求,貌似只是简单的封装了QtWebkit接口,格式不标准,需要手动处理成har。网上有外国人搞过获取请求内容的功能,用的是cache文件,不仅复杂,而且可靠性让人担心。

6. 总结起来,phantomjs提供了哪些控制(除这以外的不要报太大希望):

新建一个页面;
访问任意url,刷新,甚至是直接setContent()输入一段html;
Base64截图,或直接输出到本地磁盘文件;
在任意frame环境下执行一段js;
注入一个js文件,page.includeJs/injectJs (没尝试不知道有没有坑)
可以动态改变窗口尺寸;

7. 与Nodejs配合(输入输出)只有两种方案:标准输入输出、Websocket

phantomjs的输入输出全都是同步的(不像Nodejs那样事件驱动),如果想要通过stdin接受Nodejs发来的指令,只能setTimeout轮询。然后在输出上做个简单的消息分隔协议(我用的是一个蹩脚的multipart解析器)

继续

8. page.onLoadFinish 这个callback是碰到第一个抓狂的问题,基本没规律,有时候有,有时候没有,有时候还来两次, 像女生那啥一样……无语

所以,我们必须有:去重逻辑、超时逻辑;而且这个回调传回来的参数只有两个值 ‘success’ ‘fail’,更奇葩的是:只有程序内部错误导致无法加载页面的时候才会返回 fail ,不管网页是404、502、只要加载完了一律success!

另外一个问题,这个“事件”触发只是html文件加载完成,不是真正的onload,如果我想要一个整页的截图,必须等到所有资源“看似”加载完成,这需要我们自己实现。我的做法是,在onLoadFinish触发之后开始关注网络请求,200ms之内不在产生新的请求就认为加载完成。(因为一般的交互动画的间隔时间都比这个时间要长)实验之后效果还不错。

无力吐槽:

(至今没找到解决办法)

9. 有些网页就是加载不出来,甚至等上一分钟

暂时找到这些网站无法加载
sports.xinmin.cn
www.jisutiyu.com
www.du00.com
www.ahzb.cn
www.bandog.cn
pic.daqi.com
www.smartshe.com
www.diezhanmi.com
bbs.tiexue.net
tom.ent.ynet.com
tom.life.ynet.com
bbs.ld0766.com
www.7060.com
www.gaoxiaobar.com
gaoxiaobar.com
adk.funshion.com
www.zhaoxiaoshuo.com
www.kkkmh.com
www.ymt360.com
tom.ent.ilive.cn
bbs.voc.com.cn
www.zhijinwang.com
www.qnvod.net
www.d5yuansu.com
help.3g.163.com

posted in 未分类 by deemstone

Follow comments via the RSS Feed | 留下评论 | Trackback URL

2 Comments to "让人欢喜让我忧的phantomjs"

  1. JS跨域抓取HTML页面并解析 | segment-解决方案 wrote:

    [...] 关于phantomjs的使用,我刚刚写了一篇吐槽《让人欢喜让我忧的phantomjs》http://blog.cooer.net/2014/让人欢喜让我忧的phantomjs/ [...]

  2. 【技术分享】浅谈动态爬虫与去重 - 莹莹之色 wrote:

    [...] 让人欢喜让我忧的phantomjs  [...]

Leave Your Comment

You must be logged in to post a comment.

 
Powered by Wordpress. Design by Bingo - The Web Design Experts.