Hatena::ブログ(Diary)

XXXannex このページをアンテナに追加 RSSフィード

2017-11-25

Firefox webdriverでcookieが読めなくなった話

取得したクッキーを保存して読み直すだけのコードである

from selenium import webdriver
import json

driver = webdriver.Firefox()
#driver = webdriver.Chrome()
#driver = webdriver.PhantomJS()

try:
    driver.get("http://www.google.com")

    fw = open('cookies.json','w')
    json.dump(driver.get_cookies(),fw,indent=4)
    fw.close()
    
    f = open("cookies.json")
    cookies = json.load(f)
    f.close()

    for cookie in cookies:
        driver.add_cookie(cookie)

finally:
    driver.quit()

最近Firefoxバージョンを最新(57)に上げたら、上のコードエラーになった。

$ python test.py
Traceback (most recent call last):
  File "test.py", line 24, in <module>
    driver.add_cookie(cookie)
  File "/usr/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 760, in add_cookie
    self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict})
  File "/usr/lib/python3.6/site-packages/selenium/webdriver/remote/webdriver.py", line 308, in execute
    self.error_handler.check_response(response)
  File "/usr/lib/python3.6/site-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: .google.co.jp

よく分からん

ドメインの先頭にドットがついてるとエラーになる(InvalidCookieDomainException)という話もあるみたいだけど、generalなWebDriverExceptionとしか出てこないので、やっぱりよく分からん

先頭のドットを消すと確かにExceptionを出さずに動くようになるのだが、そうすると目的プログラムcookieを期待通りに読んでくれなくなって詰む。

仕方ないのでChromeを使うか・・・と思うと、今度はswitch_to.windowでactiveにしたウインドウ(タブ)は必ずフォアグラウンドになってしまうというアレな仕様で詰む。(参考:ウインドウの最小化 - XXXannex

Seleniumを使うと、だいたいあちらを立てればこちらが立たぬ・・・みたいなことが多くてウンザリする。

iMacrosを自動起動する方向に変換した方がいいのかもしれない。まあ、あちらはあちらでwindows nativeなことができなくて困りそうだけど。

ま、いいや。とりあえず適当に53(53.0.3)くらいまで戻して事なきを得た。二度とアップグレードしないぞ。

追記

自動更新をOFFにし忘れたせいでまた57にアップグレードされてしまった。切れそう。

2017-11-10

○○ソートみたいなやつ

ソートファクトリー

こういうの。

use strict;
use warnings;
use Path::Class;

my @list  = file('list.txt')->slurp(chomp=>1);
my $COUNT = 0;
print "$_\n" foreach sort { compare($a, $b) } @list;

sub compare{
  my($lhs, $rhs) = @_;
  printf("#%02d: which do you like?\n", ++$COUNT);
  print "1. $lhs\n";
  print "2. $rhs\n";
  print "3. Even\n";
  my $input = <>; $input =~ tr/\x0A\x0D//d;
  if($input == 1){
    return -1;
  }
  elsif($input == 2){
    return 1;
  }
  elsif($input == 3){
    return 0;
  }
  else{
    die;
  }
}

2017-11-08

ウインドウの最小化

ルームを沢山開いてウインドウを放っておくと、全ルームの音がなって大変なことになるし、ブラウザがクソ重くなって大変なことになる。

ルームを開く前にウインドウを最小化しておけば大丈夫なので、実行時はブラウザを最小化しておきたい。

しかし、これが意外とできない。マジか。最大化はアッサリできるのに。

どうやらSeleniumAPIでは無理らしいので、キーボードショートカット(Alt+Space→n)を送るという原始的手段で実現するらしい。

  1. ActionChainsを使う
  2. win32comモジュールを使う
  3. ctypesを使ってwin32 dllインポートする
  4. JavascriptからActiveXオブジェクトを使う

調べてみると、こんな感じの情報が色々出てくるが、どうやらCygwin+Python+Seleniumだと全滅のようで。素直にWindowsPythonを使っておけば良かったか・・・

PerlだとCygin用でもWin32::GuiTestみたいなモジュールが使えるんですけどね・・・

ので、苦肉の策として外部プログラムを実行することにした。

os.system('cscript close_window.vbs')
Option Explicit
On Error Resume Next

Dim objWshShell
Set objWshShell = WScript.CreateObject("WScript.Shell")
WScript.Sleep(2000)
objWshShell.SendKeys "% "
WScript.Sleep(500)
objWshShell.SendKeys "n"

Set objWshShell = Nothing

os.systemよりもsubprocess.callを使おう、みたいな情報が山ほど出てくるけど、さらにsubprocessの使い方を調べるの面倒くさいし動くからいいでしょ。(散々調べて全然できなかったので、ここに辿り着くまでに心が折れた)

なんかPythonを使うたびに「え、こんなこともできないの」みたいなことが多くてしんどい

これなら最初からJavaで書けばよかったかもしれない。。

ま、ともかく必要機能は無事に実装できた。後は細々とした使い勝手の調整とかリファクタリングとかをしていけばいいかなあ。

星投げと50カウント自動化できれば便利なんだろうけど、そこまではいいかな。ツールもあるし手動でも何とかなるし。

そもそも、一番大きな動機が「配信が始まる前に★を貯めておくのを忘れる」「捨て★の時間を忘れる」といったものなので、始まってから自動化はそこまで必要としてないのだ。

参考

Can Selenium Webdriver open browser windows silently in background? - Stack Overflow

ルームが配信終了してるチェック

特定の要素が「ない」ことで判断すると何らかの処理中で要素がない場合誤検知をしてしまうので、配信終了の時に「ある」要素を条件にしようと思った。

他にもあると思うけど、今回は配信開始時間をチェックすることにした。

ここのスタイル配信時には「display:inline」に、終了時には「display:none」になるようなので、それを使ってみた。

ついでに、配信終了しているルームは閉じて新しいルームを開く処理を追加した。

★集めの時に配信が終わってしまうのを見越してルームを多めに開いたりしていたけど、これで開いてるルーム数をキープできるようになったので便利。

ポイントとしては、ルームを開く前にonliveのページ(下のコードで言うとwindow_handles[0])に戻る必要がある。戻らないと当然だけどエラーになる。

room_start_time = driver.find_element_by_id('label-start-time')

if re.match(r"display *: *none", room_start_time.get_attribute("style")):
	print("This room is now offline. Closing a driver..")
	driver.close()
	win_ptr += 1
	if(win_ptr < len(official_rooms)):
		driver.switch_to.window(driver.window_handles[0])
		official_rooms[win_ptr].send_keys(Keys.CONTROL + Keys.RETURN)
	break

ほんとは

if(++win_ptr < len(official_rooms)):

と書きたいんですけどね。何でPythonインクリメント演算子がないの??

2017-11-07

オフィシャルルームかどうかの判定

from pprint import pprint
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException

def is_official(x):
	try:
		r = x.find_element_by_css_selector("span.icon-official")

	except NoSuchElementException:
		return False

	else:
		return True

driver = webdriver.Firefox()
driver.get('https://sr/onlive')
rooms = driver.find_elements_by_css_selector("ul.js-roomlist-list li")

# official room
pprint([x.find_element_by_css_selector("a.js-room-link").get_attribute("href") for x in rooms if is_official(x)])

# unofficial(amature) room
pprint([x.find_element_by_css_selector("a.js-room-link").get_attribute("href") for x in rooms if not is_official(x)])

一見するとアマチュアルームユーザー名が16進でオフィシャルルーム名前付きのような感じだけど、実は必ずしもそうでないらしい。仕組みがよく分からん

ちゃんと判別しようとすると、オフィシャルアイコンが付いてるかどうかで判断することになる。

要素がない時はnullを返してくれればいいのに、何か例外を投げるみたいなので例外を潰してTrue/Falseを返す関数を作った。

find_element_by_* はdriverだけじゃなくてelementからも呼べるみたいなので、ざっくりとliタグを取ってきてオフィシャルアイコンが含まれているリストだけからリンク(a)を取ってくればよい。

それにしてもPythonリスト内包表記めっちゃ便利ね。

とにかく使いにくいという感想しか無かったけど、これだけは本当に便利。Perlでいうmapgrepが一度にできる。

2017-11-05

★ in Selenium2 + Firefox + Python

import os
import time
import re
import pickle
from pprint import pprint
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.keys import Keys

sdatetime = datetime.now()
stime     = time.time()

driver = webdriver.Firefox()
driver.get('https://SR/')

cookies = pickle.load(open(os.path.dirname(os.path.abspath(__file__)) + "/cookies.pkl", "rb"))
pprint(cookies)
for cookie in cookies:
    driver.add_cookie(cookie)

driver.get('https://SR/onlive')
time.sleep(5)

rooms = driver.find_elements_by_xpath("//a[@class='js-room-link listcard-join-btn']")
pprint([x.get_attribute('href') for x in rooms])

official_rooms = [x for x in rooms if not re.match(r".+/[0-9a-f]+$", x.get_attribute('href'))]
pprint([x.get_attribute('href') for x in official_rooms])

MAX_OPEN = 25
windows = min(MAX_OPEN, len(official_rooms))
for i in range(windows):
	official_rooms[i].send_keys(Keys.CONTROL + Keys.RETURN)
#	driver.execute_script("window.open('%s');" % official_rooms[i])

s2datetime = datetime.now()
s2time     = time.time()

break_loop = False
for var in range(120):
	time.sleep(10)
	for i in range(len(driver.window_handles)-1):
		driver.switch_to.window(driver.window_handles[i+1])
		print(datetime.now(), end=" ")
		stars = driver.find_elements_by_css_selector('div.gift-free-num-label')
		cnt   = 0;
		for s in stars:
			n = re.search(r'\d+', s.text)
			if n:
				num = n.group(0)
#				if num == '99':
# 					cnt+=1
				print(num, end=" ")
			else:
				break
		print()
		if cnt == 5:
			break_loop = True
			break

	if break_loop:
		break

	driver.switch_to.window(driver.window_handles[0])

edatetime = datetime.now()
etime     = time.time()

print("Script start time     : ", sdatetime)
print("Collecting start time : ", s2datetime)
print("Collecting end time   : ", edatetime)
print("Elapsed               : ", etime-s2time)
driver.quit()

ハマったところとか色々

Selenium歴史も長そうだし枯れてるんだと思っていたけどクセがすごいし、Pythonは初めて使ってみたけどPerlに比べて面倒なことが多いし、微妙なかんじ。慣れですかね。

Python自体の話

Perlと比べた場合、Rに関しては明確なメリットがあったけど、Pythonは手間のかかるPerlという感じで今いちメリットが感じられなかったなあ。慣れですかね。あるいは必要ライブラリーPython用にしか提供されてないとか。

Seleniumの話

ブラウザ(Firefox/Chrome)の拡張と同じレベルのことを外部からできるものだと想像していたけど、予想以上に機能制限されてた。

ブラウザ操作という点では拡張スクリプトが最強だけど、sandboxでの実行になってしまったり、自動実行という点では今ひとつだったり、コードコールバック地獄になったり、それぞれメリットデメリットがあるみたい。

Seleniumで実行したブラウザから自作拡張を実行する、みたいなことが出来たらいいとこ取りなんだけどなあ。できてもよさそうなモノだけど。

TBD
いろいろ

Does python have a "use strict;" and "use warnings;" like in perl? - Stack Overflow

Python変数必要になった時点で割り当てるのでPerlにおけるstrict的な静的チェックはできない、ということか。

2番めのコメントにあるように、変なtypoで実行結果がおかしくなったり、途中まで処理が進んでからエラーで落ちたり、strict的なものがないと不便だと思うんだよなあ。そしてこの手のバグは非常に見つけづらい・・・

そんな変なコードを書くな?はい