AIっておもしろい?

離職者等再就職訓練でAIを勉強するブログです

なんとなくWebスクレイピング

今日の授業は、ほぼ復習でした。

「リスト」「タプル」「ディクショナリ」「セット」
このあたりの再読と確認テスト。

意外と紙に書くタイプのテストだと、
スペルミスとかありえるので、どきどき。

解答例にない書き方をして、一時はNGになったものの
実際にコーディングしたら動いたので、OKにしたり、
なんか学生みたい(笑

最後の一時間で、「関数」に突入。
pythonの関数って、引数と戻り値が自由すぎる!
どこかでJavaと比較したものを整理したいです。
どこかで、ね。

totoスクレイピング

さて、ここからは前回の続き。

JRAに挫折し、totoのサイトをスクレイピングできないかチャレンジします。

https://store.toto-dream.com/dcs/subos/screen/pi04/spin011/PGSPIN01101InitLotResultLsttoto.form


試合結果のページを眺めながら、目標設定をします。

目標
『回数を指定し、対象のページから結果をスクレイピング
 CSVファイルに出力する』

教材っぽい目標ですね。
学習観点でいうと、学びポイントは以下。
・HTML文字列の取得
・パーサークラスを使って、対象データを抽出
・複数ページを走査
CSVファイルにデータ出力

ソースの紹介

とりあえず出来上がったコードは以下。
※行数が表示できると見やすいですね。
 近日、方法を探してみます。

## totoデータ取得 ##
## 清書版 ##

import csv
import urllib.request
from html.parser import HTMLParser

# パーサークラス
class local_HTMLParser(HTMLParser):
    # 初期化メソッド
    def __init__(self):
        HTMLParser.__init__(self)

        # クラス内変数の初期化
        self.table = False
        self.title = False
        self.tr = False
        self.table_cnt = 0
        self.row = []
        self.rows = []

    # 開始タグ取得メソッド
    def handle_starttag(self, tag, attrs):
        # table
        if ( tag == "table" ) and ( len(attrs) == 5 ) and  ( attrs[4][1] == "adjustment" ) :
            self.table_cnt += 1

        if self.table_cnt == 1 :
            self.table = True
        else :
            self.table = False

        # tr
        if ( self.table == True ) and ( tag == "tr" ) :
            if ( len(attrs) == 1 ) and ( attrs[0][1] == "backgray" ) :
                self.tr = True
            else :
                self.tr == False

    # データ取得メソッド
    def handle_data(self, data):
        # タイトル取得
        if self.table == True and self.title == False:
            # toto が実施された回だけデータ取得
            if "回 toto くじ結果" in data :
                self.title = data[1:5]

        # データ取得
        elif self.table == True and self.title != "" and self.tr == True :
            # toto は1回13試合を予想
            ROW_NUM = 13
            # カラム数
            COLMUN_NUM = 7
            
            # データを格納
            dat = data.strip()
            if len(self.rows) < ROW_NUM and dat != "" :
                if len(self.row) < COLMUN_NUM :
                    self.row.append(dat)
                else :
                    self.row.insert(0, self.title)
                    self.rows.append(self.row)
                    self.row = []
                    self.row.append(dat)
                    
## メイン 処理 ## 
# 入力
while True :
    start_time = int(input("取得開始回を入力してください(第260~):"))
    end_time = int(input("取得終了回を入力してください:"))

    if start_time <= end_time :
        break
    else :
        print("入力が不正です。\n開始 <= 終了 で入力してください。\n")

# スクレイピング
for time in range(int(start_time), int(end_time)+1) : 
    # HTML読込
    url = "https://store.toto-dream.com/dcs/subos/screen/pi04/spin011/PGSPIN01101LnkHoldCntLotResultLsttoto.form?holdCntId=" + str(time)
    page = urllib.request.urlopen(url)
    html = page.read()
    page_str = html.decode()

    # HTML解析
    p = local_HTMLParser()
    p.feed(page_str)
 
    # CSVファイル出力
    for csv_row in p.rows :
        if len(p.rows) > 0 :
            with open("C:/WORK/test_data/" + "toto_" + str(start_time) + "-" + str(end_time) + ".csv", "a", newline ="", encoding='utf-8_sig') as f:
                writer = csv.writer(f, lineterminator='\n') # 行末は改行
                writer.writerow(csv_row)

    p.close()
 
# 終了時処理
print("\n処理が終了しました")

解説というか、備忘というか

・HTML文字列の取得

 →# HTML読込
 urlを文字列にして、
 上のほうで import した、 urllib.request の urlopen() でpageを開きます。
 read() で読んだ html をdecode() するのですが、
 ここで、文字コードに注意が必要です。
 JRAのサイトをスクレイピングしたとき、ここで文字コードエラーが発生し、
  page_str = html.decode('CP932')
 としました。
 元のHTMLが s-jis で書かれているってことなんですかね?ワカッテナイ
 

・パーサークラスを使って、対象データを抽出

 →# HTML解析
 ローカルクラス local_HTMLParser を生成して、
 page_str を食わせます。
 local_HTMLParser では以下の処理をしています。
  __init__(self)
   : local_HTMLParser 内で使用する変数を定義しておきます。
    ここで定義することで、クラス内でグローバルに使えます。
  handle_starttag(self, tag, attrs)
   : パラメータの tag と atters からタグと属性を取得、判定し、
    解析を開始、終了をフラグで管理します。
    ちなみに、何とか動いてますが、この部分のアルゴリズムが汚い。。。
  handle_data(self, data)
   : 同一のサイトで「toto」「mini toto A組」「mini toto B組」「totoGoal3」の記述があるため、
     「toto」のみを取得するために、タイトルを判定しています。
     「toto」のデータ(テーブル)構成は行が13(試合)カラムは7つです。
     data.strip() と dat != "" でごみを取り除き、二次元リストに格納します。

・複数ページを走査

 →# スクレイピング
  urlの末尾が「回」の番号となっているので、
  for 文でループさせることで、複数ページを走査します。
  なお、開始回と終了回は input で任意に指定できるようにしました。
  

CSVファイルにデータ出力

 →# CSVファイル出力
 解析した p から、二次元リストを取得し、行単位でループします。
 ファイルを開いて、import した csv モジュールで行単位にCSV形式に変換し、writer で書き込みます。
 Javaと比べると、CSV形式に変換してくれたり、PrintWriter を作る必要がなかったり、
 楽です。

最後に close しているのは、ファイルではなくて、解析したHTMLです。
複数ページを走査するので、ここで close しておく必要があります。

次回予告

授業は淡々と進んでいるため、
ブログに書くネタには乏しいのが悩みです。
面白いことをかけるように、努力します。