銀月の符号

Python 使い見習いの日記・雑記

Re:Loto6の最新結果をPythonで取得してみた

Loto6の最新結果をPythonで取得してみた - 牌語備忘録 -pygo』を見かけたので、 HTML の解析方法を正規表現 1 つだけとなるようにしてみた。

シンプルさはかえって失なわれている気がする。正規表現ができてしまえば、マッチオブジェクトの group メソッドできれいに処理できるのだが、要となる正規表現デバッグが難しい。

機能の追加は、「少し前の結果を得られる」、「抽せん日を得られる」、の 2 つ。

# coding: utf-8

u"""みずほ銀行のサイトから Loto6 の最新結果をゲット

作成
    fgshun (http://d.hatena.ne.jp/fgshun/)

オリジナル
    http://d.hatena.ne.jp/CortYuming/20091024/p1

機能追加
    一番新しいものだけでなく、少し前の結果も得られるようにした。
    また、抽せん日も得られるようにした。

さらなる改良案
    1〜6等の口数、当せん金額、販売実績額、キャリーオーバーなども
    得られるようにする。
    正規表現を増やしていてけばできるはず。

    iter_loto6_result などが返すオブジェクトは今のところ辞書となっている。
    このため、 result["numbers"] のようにアクセスしなければならない。
    これを result.numbers のように属性アクセスできるようにする。
    collections.namedtuple か独自のクラスを使うようにするとできるはず。
"""

import urllib
import re
import datetime

def get_loto6_html(reader=None):
    u"""Loto6 の最新結果を公開している HTML を得る"""

    if reader is None:
        reader = urllib.urlopen(
            u'http://www.takarakuji.mizuhobank.co.jp/miniloto/lt6-new.html')
    html = reader.read()
    return html.decode('sjis')

_loto6_html_parser = re.compile(ur"""
        <table .*? class="mB12\ number" .*? > .*?
            <th .*?> 回別 </th> .*?
            <th .*?> 第(?P<count>\d+)回 </th> .*?
            <th .*?> 抽せん日 </th> .*?
            <td .*?> (?P<year>\d+)年
                     (?P<month>\d+)月
                     (?P<date>\d+)日 </td> .*?
            <th .*?> 本数字 </th> .*?
            <td .*?> (?P<number_0>\d{2}) </td> .*?
            <td .*?> (?P<number_1>\d{2}) </td> .*?
            <td .*?> (?P<number_2>\d{2}) </td> .*?
            <td .*?> (?P<number_3>\d{2}) </td> .*?
            <td .*?> (?P<number_4>\d{2}) </td> .*?
            <td .*?> (?P<number_5>\d{2}) </td> .*?
            <th .*?> ボーナス数字 </th> .*?
            <td .*?> (?P<bonus>\d{2}) </td> .*?
        </table>""",
        re.DOTALL | re.VERBOSE)

def iter_loto6_result(html):
    u"""Loto6 の結果を HTML より読み出して一つずつ返す"""

    for mo in _loto6_html_parser.finditer(html):
        count = int(mo.group(u'count'))
        date = datetime.date(
                *(int(i) for i in mo.group(u'year', u'month', u'date')))
        numbers = tuple(
                int(i) for i in mo.group(*(u'number_%s' % i for i in range(6))))
        bonus_number = int(mo.group(u'bonus'))
        yield dict(
            count=count, date=date, numbers=numbers, bonus_number=bonus_number)

def get_loto6_result(html):
    u"""Loto6 の結果を HTML より読み出して返す
    
    結果は新しいものがより先頭にくるようにソートされている。"""

    return sorted(
            iter_loto6_result(html),
            key=lambda d: d[u'count'],
            reverse=True)

def get_loto6_latest_result(html):
    u"""Loto6 の最新結果を HTML より読み出して返す"""

    return max(
            iter_loto6_result(html),
            key=lambda d: d[u'count'])

def test():
    html = get_loto6_html()

    if True:
        result = get_loto6_latest_result(html)
        print u'第%d回' % result[u'count']
        print result[u'date']
        print u'本数字: %02d %02d %02d %02d %02d %02d' % result[u'numbers']
        print u'ボーナス数字: %02d' % result[u'bonus_number']

    if False:
        for result in get_loto6_result(html):
            print u'第%d回' % result[u'count']
            print result[u'date']
            print u'本数字: %02d %02d %02d %02d %02d %02d' % result[u'numbers']
            print u'ボーナス数字: %02d' % result[u'bonus_number']

if __name__ == '__main__':
    test()

実行結果。

第468回
2009-10-22
本数字: 09 17 26 30 34 40
ボーナス数字: 33

そういえば Loto6 のサイトを眺めていたら抽せん日や当せん金額といった記述を見かけた。抽選日、当選金額と書かないのにはなにか理由があるのだろうか?

1〜6等の口数、当せん金額、販売実績額、キャリーオーバーの取得対応

10月27日追記。1〜6等の口数、当せん金額、販売実績額、キャリーオーバーの取得もできるようにした。

10月27日22時修正。 1000 を 1,000 のように表示する moneyfmt 関数を追加。 http://www.python.jp/doc/release/lib/decimal-recipes.html の decimal.Decimal 用のものを int 用に直したもの。

# coding: utf-8

u"""みずほ銀行のサイトから Loto6 の最新結果をゲット

作成
    fgshun (http://d.hatena.ne.jp/fgshun/)

オリジナル
    http://d.hatena.ne.jp/CortYuming/20091024/p1

機能追加
    一番新しいものだけでなく、少し前の結果も得られるようにした。
    また、抽せん日、1〜6等の口数、当せん金額、販売実績額、
    キャリーオーバーも得られるようにした。


さらなる改良案
    iter_loto6_result などが返すオブジェクトは今のところ辞書となっている。
    このため、 result["numbers"] のようにアクセスしなければならない。
    これを result.numbers のように属性アクセスできるようにする。
    collections.namedtuple か独自のクラスを使うようにするとできるはず。
"""

import urllib
import re
import datetime

def moneyfmt(value, curr=u'', sep=u',',
             pos=u'', neg=u'-', trailneg=u''):
    u"""int を通貨表現の文字列に変換する

    curr:     符号の前に置く通貨記号: u'\\', u'$', u' ', u'' など
    sep:      桁のグループ化に使う記号: u',', u'' など
    pos:      正数の符号オプション: u'+', u'' など
    neg:      負数の符号オプション: u'-', u'(', など
    trailneg: 後置マイナス符号オプション:  ')', u'' など
    """

    sign = value < 0
    result = []
    digits = list(unicode(value))
    build, next = result.append, digits.pop
    if sign:
        build(trailneg)
    i = 0
    while digits:
        build(next())
        i += 1
        if i == 3 and digits:
            i = 0
            build(sep)
    build(curr)
    if sign:
        build(neg)
    else:
        build(pos)
    result.reverse()
    return u''.join(result)

def get_loto6_html(reader=None):
    u"""Loto6 の最新結果を公開している HTML を得る"""

    if reader is None:
        reader = urllib.urlopen(
            u'http://www.takarakuji.mizuhobank.co.jp/miniloto/lt6-new.html')
    html = reader.read()
    return html.decode('sjis')

_loto6_html_parser = re.compile(ur"""
        <table .*? class="mB12\ number" .*? > .*?
            <th .*?> 回別 </th> .*?
            <th .*?> 第(?P<count>\d+)回 </th> .*?
            <th .*?> 抽せん日 </th> .*?
            <td .*?> (?P<year>\d+)年
                     (?P<month>\d+)月
                     (?P<date>\d+)日 </td> .*?
            <th .*?> 本数字 </th> .*?
            <td .*?> (?P<number_0>\d{2}) </td> .*?
            <td .*?> (?P<number_1>\d{2}) </td> .*?
            <td .*?> (?P<number_2>\d{2}) </td> .*?
            <td .*?> (?P<number_3>\d{2}) </td> .*?
            <td .*?> (?P<number_4>\d{2}) </td> .*?
            <td .*?> (?P<number_5>\d{2}) </td> .*?
            <th .*?> ボーナス数字 </th> .*?
            <td .*?> (?P<bonus>\d{2}) </td> .*?
            <th .*?> 1等 </th> .*?
            <td .*?> (?P<items_1>[\d,]+) </td> .*?
            <td .*?> (?P<prize_1>[\d,]+) </td> .*?
            <th .*?> 2等 </th> .*?
            <td .*?> (?P<items_2>[\d,]+) </td> .*?
            <td .*?> (?P<prize_2>[\d,]+) </td> .*?
            <th .*?> 3等 </th> .*?
            <td .*?> (?P<items_3>[\d,]+) </td> .*?
            <td .*?> (?P<prize_3>[\d,]+) </td> .*?
            <th .*?> 4等 </th> .*?
            <td .*?> (?P<items_4>[\d,]+) </td> .*?
            <td .*?> (?P<prize_4>[\d,]+) </td> .*?
            <th .*?> 5等 </th> .*?
            <td .*?> (?P<items_5>[\d,]+) </td> .*?
            <td .*?> (?P<prize_5>[\d,]+) </td> .*?
            <th .*?> 販売実績額 </th> .*?
            <td .*?> (?P<total>[\d,]+) </td> .*?
            <th .*?> キャリーオーバー </th> .*?
            <td .*?> (?P<carry_over>[\d,]+) </td> .*?
        </table>""",
        re.DOTALL | re.VERBOSE)

def iter_loto6_result(html):
    u"""Loto6 の結果を HTML より読み出して一つずつ返す"""

    for mo in _loto6_html_parser.finditer(html):
        count = int(mo.group(u'count'))
        date = datetime.date(
                *(int(i) for i in mo.group(u'year', u'month', u'date')))
        numbers = tuple(
                int(i) for i in mo.group(*(u'number_%d' % i for i in range(6))))
        bonus_number = int(mo.group(u'bonus'))
        items = tuple(
                int(i.replace(u',', u'')) for i in mo.group(
                    *(u'items_%d' % i for i in range(1, 6))))
        prize = tuple(
                int(i.replace(u',', u'')) for i in mo.group(
                    *(u'prize_%d' % i for i in range(1, 6))))
        total = int(mo.group(u'total').replace(u',', u''))
        carry_over = int(mo.group(u'carry_over').replace(u',', u''))
        yield dict(
            count=count, date=date,
            numbers=numbers, bonus_number=bonus_number,
            items=items, prize=prize,
            total=total,
            carry_over=carry_over)

def get_loto6_result(html):
    u"""Loto6 の結果を HTML より読み出して返す
    
    結果は新しいものがより先頭にくるようにソートされている。"""

    return sorted(
            iter_loto6_result(html),
            key=lambda d: d[u'count'],
            reverse=True)

def get_loto6_latest_result(html):
    u"""Loto6 の最新結果を HTML より読み出して返す"""

    return max(
            iter_loto6_result(html),
            key=lambda d: d[u'count'])

def test():
    html = get_loto6_html()
    #html = get_loto6_html(open(u'temp.html', 'rb'))

    if True:
        r = [get_loto6_latest_result(html)]
    else:
        r = get_loto6_result(html)

    for result in r:
        print result[u'count']
        print result[u'date']
        print result[u'numbers']
        print result[u'bonus_number']
        print result[u'items']
        print result[u'prize']
        print result[u'total']
        print result[u'carry_over']
        print
        print u'第%d回' % result[u'count']
        print u'%d年%d月%d日' % (
                result[u'date'].year,
                result[u'date'].month,
                result[u'date'].day)
        print u'本数字: %02d %02d %02d %02d %02d %02d' % result[u'numbers']
        print u'ボーナス数字: %02d' % result[u'bonus_number']
        for i, (items, prize) in enumerate(
                zip(result[u'items'], result[u'prize']), 1):
            print u'%d等 %s口 %s 円' % (
                    i, moneyfmt(items), moneyfmt(prize))
        print u'販売実績額 %s円' % moneyfmt(result[u'total'])
        print u'キャリーオーバー %s円' % moneyfmt(result[u'carry_over'])
        print

if __name__ == '__main__':
    test()

実行結果

468
2009-10-22
(9, 17, 26, 30, 34, 40)
33
(6, 20, 781, 35937, 589401)
(96632000, 18813600, 578100, 11000, 1000)
4963540000
0

第468回
2009年10月22日
本数字: 09 17 26 30 34 40
ボーナス数字: 33
1等 6口 96,632,000 円
2等 20口 18,813,600 円
3等 781口 578,100 円
4等 35,937口 11,000 円
5等 589,401口 1,000 円
販売実績額 4,963,540,000円
キャリーオーバー 0円