Google Cloud FunctionsでSeleniumが使えなかったのでPhantomJSで代用した

Google Cloud Functionsを使って、スクレイピングを自動化したい

この記事に書いてあるとおり、過去にお名前VPSを使ってFXの自動取引を行なっていました。

関連記事

全ての取引を自動化 前々からFXを自動取引してみたいな~という気持ちはありつつ、 そもそもEA(エキスパートアドバイザー)を作るハードルや、金銭的な余裕がなかったので手は出していませんでした。 普通にEAを買おうとすると安くて[…]

FXに全てを任せる記録

で、もうFXの自動取引はやっていないわけですが、TwitterのBotとか、仮想通貨取引のレート取得のために、そのVPSを使ってスクレイピングを定期実行していました。

そんなしょうもないことに毎月1,300円払うのも馬鹿らしくなったので、思い切ってお名前VPSを解約して、少しずつGoogle Cloud Functions(以下GCF)に移行してました。

GCFでの定期実行に関しては参考になる記事がたくさんあったので、つまずき続けながらも、なんとか前に進むことができました。

以下、参考になった記事たち。

幾多ものつまずきの中で2つ紹介します。

つまずきポイント①:引数の指定

import os

def Hoge(event, context):  #ココ
    fuga = 1
にしかた
いきなりつまずいたぜ
上述した参考記事は全部、
def Hoge():

となっていて、引数がなにも設定されていませんでした

でも仕様変更?があったのか、引数にevent, contextが設定されていないとダメっぽい。細かい仕様は分からんのですが。(分かる人いたら教えて)

ちなみに誤ってeventではなくdataという引数を設定してしまったんですが、きちんと動作したので別に引数の指定はなんでもいいっぽいです。

つまずきポイント②:BigQuery・スプレッドシートとの連携

BigQueryにデータを保存して、そのデータを元にGoogle Data Studioでビジュアライズすることを目指していました。

GCFからBigQueryの書き込みは割と簡単で、pandasでできます。

df.to_gbq(table_id, project_id,if_exists='append')

で、加えてスプレッドシートにも書き込みをしようと思ったのですが、その際の認証周りがよく分からず。

お名前VPSで動かしていたときはjsonのキーファイルを読み込んで認証していたのですが、そのjsonキーファイルをGCFでどう読み込めば良いのか分からなかった。

Cloud Storageにいれても上手くパスを読み込めないし、いろいろな記事を読んでも「認証をウンタラカンタラ」ばかりで、その「認証をウンタラカンタラ」ができませんでした。

最終的に海外の記事で、とんでもない方法でやっていた人がいたので、そちらを参考にしました。

具体的には、こんな感じ。

def bq_insert(event, context):
  #ここはBigqueryへの書き込み
	df = pandas.DataFrame(data=df_list, columns=columns)
	project_id = 'PROJECT-ID'
	table_id = 'TABLE-ID'
	df.to_gbq(table_id, project_id,if_exists='append')

  #ここからスプレッドシートへの書き込み
	scopes = [
	    'https://www.googleapis.com/auth/spreadsheets',
	    'https://www.googleapis.com/auth/drive'
	]

	json_data = {
	'type': 'service_account",
	'project_id': "hogehoge",
	'private_key_id': "fugafufa",
	'private_key': '-----BEGIN PRIVATE KEY-----hogehogehoge,
	'client_id': 'CLIENT-ID',
	'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
	'token_uri': 'https://oauth2.googleapis.com/token',
	'auth_provider_x509_cert_url': "https://www.googleapis.com/oauth2/v1/certs',
	'client_x509_cert_url': "https://www.googleapis.com/robot/v1/metadata/abababababab'
	}

	credentials = ServiceAccountCredentials.from_json_keyfile_dict(json_data, scopes)
	gc = gspread.authorize(credentials)

JSONデータを、直接コードに書き込んじゃうという荒業w

セキュリティ的な問題とかあるのかな?そのへんはよく分からんけども。

Google Cloud FunctionsでSeleniumを動かしたい

で、本題です。

にしかた
本題まで長えな

Selenium + WebDriverの実装で詰んだ

基本スクレイピングはBeautifulSoupを使って行なっていたんですが、Javascriptでレンダリングされている動的ページはBeautifulSoupではソースを上手く引っ張ってくることができず、SeleniumでWebDriverを動かさないといけなかったわけです。
で、さっきのJSON問題と同じように、どうWebDriverをGCFで読み込むかに悩んだわけです。
JSONファイルと同じようにStorageにあげても、ZIPアップロードしても上手く読み込めず、「Message: ‘chromedriver’ executable needs to be in PATH.」というエラーメッセージに悩まされました。
ちなみに参考にした記事は以下の通り。
ほかにもたくさん。国内・海外の記事も読み漁ったけど答えを見つけることができず、もう手詰まりって感じでした。
上述の記事たちも、
def Hoge():
def Fuga(request):
と、引数の指定が今の仕様に合っていない?気がして、内容が古いものなんじゃないかと思います。
GitHubに「gcf-packs」というものも上がっているんですが、これも試してみたけどダメ。
本当に手詰まり。めちゃくちゃテンション下がってました。で、見つけたのがPhantomJSCloud。

PhantomJSCloudで、Seleniumの代用をする

SeleniumでもPhantomJSは動かせますが、PhantomJSCloudはリクエストしたURLをPhantomJSCloudがスクレイピングして値を返してくれるものです。

なので問題だったドライバーをインストールが、PhantomJSCloudでは必要がありませんでした!!

にしかた
できたときはマジで歓喜。
実装も簡単で、Seleniumで取得していた部分を書き換えるだけ。
payload = {'url':'https://google.com/','renderType':'HTML','outputAsJson':'true'}
payload = json.dumps(payload)
payload = urllib.parse.quote(payload, safe = '')

phantomjs_key = 'API-KEY'
url = 'https://phantomjscloud.com/api/browser/v2/' + phantomjs_key + '/?request=' + payload

pj_res = requests.get(url)
pj_res = pj_res.json()
html = pj_res["content"]["data"]

soup = BeautifulSoup(html, 'html.parser')
サイト内のデータ取得はBeautifulSoupの方が慣れているので、最後にhtmlデータを渡してます。
APIは毎日500リクエストなら無料なので、基本はBeautifulSoupで、Javascriptの動的ページのみPhantomJSCloudを使っています。
実装できてマジハッピーです。

まとめ

Google Cloud FunctionsでSelenium使うより、PhantomJSCloud使ったほうが良さそう。

GCF×Seleniumで無駄にした時間=5時間くらい

PhantomJSCloudに切り替えて実装した時間=10分

にしかた
効率、効率ゥ!