将棋ウォーズの棋譜をまとめてローカルに保存する方法

続編は以下の記事。

teriyaki398.hatenablog.com

後から調べたら...

すでに有識者がフリーウェアを開発してくださっていたので、
Mac でもやりたいよという人限定の話になってしまった

将棋ウォーズ棋譜ダウンローダー Wiki - OSDN

成果物

棋譜をダウンロードするPython スクリプトを作成し、一括DLできた!

f:id:teriyaki398:20180928100838j:plain

環境

Webスクレイピングの準備

selenium

今回は 将棋ウォーズ棋譜検索β で検索した結果を一括でダウンロードする方針でいく

このようにWebサイトから情報を一括でダウンロードすることを Webスクレイピングと呼び、
プログラムからブラウザを操作したいときには、selenium というソフトが使われる

今回はPython から操作したいので 以下のコマンドでパッケージを入れておく

% pip install selenium

ブラウザ

今回は Google Chrome をヘッドレスモードで使う。

ヘッドレスとは、実際にブラウザを起動して画面を描画したりしないモードと捉えておけば問題ないだろう

webスクレイピングは大量のデータにアクセスしたいので、いちいち画面を描画させると重くなってしまうのだ
(有名なヘッドレスブラウザとしてPhantomJS があったが、開発が終了してしまった)

selenium からブラウザを操作するためには、操作したいブラウザに対応するドライバが必要なので、以下からダウンロードしておく

Downloads - ChromeDriver - WebDriver for Chrome

具体的なコード

from selenium import webdriver
from bs4 import BeautifulSoup
import requests
from tqdm import tqdm
from time import sleep

# 操作するブラウザの準備
option = webdriver.ChromeOptions()
option.add_argument('--headless')
driver = webdriver.Chrome(executable_path='./chromedriver', options=option)

url = 'http://swks.sakura.ne.jp/wars/kifusearch/'
u_name = '取得したいユーザ名'

sleep(1)
driver.get(url)

driver.find_element_by_id('id_name1').send_keys(u_name)
driver.find_element_by_id('searchBtn').click()

# ページのソースを取得
soup = BeautifulSoup(driver.page_source, 'lxml')
driver.quit()

lis = soup.find_all(class_='btn1')
kif = []

for i in lis:
    if 'kif' in i.get('href'):
        kif.append(i.get('href'))

kif = ['http://swks.sakura.ne.jp/wars' + x[2:] for x in kif]

for i in tqdm(kif):
    sleep(1)
    r = requests.get(i)
    r.encoding = 'Shift_JIS'

    f_name = i.split('/')[-1]
    # ユーザ名のディレクトリ以下にファイルを保存するようにしている
    # あらかじめディレクトリを作成しておく
    with open(u_name + '/' + f_name, mode='w') as f:
        f.write(r.text)

流れは以下の通り

  1. selenium + Chrome で ID を検索する
  2. 検索結果のソースを取得
  3. 保存ボタンに棋譜のリンクが貼ってあるので、それを取得してリスト化
  4. GETリクエストを送り、棋譜を保存

Tips

実際のwebページがどのように記述されているかによってコードが変わるので、
Chrome の場合)右クリック -> 検証 からコードを表示しながらプログラムすることになる

今回の場合、検索ボックスに 属性id_name1、検索ボタンに属性searchBtn が付与されていたので、 それらを検索し、文字列を入力 > 検索 という流れで結果を取得した

また、今回は 10分切れ負けに絞ったが、もし他の対局ルールでの棋譜が欲しい場合は
ラジオボタンを選択する処理が必要となる

取得した棋譜ファイルを活用して、例えば 四間飛車 vs 居飛車穴熊 だけ抽出
といったことができるだろう

raspberry piの初期設定

とりあえず自分が行った設定のメモ

基本的にSSH接続して、操作することを想定している

環境

Raspbian 起動以前

ラズパイの標準OSであるRaspbianをインストールする

イメージファイルを用意する

公式サイト
Raspberry Pi Downloads - Software for the Raspberry Pi

過去のバージョン
Index of /raspbian/images

SDカードをフォーマットする

windows10なら右クリック -> フォーマットでできる
専用のソフト(SDFormatterなど)を使ったらもっと分かりやすいかもしれない

Mac OSならディスクユーティリティを使って可能
Raspberry Pi 3にRaspbianをインストール(Mac OS X を使用) - Qiita

OSをSDカードに書き込む

Windows

Win32 Disk Imager が簡単か
Win32 Disk Imager download | SourceForge.net

Mac OS

Raspberry Pi 3 初期セットアップ(Mac 用) - Qiita

*df -hでSDカードを確認(仮に/dev/disk2s1とする)

% sudo diskutil unmount /dev/disk2
% sudo dd bs=1m if=2017-01-11-raspbian-jessie-lite.zip of=/dev/rdisk2
% sudo diskutil eject /dev/disk2

Raspbian で直接やること

キーボード、マウスなしで最初からやれるらしいが、持ってるならそれでやって問題ない

Raspberry Pi モニタなしキーボードなしマウスなし パソコンのみでRaspberry Piのデスクトップにアクセスする。 - Qiita

インターネットに接続する

raspberry pi 3はwifiが使えるので楽ですね

ファイルシステムの最適化

大きな容量のSDカードを入れても正しく認識してくれなかったりするらしい
新しいバージョンだと大丈夫なこともあるらしいが、一応確認した方がいいかも

% df -h

を実行し、/dev/rootのサイズを確認する

このサイズがSDカードの容量とほぼ一致していればOK

もし小さく表示されているようなら以下を実行

% sudo raspi-config

から Expand Filesystemを選択

raspi-configからやること

  • Change Password
  • Interfacing Options - SSH

ここまでやったらあとはリモートから操作可能。

SSHでログインする

% ssh -p [ポート番号] pi@[IPアドレス]

アカウント名を変えている場合はpiの部分を変更する

ファイルの転送を行いたい場合は

% scp -P [ポート番号] [送りたいファイル] pi@[IPアドレス]:[送りたい場所(/home/pi/...)]

SSHのポート番号を変更する

% sudo apt-get install vim
% cd /etc/ssh
% sudo vim sshd_config

ここでPort 22コメントアウトし、他の番号に変更しておく

色々更新する

% sudo apt-get update
% sudo apt-get upgrade
% sudo apt-get dist-upgrade # ディストリビューションを最新に
% sudo rpi-update # ファームウェアを最新に

Tpis

.bashrc

.bashrcファイルをカスタマイズすると快適になる。

alias ls='ls -F --color=auto'
alias ll='ls -l -1'

を追加しておいた

友人の特定ツイートにクソリプを自動的に送りたい

(Python 2.7.13)

Twitter APIを使ってみたという記事は山ほどあるので、今更という感じだが

成果物

特定のユーザが"二郎"という単語を含んだツイートをすると、自動的に二郎に誘うリプを送る
(二郎:一部の人が大好きな高カロリーラーメン)

f:id:teriyaki398:20181001141813j:plain

Twitter API の準備

これも多くの人が解説してくれてるので、そちらを参照しよう

Twitterアプリケーションの作成(Consumer key、Consumer secret、Access token、Access token secretの確認方法)

Twitter REST APIの使い方

以下の4つの情報が取得できればOK * Consumer Key (API Key) * Consumer Secret (API Secret) * Access Token * Access Token Secret

Python ライブラリ

わざわざライブラリを使うまでもないとは思うが、圧倒的に楽なので

様々なライブラリが存在するが、今回はこちらを用いる

GitHub - sixohsix/twitter: Python Twitter API

以下のコマンドでインストールする

% pip install twitter

基本的には以下のような形で用いる

各認証情報は先ほど取得したものに置き換える

また、タイムラインなどは辞書型で取得される

from twitter import *

# 認証情報
CONSUMER_KEY        = "Consumer Key"
CONSUMER_SECRET     = "Consumer Secret"
ACCESS_TOKEN        = "Access Token"
ACCESS_TOKEN_SECRET = "Access Token Secret"

twitter = Twitter( auth=OAuth(ACCESS_TOKEN, ACCESS_TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET) )

home_timeline = twitter.statuses.home_timeline()    # ホームのタイムラインを取得(デフォルトは20件)
user_timeline = twitter.statuses.user_timeline(screen_name="@xxxx")    # 指定したユーザのタイムラインを取得
twitter.statuses.update(status="hello")    # "hello" とつぶやく

指定できるパラメータの情報などはこの辺りで確認しよう

POST statuses/update — Twitter Developers

特定のツイートに対してリプをする

各ツイートにはidが設定されており、ツイートの際にこのidを指定することで、特定のツイートにリプが返せる

ユーザ@xxxx のツイートid yyyyにリプするときは以下のようにする

status_id = "yyyy"
twitter.statuses.update(status="@xxxx hello", in_reply_to_status_id=status_id)

基本的な流れは、

  1. ユーザのタイムラインを取得
  2. 取得したツイートのテキストにキーワードが含まれているかを判定
  3. リプ済IDリストを確認し、まだリプしていないならクソリプし、IDをリストに追加
  4. 1分ほど待って、1.に戻る

これを繰り返すことで、自動的にチェックしてリプする

同じツイートにリプを返さないように、一度リプしたものは除外する処理が必要

# coding:utf-8

from twitter import *
import time

# 認証情報
CONSUMER_KEY        = "Consumer Key"
CONSUMER_SECRET     = "Consumer Secret"
ACCESS_TOKEN        = "Access Token"
ACCESS_TOKEN_SECRET = "Access Token Secret"

SCREEN_NAME = "@xxxx"


twitter = Twitter(auth=OAuth(ACCESS_TOKEN, ACCESS_TOKEN_SECRET, CONSUMER_KEY, CONSUMER_SECRET))
status_id_list = [] # 同じツイートにリプしないように、リプしたidを記録しておく

while True:
    # secreen_nameのユーザのタイムラインを取得
    tweet_list = twitter.statuses.user_timeline(screen_name=SCREEN_NAME)

    # 二郎に関するツイートをしていた場合、クソリプを送る
    for tweet in tweet_list:
        if tweet["text"].find(u"二郎") != -1:    # 探せなかった場合は、-1が帰ってくる
        
            status_id = str(tweet["id"])

            # まだリプしていないツイートがあったらリプする
            if status_id not in status_id_list:
                twitter.statuses.update(status="だったら、二郎行こうぜ", in_reply_to_status_id=status_id)
                status_id_list.append(status_id)

                if len(status_id_list) >= 20:  # 20以上保持する必要はないので、必要に応じて減らす
                    status_id_list = status_id_list[1:]
    time.sleep(60)

プログラムを動かしっぱなしにすることを想定しているため、timeで制御している

cronなどで定期的にプログラムを実行させる方法もある

(こちらの場合は、IDリストを外部ファイルに書き出す必要がある)