説明会の空席通知スクリプト書いてみた【リクナビ】

どうも。絶賛就職活動中の id:moqada です。

就活生らしく、せっせとリクナビで企業にエントリーしたり説明会の予約したりしてるわけなんだけど、最近はちょっと興味ある企業の説明会とか見てもほとんど満席で空席待ちなとこばっかり。そんでもって、リクナビは空席が出たら教えてくれたりとかしてくれない(たぶん)ので、毎度毎度いちいち確認、いちいちチェックせにゃならない。

これがものすご不便。そしてものすごめんどくさい。

なので、ちょっとその辺楽になるように、気になるとこの説明会に空席が出たり状態が変更されたりしたらメールで通知するスクリプト書いてみた。これを cron で回せば幸せになれるはず。

# -*- coding: utf-8 -*-
import re
import pickle
import urllib, urllib2, cookielib, smtplib
from datetime import date
from email.MIMEText import MIMEText
from email.utils import formatdate
from BeautifulSoup import BeautifulSoup

# -------------
# 設定事項(ここから)
gmail_addr = 'example@gmail.com'  # gmailアドレス
gmail_passwd = 'pass'  # gmailのpass
kaiin_id = '0000000000'  # リクナビのid
password = 'pass'  # リクナビのパスワード
seminars = [
    'http://2010.rikunabi.com/bin/KDBG03200.cgi?KOKYAKU_ID=0000000000&FORM_ID=C000',
    'http://rikunabi2010.yahoo.co.jp/bin/KDBG03200.cgi?KOKYAKU_ID=1111111111&FORM_ID=C001',
]  # 説明会予約ページのURL
places = ['東京都', '神奈川県', '埼玉県', '千葉県']  # 説明会の開催場所
stored_file = '/hoge/stored'  # 説明会情報の記録ファイル
# 設定事項(ここまで)
# -----------

rikunabi_url = 'http://2010.rikunabi.com'
login_url = 'http://2010.rikunabi.com/bin/NAVG25100.cgi'
bm_seminar_url = 'http://2010.rikunabi.com/bin/NAVG00700.cgi?&SELECT_SORT=&SELECT_ENTRY=&SELECT_EX=2'

regexp_id = re.compile('KOKYAKU_ID=(\d+)')
regexp_th = re.compile('gh_evt_col\d+')
regexp_none = re.compile(\
            '現在、このサービスは一時的にご利用いただけない状態です。')


# cookieハンドラを作ってとりあえずログイン
def login():
    postdata = {
        'KAIIN_ID': kaiin_id,
        'PASSWORD': password,
        'LOC_URL': '',
        'RLOGIN': 'ON',
        'TAB_ID': '0',
        'FROM_R': '0'
        }
    cj = cookielib.CookieJar()
    cjhdr = urllib2.HTTPCookieProcessor(cj)
    opener = urllib2.build_opener(cjhdr)
    opener.open(login_url, urllib.urlencode(postdata))
    return opener

# まだ予約してない説明会ページを取得
def get_unreserved_seminar(bm_url=bm_seminar_url, unreserved=seminars[:]):
    soup = BeautifulSoup(opener.open(bm_url).read())
    tds = soup.findAll('td', {'class': 'g_pl5 rn_cassete_table'})
    for td in tds:
        p = td.find('p', {'class': 'rnhn_p gh_large'})
        href = p.find('a')['href']
        if not href:
            break
        # 説明会が既に予約済みであればurl削除
        id = regexp_id.findall(href)[0]
        unreserved = [seminar_url for seminar_url in unreserved \
                     if not id in seminar_url]
    # 次のページのリンクを確認
    next_link = soup.find(text='次のページ').parent.get('href')
    if next_link:
        get_unreserved_seminar(rikunabi_url + next_link, unreserved)
    else:
        return unreserved

# seminar情報を取得
def get_schedule(seminars):
    head = {
        'gh_evt_col02': 'date',
        'gh_evt_col03 g_txt_C': 'place',
        'gh_evt_col04 g_txt_C': 'map',
        'gh_evt_col05 g_txt_C': 'state'
    }
    schedules = {}
    for url in seminars:
        soup = BeautifulSoup(opener.open(url).read())
        if soup.find(text='掲載終了') or soup.find(text=regexp_none):
            continue
        form = soup.find('form', {'class': 'rnhn_form'})
        if not form:
            continue
        th = form.findAll('th', {'class': regexp_th})
        if not th:
            continue
        company_name = soup.find('h1').find('a').string
        schedule = {}
        for i, data in enumerate(th):
            schedule[head.get(data['class'])] = data.string
            if (i + 1) % 4 == 0:
                if schedule['state'] == '受付終了' \
                    or schedule['date'] == u'\u2212':
                    schedule = {}
                    continue
                if date(*[int(d) for d in \
                        schedule['date'][:10].split('/')]) >= date.today() \
                    and schedule['state'] != '満席' \
                    and schedule['place'] in places:
                    schedule['name'] = company_name
                    schedules[(url, schedule.pop('date'))] = schedule
                schedule = {}
    return schedules

# 変更箇所を探す
def check_diff(schedules, stored_schedules):
    diff_schedules = {}
    for key in schedules:
        try:
            if schedules[key] != stored_schedules[key]:
                diff_schedules[key] = schedules[key]
        except:
            diff_schedules[key] = schedules[key]
    return diff_schedules

# セミナー情報の保存/読み出し
def load_dump_schedule(schedules):
    try:
        f = open(stored_file, 'r')
        stored = pickle.load(f)
        f.close()
    except:
        stored = dict([(key, {'name': '', 'state': '', 'place': ''}) \
                for key in schedules])
    f = open(stored_file, 'w')
    pickle.dump(schedules, f)
    f.close()
    return stored

# メール本文の生成
def create_message(schedules):
    body = \
    """%s
----------------------------------------

    [日時] %s
    [場所] %s
    [詳細] %s
    """
    bodies = [body  % \
             (schedules[(url, time)]['name'],
              time,
              schedules[(url, time)]['place'],
              url) \
                 for url, time in schedules]
    separater = '\n' * 2 + u'\u2501' * 30 + '\n' * 3
    return separater.join(bodies)

# gmail経由でメール送信
def send_via_gmail(message):
    msg = MIMEText(message)
    msg['Subject'] = '説明会日程通知 [beta]'
    msg['To'] = gmail_addr
    msg['From'] = gmail_addr
    msg['Date'] = formatdate()
    s = smtplib.SMTP('smtp.gmail.com', 587)
    s.ehlo()
    s.starttls()
    s.login(gmail_addr, gmail_passwd)
    s.sendmail(gmail_addr, [gmail_addr], msg.as_string())
    s.close()


if __name__ == '__main__':
    opener = login()
    seminars = get_unreserved_seminar()
    schedules = get_schedule(seminars)
    stored = load_dump_schedule(schedules)
    schedules = check_diff(schedules, stored)
    if schedules:
        send_via_gmail(create_message(schedules))


んーなんか冗長っつーか、これでいいのか感がものすごい。。そして汚い。。pickle ってのもなんとなく名前気に入ったんで使ってみたけど意味あったんかな。。
ささっと書けると思っとったけど、ものすごく時間かかったし。たったこんだけなのに。。おれダメだなーまじで。自分のコーディング能力の低下をまじまじと感じたわよ。。まあもともと全然低いんだけど、さらにね。。

でまぁこんなへぼコードだけど、いい機会なので今まで使えてなかった bitbucket の方に置くだけ置いてみることにした。
http://bitbucket.org/moqada/misc/src/tip/seminar_notification_rikunabi.py:title=
あぁほんともっとええコード書けるようになりてぇっす。



よし!就活、がんばらな!そしてプログラミングももっともっと精進せなな!