読者です 読者をやめる 読者になる 読者になる

セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

マーブルワーズ

Phantomjsソースを読んでQtWebkitのAPIについて調べた

Phantomjsとは、Headless browserといって、画面を表示しないブラウザを裏で持ち、そことのやりとりをJavaScriptで行うというコンセプトのツールになります。

モチベーション

Webスクレイパーを作ろうと思ったのですが、最近のサイトはJavaScriptで動的にコンテンツを生成するのが主流になっています。

今まで、NokogiriやMechanizeを使ってスクレイピングしていたのですが、静的にHTMLを解析するアプローチでは、難しいシーンが増えています。

このままでは限界だろうということで、Phantomjsを利用することにしました。

Cookieが動かない!

色々試してみて、すぐにCookieの動作が思うようにならないことに気が付きました。

色々調べてもわからなかったので、StackOverflowに質問したのが、こちらです。

ja.stackoverflow.com

ちなみに現時点で解答0です。^^;

しかたがないのでソースコードを読むことに

StackOverflow(ja)にも、回答来ないし、外から色々試してみるのにも限界を感じたので、直接ソースコードを漁り始めます。

GitHub - ariya/phantomjs: Scriptable Headless WebKit

てっきり、JavaScriptで書かれているのかと思っていたのですが、C++で書かれていてちょっとビビります。(^^;

最初の取っ付きにくさに戸惑いましたが、マニュアルと突き合わせて読んでいくと、だんだん意味がわかってくるで不思議です。

Qt

読んでいると、Qxxxというヘッダーをやたらと使っていることに気が付きます。

軽くググると、Qtを使っていることが判明。

知らなかったのですが、Qtには、QtWebkitというWebkitをラップしたAPIが提供されているようです。

WebKit in Qt | Qt 4.8

今度は、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をヘッダーにセットしてくれる機能は無い」という結論に至りました。

サンプルコードやテストコードでは、できそうな感じのことが書いてあるのに!

とほほ。