cloverrose's blog

Python, Machine learning, Emacs, CI/CD, Webアプリなど

PyQt4でJavaScriptでレンダリングしてるWebページをScraping! CookieとUserAgentも扱うよ

JavaScriptレンダリングしてるWebページをScrapingしたい!!!

自分の環境

  • Virtualbox (Host OS Windows 7 64bit)
  • Guest OS Ubuntu16.04.1 LTS Desktop 64bit
  • Python 2.7.12
  • PyQt4は sudo apt-get install python-qt4 でインストール

実現方法

調べたら素晴らしいBlogが見つかった。

Ultimate guide for scraping JavaScript rendered web pages | IMPYTHONIST

BlogではPyQt4のWebKitを使ってJavaScript部分もレンダリングしたHTMLを取得する方法を紹介している。

(HTMLのParseに関する話もあるが、HTMLさえ取得できればBeautifulSoup使えばいい)

このBlogはコメント欄も有用な情報がたくさんあって、同じことをする別のライブラリやプロジェクトを示唆している人もいた。

ただやりたいことに対して複雑なライブラリを使いたくなかったので、このBlogの方法で実現してみた。

Blogの通りに動かすとちゃんと http://pycoders.com/archive/スクレイピングできた!!

タスク

だいたいやりたいことの大枠はBlogのソースコードで実現できた。

あとは細かい部分を詰めていくだけ。やりたいところはこの2つ

  • Cookieを扱いたい
  • UserAgentを扱いたい

Cookieを扱いたい

Blogのコメント欄にもあるけどQNetworkCookieJarを使えばいける

実現方法の詳細はこのSOがとても参考になった。

python - Permanent cookies with QWebKit -- where to get the QNetworkAccessManager? - Stack Overflow

手順

  1. EditThisCookie などを使ってCookie情報をJSONファイルに保存しておく
  2. QNetworkCookieを作る
  3. QNetworkCookieのListを作る
  4. QNetworkCookieJarを作る
  5. QWebPageにQNetworkCookieJarをセット .networkAccessManager().setCookieJar(cookiejar)

関連クラスの仕様

UserAgentを扱いたい

このSOが参考になった。

python - Setting useragent in QWebView - Stack Overflow

自分のUserAgentはここでわかるっぽい

手順

  1. What's My User Agent? - User Agent & Browser Tools などでCookieを調べる
  2. .userAgentForUrl に このCookieを返す関数をセットする

最終結果

# -*- coding:utf-8 -*-
import datetime
import json
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtWebKit import *
from PyQt4.QtNetwork import *


# 偽装したいUserAgent
USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36"
COOKIE_FILE = "cookie.json"


def customuseragent(url):
    u"""
    どのurlでも同じUserAgentを返す
    """
    return USER_AGENT


def make_cookiejar(filename):
    u"""
    JSON形式でExportされたCookieを読み込みQNetworkCookieJarを作る
    """
    with open(filename) as f:
        data = json.load(f)

    cookies = []
    for item in data:
        cookie = QNetworkCookie(item['name'], item['value'])
        if 'domain' in item:
            cookie.setDomain(item['domain'])
        if 'expirationDate' in item:
            cookie.setExpirationDate(datetime.datetime.fromtimestamp(item['expirationDate']))
        if 'hostOnly' in item:
            cookie.setHttpOnly(item['hostOnly'])
        if 'path' in item:
            cookie.setPath(item['path'])
        if 'secure' in item:
            cookie.setSecure(item['secure'])
        cookies.append(cookie)

    cookiejar = QNetworkCookieJar()
    cookiejar.setAllCookies(cookies)
    return cookiejar


#Take this class for granted.Just use result of rendering.
class Render(QWebPage):
    def __init__(self, url):
        self.app = QApplication(sys.argv)
        QWebPage.__init__(self)
        self.loadFinished.connect(self._loadFinished)

        self.userAgentForUrl = customuseragent
        cookiejar = make_cookiejar(COOKIE_FILE)
        self.networkAccessManager().setCookieJar(cookiejar)

        self.mainFrame().load(QUrl(url))
        self.app.exec_()

    def _loadFinished(self, result):
        self.frame = self.mainFrame()
        self.app.quit()


def main(url, filename):
    r = Render(url)
    result = r.frame.toHtml().toUtf8()
    with open(filename, 'w') as f:
        f.write(result)


if __name__ == '__main__':
    URL = 'http://pycoders.com/archive/'
    FILENAME = 'result.html'
    main(URL, FILENAME)

補足

Qtの独自の型があるけど

  • QByteArrayってなってるところはPythonのstr型でOK
  • QDateTimeってなってるところはPythonのDatetime型でOK
  • frame.toHtml()の結果をtoUtf8()してあげないと日本語が文字化けで?になってしまう

感想

3年前くらいにMacPyQt動かそうとしてメッチャ苦労したけどUbuntuでは超簡単に動いた。

わりとすぐにいいBlogにたどり着くことができてよかった。

PyQtのリファレンス体裁が汚すぎる…