运用selenium+bs4实现课程表的爬取

 

锵锵✨

锵锵✨

为了解决每天忘记课程而需要不停进入教务系统查课表的烦恼(不是

刚刚学了一点点爬虫的x73决定写一个爬虫项目来爬取课表信息用来导入本地课表软件!

准备工作

用到的模块

模块 版本
selenium >= 4.0
webdriver-manager 3.8.4
bs4 4.11.1
re all

用到的工具

PyCharm、 Edge

tips:Edge 为 selenium所需的浏览器,需保证最新版本,可以是其他浏览器,但要保证代码中调整为相应的驱动

开始!!

provider – 获取课表页面源代码

step1. 获取登录状态cookie

课表界面都没看到,怎么获取嘛

我们想要见到我们亲爱的课表同学,就先要保证我们处于登录状态,这样我们才能顺利的见到课表~

所以我们要先通过selenium模拟登录行为,获取到登录状态下的cookie。

def get_cookie(url):
    option = webdriver.EdgeOptions()
    option.add_argument('headless')
    option.add_argument('disable-gpu')
    service = EdgeService(executable_path=EdgeChromiumDriverManager().install())
    driver = webdriver.Edge(service=service, options=option)

    driver.get(url)
    driver.find_element(By.ID, 'username').send_keys('******') # your username/id
    driver.find_element(By.ID, 'passWord').send_keys('******') # your password
    driver.find_element(By.ID, 'loginButton').click()
    return driver.get_cookies()

小插曲

这里浅浅的讲一下 selenium 的使用方法~

step0. 设置隐藏浏览器,即使用无头模式进行,可以酌情删掉哦(不是

option = webdriver.EdgeOptions()
option.add_argument('headless')
option.add_argument('disable-gpu')

step1. 使用 install() 获取管理器使用的位置, 并将其传递到服务类中

service = EdgeService(executable_path=EdgeChromiumDriverManager().install())

step2. 使用 Service 实例并初始化驱动程序

driver = webdriver.Edge(service=service, options=option)

tips: 详情参考 Install browser drivers - Selenium

step2. 启动 Edge-Driver

接下来就可以启动 新的 edgeDriver, 步骤和之前一样哦~

def html_provider():
    print('[INFO] Get URL and Cookie')
    url = "http://jwglxt.hncj.edu.cn:8061/admin/indexMain/M140202"  # 课表页面url
    url_login = "http://jwglxt.hncj.edu.cn:8061/admin/login"  # 登录界面url
    cookies = get_cookie(url_login)

    print('[INFO] Start Edge Driver')
    option = webdriver.EdgeOptions()
    option.add_argument('headless')
    option.add_argument('disable-gpu')
    service = EdgeService(executable_path=EdgeChromiumDriverManager().install())
    driver = webdriver.Edge(service=service, options=option)

step3. 添加Cookie

添加前需要先使用driver.get(url) 访问一次目的url, 让selenium识别到需要添加cookie的作用域,不然会报错哦!

    print('[INFO] Getting HTML from URL')
    driver.get(url)  
    driver.delete_all_cookies()  # 这个也是必要的,不然cookie会添加失败,但不会报错!
    for i in cookies:
        driver.add_cookie(i)

    driver.get(url)
    driver.refresh()

step4. 进入iframe

获取到页面html后,我们需要进入iframe中,因为课表信息是藏在里面的,不进入的话只能获取到框架的源码!

    print('[INFO] Getting HTML from iframe')
    frame = driver.find_element(By.XPATH, '//*[@id="iframeCon"]/div/div/iframe')
    driver.switch_to.frame(frame)

step5. 获取课表源码

    soup = BeautifulSoup(driver.page_source, 'lxml')
    dataForm = soup.find('div', id='dataForm')
    # print(dataForm.prettify())

    print('[INFO] provider finished')
    return dataForm

就是这个页面的所有内容啦!

image-20221025161807919

parser – 提取信息并格式化

获取到课表的源码后我们就可以开始分析提取并进行格式化啦!

step6. 处理单节课存在两种课程的情况

123

我们发现一个单元格中,会出现存在两种课程的情况,即如上图,我们可以借用正则表达式通过提取周数的方式界定不同课程,以此保证不漏掉任何一节课。

def html_parser(dataForm):
    print('[INFO] Start parsing HTML')
    result = []
    cells = dataForm.find_all('td', class_='cell')
    reg = re.compile(r'(([0-9-]+(\([单双]\))?,?)+(?=周))')
    for cell in cells:
        if len(cell.contents) < 1:
            continue
        tmp_ow = re.findall(reg, cell.text)
        originWeeks = [i[0] for i in tmp_ow]
        # print(originWeeks)

step7. 一次提取每节课的信息

接下来我们就可以根据获取到的周数列表提取每节课的信息啦!

    for i in range(len(originWeeks)):
        res = {'sections': [], 'weeks': []}
        aaa = cell.find_all('a')

day - 课程所在的星期(1~7)

根据单元格的id,我们可以轻松获取到星期数(感谢学校

        # day
        res['day'] = cell['id'][4]

section - 节次

同上,根据单元格id,以及rowspan属性,也可以轻松获取课程节次

        # section
        be = int(cell['id'][5])
        cl = int(cell['rowspan'])
        for j in range(be, be + cl):
            res['sections'].append(j)

name, teacher, position - 课程,教师,上课地点

这就更简单了,直接就在text中有了,直接按顺序获取就行

        # name, teacher, position
        res['name'] = aaa[i * 3].text
        res['teacher'] = aaa[i * 3 + 1].text
        res['position'] = aaa[i * 3 + 2].text

tips:由于存在单个单元格多种课程的情况,索引中要加上前面for循环中的周次索引!

weeks - 周次

这个有一点点复杂,也不是很复杂,只要先这样,再这样,就可以了!

看代码吧,灰常好李姐理解~

        # weeks
        week = originWeeks[i].split(',')
        for w in week:
            try:
                be = int(w.split('-')[0])
                if len(w.split('-')) > 1:
                    cl = w.split('-')[1]
                else:
                    cl = be
                ds = w.split('(')
                if len(ds) > 1:
                    if ds[1] == '双)':
                        if be % 2 != 0:
                            be += 1
                        for j in range(be, int(cl.split('(')[0]) + 1, 2):
                            res['weeks'].append(j)
                    elif ds[1] == '单)':
                        if be % 2 == 0:
                            be += 1
                        for j in range(be, int(cl.split('(')[0]) + 1, 2):
                            res['weeks'].append(j)
                else:
                    for j in range(be, int(cl) + 1):
                        res['weeks'].append(j)
            except:
                print('Error: ', w + ' 周数无法分析')

step8. 输出,搞定

            result.append(res)
    print('[INFO] Finish parsing HTML')
    print('=' * 50)
    return result

step-1. main

from parser import html_parser
from provider import html_provider

if __name__ == '__main__':
    html = html_provider()
    # print(html)
    res = html_parser(html)
    for i in range(len(res)):
        print(f'{i}: {res[i]}')

最终结果

E:\anaconda3\envs\study\python.exe D:\source\PycharmProjects\study\crawler\main.py 
[INFO] Get URL and Cookie
[INFO] Start Edge Driver
[INFO] Getting HTML from URL
[INFO] Getting HTML from iframe
[INFO] provider finished
[INFO] Start parsing HTML
[INFO] Finish parsing HTML
==================================================
0: {'sections': [1, 2], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '1', 'name': '数据导入与预处理', 'teacher': '薛冰', 'position': '筑美楼10#A514'}
1: {'sections': [1, 2], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '2', 'name': '计算机组成原理', 'teacher': '仝瑞阳', 'position': '筑美楼10#A514'}
2: {'sections': [1, 2], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '3', 'name': '分布式数据库原理及应用', 'teacher': '新进人员2', 'position': '筑美楼10#A513'}
3: {'sections': [1, 2], 'weeks': [1, 3, 5, 7, 9, 11, 13], 'day': '4', 'name': '计算机组成原理', 'teacher': '仝瑞阳', 'position': '筑美楼10#A514'}
4: {'sections': [1, 2], 'weeks': [2, 4, 6, 10, 12, 14], 'day': '4', 'name': '准职业人导向训练(5)', 'teacher': '刘源源', 'position': '筑美楼10#A413'}
5: {'sections': [1, 2], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '5', 'name': 'Hadoop大数据技术', 'teacher': '新进人员1', 'position': '筑美楼10#A407'}
6: {'sections': [3, 4], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '1', 'name': '分布式数据库原理及应用', 'teacher': '新进人员2', 'position': '筑美楼10#A513'}
7: {'sections': [3, 4], 'weeks': [1, 3, 5, 7, 9, 11, 13], 'day': '2', 'name': 'VIP创新项目(3)', 'teacher': '张军', 'position': '筑美楼10#A407'}
8: {'sections': [3, 4], 'weeks': [2, 4, 6, 8, 10, 12, 14], 'day': '2', 'name': 'Hadoop大数据技术', 'teacher': '新进人员1', 'position': '筑美楼10#A407'}
9: {'sections': [3, 4], 'weeks': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 'day': '3', 'name': '数据导入与预处理', 'teacher': '薛冰', 'position': '筑美楼10#A514'}
10: {'sections': [3, 4], 'weeks': [8], 'day': '4', 'name': '准职业人导向训练(5)', 'teacher': '刘源源', 'position': '筑美楼10#A409'}

接下来就可以将结果导入到软件中进行进一步操作啦,这就是另外一件事了,下班!

One More Thing.

没事了(


X73

Hello world!

本文遵守 CC-BY-NC-4.0 许可协议。
欢迎转载,转载需注明出处,且禁止用于商业目的。 CC-BY-NC-4.0