高级技巧

这一部分提供了使用 hfut 的一些高级技巧.

注解

例子仅作为学习的参考或者为你的使用提供思路, 我只能确保在自己的环境下能够正常工作. 如果你想提供修改意见或者添加例子可以 在 GitHub 上提交 Issue 或者直接提交 Pull Request.

使用 hfut 下载全校学生证件照

# -*- coding:utf-8 -*-
"""
抓取全校学生的照片
"""
from __future__ import unicode_literals

import logging
import os
import sys
import threading

import requests
import six

from hfut import Guest, XC, HF
from hfut.util import cal_term_code

# 文件保存路径
DIR_NAME = 'img'
# 起始年份
START_YEAR = 2012
# 结束年份
END_YEAR = 2015

# 校区
campus = XC

# 所有人都上的课程 # 军事训练
if campus == HF:
    COURSE_CODE = '52000020'
else:
    COURSE_CODE = '5200023B'

# 设置日志
logger = logging.Logger('hfut_img', level=logging.WARNING)
sh = logging.StreamHandler()
fh = logging.FileHandler('hfut_img.log', encoding='utf-8')
fmt = logging.Formatter('%(threadName)s %(levelname)s %(lineno)s行: - %(asctime)s\n\t %(message)s', '%d %H:%M')
sh.setFormatter(fmt)
fh.setFormatter(fmt)
logger.addHandler(sh)
logger.addHandler(fh)
logger.setLevel(logging.INFO)

# 初始化 session
shortcuts = Guest(campus)


# 初始化文件夹
def setup_dir():
    if not os.path.isdir(DIR_NAME):
        os.mkdir(DIR_NAME)
        logger.info('成功创建目录 {}'.format(DIR_NAME))
    for i in range(START_YEAR, END_YEAR + 1):
        path = os.path.join(DIR_NAME, six.text_type(i))
        if not os.path.isdir(path):
            os.mkdir(path)
            logger.info('成功创建目录 {}'.format(path))


# 下载照片
def fetch_img(term_code):
    file_suffix = '.jpg'
    stu_sum = 0
    success_sum = 0
    fail_sum = 0
    error_sum = 0
    exist_sum = 0
    # 获取该学期的所有教学班
    klass = shortcuts.search_course(term_code, COURSE_CODE)
    if klass:
        logger.info('{} 学期共有 {} 个教学班'.format(term_code, len(klass)))
        for k in klass:
            # 获取教学班学生
            class_stus = shortcuts.get_class_students(term_code, COURSE_CODE, k['教学班号'])
            if class_stus is None:
                logger.critical('没有获取到 {} 学期的教学班'.format(term_code))
                sys.exit(0)
            stu_num = len(class_stus['学生'])
            logger.info('{} 班共有 {} 名学生'.format(class_stus['班级名称'], stu_num))
            stu_sum += stu_num
            for stu in class_stus['学生']:
                year = str(stu['学号'] // 1000000)
                code = str(stu['学号'])
                img_url = six.moves.urllib.parse.urljoin(shortcuts.session.host, ''.join(
                    ['student/photo/', year, '/', code, file_suffix]))
                sex = '男'
                stu_name = stu['姓名']
                if stu['姓名'].endswith('*'):
                    sex = '女'
                    stu_name = stu_name[:-1]
                full_name = ''.join([code, '-', sex, '-', stu_name])
                filename = os.path.join(DIR_NAME, year, ''.join([full_name, file_suffix]))
                if os.path.isfile(filename):
                    logger.warning('{} 的照片已下载过'.format(full_name))
                    exist_sum += 1
                    continue
                try:
                    res = requests.get(img_url)
                    if res.status_code == 200:
                        with open(filename, 'wb') as fp:
                            fp.write(res.content)
                        logger.info('下载 {} 的照片成功'.format(full_name))
                        success_sum += 1
                    elif res.status_code == 404:
                        logger.warning('下载 {} 的照片失败'.format(full_name))
                        fail_sum += 1
                except Exception as e:
                    logger.error('下载 {} 的照片出错\n\t{}'.format(full_name, e))
                    error_sum += 1
        logger.info('{} 学期共有 {} 名学生'.format(term_code, stu_sum))
        logger.info('{} 学期下载完成,成功 {},失败 {},错误 {}, 已存在 {}'.format(
            term_code, success_sum, fail_sum, error_sum, exist_sum))
    else:
        logger.critical('没有获取到第 {} 的教学班级'.format(term_code))
        sys.exit(0)


if __name__ == '__main__':
    setup_dir()
    for year in range(START_YEAR, END_YEAR + 1):
        term_code = cal_term_code(year)
        t = threading.Thread(target=fetch_img, name=year, args=(term_code,))
        t.start()

在全平台使用 hfut 编写课表工具

_images/web_curriculum.png
# -*- coding:utf-8 -*-
# qpy:webapp
# qpy://127.0.0.1:8080
"""
使用 bottle 编写的一个简单课表查看页面, 可以筛选每周的课程, 可以在手机上安装 qpython 并安装好依赖后在手机上运行
"""
from bottle import Bottle, template

import hfut

index_tpl = """
<!DOCTYPE html>
<html lang="cn">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="initial-scale=1.0">
    <title>第{{week}}周课表</title>
    <style>
        table{margin: 0 auto;border-collapse:collapse;}
        table, th, td{border: 1px solid;}
        thead{background-color: #EFC363;}
        tbody{background-color: #D6D3CE;}
        caption{margin-bottom:0.5em;}
        .index-td{text-align:center}
    </style>
</head>
<body onload="document.getElementById('opt_' + '{{week}}').selected = true">
<table>
    <caption>
        <select id="week" onchange="window.location=document.getElementById('week').value">
            %for i in range(start, end+1):
                <option id="opt_{{i}}" value="{{i}}">
                    第{{i}}周
                </option>
            %end
        </select>
    </caption>
    <thead>
        <tr>
            <th></th>
            <th>周一</th>
            <th>周二</th>
            <th>周三</th>
            <th>周四</th>
            <th>周五</th>
            <th>周六</th>
            <th>周日</th>
        </tr>
    </thead>
    <tbody>
        %for i in range(11):
            <tr>
                <td class="index-td">{{i+1}}</td>
                %for j in range(7):
                    %if curriculum[j][i]:
                        <td>
                            %for c in curriculum[j][i]:
                                {{c[u'课程名称']}}:{{c[u'课程地点']}}/
                            %end
                        </td>
                    %else:
                        <td></td>
                    %end
                %end
            </tr>
        %end
    </tbody>
</table>
</body>
</html>
"""
app = Bottle()
session = hfut.Student('你的学号', '密码', '校区')
curriculum = session.get_my_curriculum()
start = curriculum[u'起始周']
end = curriculum[u'结束周']
filtered = [None] * (end - start + 1)


@app.route('/')
@app.route('/<week:int>')
def index(week=1):
    idx = week - 1
    filtered[idx] = filtered[idx] or hfut.util.filter_curriculum(curriculum[u'课表'], week)
    return template(index_tpl, curriculum=filtered[idx], week=week, start=start, end=end)


if __name__ == '__main__':
    app.run(host='localhost', port=8080, debug=True)

将课表导出为日历文件

_images/calendar.png
# -*- coding:utf-8 -*-
"""
将课表导出为日历文件
"""
from __future__ import unicode_literals

from datetime import datetime

import icalendar

import hfut


def schedule2calendar(schedule, name='课表', using_todo=True):
    """
    将上课时间表转换为 icalendar

    :param schedule: 上课时间表
    :param name: 日历名称
    :param using_todo: 使用 ``icalendar.Todo`` 而不是 ``icalendar.Event`` 作为活动类
    :return: icalendar.Calendar()
    """
    # https://zh.wikipedia.org/wiki/ICalendar
    # http://icalendar.readthedocs.io/en/latest
    # https://tools.ietf.org/html/rfc5545
    cal = icalendar.Calendar()
    cal.add('X-WR-TIMEZONE', 'Asia/Shanghai')
    cal.add('X-WR-CALNAME', name)
    cls = icalendar.Todo if using_todo else icalendar.Event
    for week, start, end, data in schedule:
        # "事件"组件更具通用性, Google 日历不支持"待办"组件
        item = cls(
            SUMMARY='第{:02d}周-{}'.format(week, data),
            DTSTART=icalendar.vDatetime(start),
            DTEND=icalendar.vDatetime(end),
            DESCRIPTION='起始于 {}, 结束于 {}'.format(start.strftime('%H:%M'), end.strftime('%H:%M'))
        )
        now = datetime.now()
        # 这个状态"事件"组件是没有的, 对于待办列表类应用有作用
        # https://tools.ietf.org/html/rfc5545#section-3.2.12
        if using_todo:
            if start < now < end:
                item.add('STATUS', 'IN-PROCESS')
            elif now > end:
                item.add('STATUS', 'COMPLETED')
        cal.add_component(item)
    return cal


if __name__ == '__main__':
    session = hfut.Student('你的学号', '密码', '校区')
    curriculum = session.get_my_curriculum()
    first_day = datetime(2016, 8, 29)
    schedule = hfut.util.curriculum2schedule(curriculum['课表'], first_day, compress=True)
    print(len(hfut.util.curriculum2schedule(curriculum['课表'], first_day)), len(schedule))
    cal = schedule2calendar(schedule)
    with open('curriculum.ics', 'wb') as fp:
        fp.write(cal.to_ical())