Phantomjsとは、Headless browserといって、画面を表示しないブラウザを裏で持ち、そことのやりとりをJavaScriptで行うというコンセプトのツールになります。
モチベーション
Webスクレイパーを作ろうと思ったのですが、最近のサイトはJavaScriptで動的にコンテンツを生成するのが主流になっています。
今まで、NokogiriやMechanizeを使ってスクレイピングしていたのですが、静的にHTMLを解析するアプローチでは、難しいシーンが増えています。
このままでは限界だろうということで、Phantomjsを利用することにしました。
Cookieが動かない!
色々試してみて、すぐにCookieの動作が思うようにならないことに気が付きました。
色々調べてもわからなかったので、StackOverflowに質問したのが、こちらです。
ちなみに現時点で解答0です。^^;
しかたがないのでソースコードを読むことに
StackOverflow(ja)にも、回答来ないし、外から色々試してみるのにも限界を感じたので、直接ソースコードを漁り始めます。
GitHub - ariya/phantomjs: Scriptable Headless WebKit
てっきり、JavaScriptで書かれているのかと思っていたのですが、C++で書かれていてちょっとビビります。(^^;
最初の取っ付きにくさに戸惑いましたが、マニュアルと突き合わせて読んでいくと、だんだん意味がわかってくるで不思議です。
Qt
読んでいると、Qxxxというヘッダーをやたらと使っていることに気が付きます。
軽くググると、Qtを使っていることが判明。
知らなかったのですが、Qtには、QtWebkitというWebkitをラップしたAPIが提供されているようです。
今度は、QtのリファレンスとPhantomjsのソースコードを行ったり来たりしながら、実際に動かしてみて確認するという作業が始まります。
この辺、非常に辛かったです。(^^;
悟る
それで、あちこち見ているうちに、ようやく尻尾を捕まえて、どんな仕組みで動いているのかを理解することができました。
まずは、下記の部分からです。
m_page->mainFrame()->addToJavaScriptWindowObject("phantom", this);
https://github.com/ariya/phantomjs/blob/master/src/phantom.cpp#L482
addToJavaScriptWindowObjectというメンバ関数ですが、mainFrame()が返す、QWebFrame型のクラスにJavaScriptのObjectとしてthisを注入しています。
QWebFrameは、ブラウザのページ内のメインフレームを示すオブジェクトです。要するに現在表示しているWebページを指します。
そこにthisこと、Phantomクラスを注入しているわけです。
class Phantom : public QObject
addToJavaScriptWindowObjectで突っ込めるクラスは、QObjectを継承している必要があります。
こうすることで、Phantomjsに渡すスクリプト内からは、「phantom」という変数にアクセスすることができるようになります。
注入したオブジェクトのメンバ関数をJavaScriptないの関数として呼び出すためには、下記のように「slots:」キーワードの後にメソッドを並べれば良いです。
public slots: QObject* createCookieJar(const QString& filePath); QObject* createWebPage(); QObject* createWebServer(); ...
phantomjs/phantom.h at master · ariya/phantomjs · GitHub
これで、Phantom変数経由で、C++側のAPIにアクセスできるようになりました。
その他のAPIは、だいたいJavaScriptで実装されているみたいなので、そんなに苦労せずに解析可能です。
Phantomjs自体は、普通にmain.cppにあるmain関数から起動されて、replを立ち上げる仕組みになっていました。
Cookieの挙動は?
結局、色々解析した結果、「Phantomjsには、リクエストするURLに合わせて、Cookieをヘッダーにセットしてくれる機能は無い」という結論に至りました。
サンプルコードやテストコードでは、できそうな感じのことが書いてあるのに!
とほほ。