人生苦短,我用python--爬虫模拟登陆教务处并且保存数据到本地(代码片段)

斩月也会过期 斩月也会过期     2022-12-09     786

关键词:

刚开始接触Python,看很多人玩爬虫我也想玩,找来找去发现很多人用网络爬虫干的第一件事就是模拟登陆,增加点难度就是模拟登陆后在获取数据,但是网上好少有Python 3.x的模拟登陆Demo可以参考,加上自己也不怎么懂Html,所以这第一个Python爬虫写的异常艰难,不过最终结果还是尽如人意的,下面把这次学习的过程整理一下。

工具
- 系统:win7 64位系统
- 浏览器:Chrome
- Python版本:Python 3.5 64-bit
- IDE:JetBrains PyCharm (貌似很多人都用这个)

我把目标瞄准了我们的教务处,这次爬虫的目的是从教务处获取成绩并且把成绩输入Excel表格中保存起来,

我们学校教务处的地址是:http://jwc.ecjtu.jx.cn/ ,往常每次我们获取成绩都需要先进入教务处,然后点击成绩查询,输入公共的账号密码进入,最后输入相关信息获取成绩表格,这里登陆不需要验证码省了我一番功夫,这样我们先进入成绩查询系统登陆界面,先看看怎么模拟登陆这个过程,在Chrome浏览器下按F12打开开发者面板:

这里我们学校的教务处查询系统的密码是公共的jwc也就是拼音缩写,我们输入用户名和密码点击登陆,这时候注意POST请求:

发现了什么,好像Chrome并没有把Post提交的表单信息保留下来直接跳转到了另一个界面然后展示另一个界面的数据,这里就需要我们自己动手操作一下,注意开发者面板左上角的小红点表示这时候正在抓取数据,如果点击一下就会变成灰色,就可以变相地保存下当时抓取到的包,我在点击登陆后新界面未刷新出来之前点击了这个小红点,如愿以偿的得到了Post的表单数据:

这样就获取了浏览器在登陆时候向服务器传递的表单数据,看一下这个表单都有些什么:

这里看到我们需要传递三个参数,分别是:user、pass、Submit,可以很容易的理解这几个单词的字面意思,这样有了思路,我们就可以写出这次代码的第一步:模拟登陆教务处

直接上代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
url = 'http://jwc.ecjtu.jx.cn/mis_o/login.php'
datas = 'user': 'jwc',
         'pass': 'jwc',
         'Submit': '%CC%E1%BD%BB'
         
headers = 'Referer': 'http://jwc.ecjtu.jx.cn/mis_o/login.htm',
           'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 '
                         '(KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
           'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
           'Accept-Language': 'zh-CN,zh;q=0.8',
           
sessions = requests.session()
response = sessions.post(url, headers=headers, data=datas)
print(response.status_code)

代码输出:

200

说明我们模拟登陆成功了,这里用到了Requests模块,还不会使用的可以查看中文文档 ,它给自己的定义是:HTTP for Humans,因为简单易用易上手,我们只需要传入Url地址,构造请求头,传入post方法需要的数据,就可以模拟浏览器登陆了,这里因为有进一步获取成绩的操作所以使用了session来保持连接,这里单看最后的返回码的话我们是成功了的,具体如何还要看下一步操作,接下来:

这里为了简便代码我们设定输入学号查询所有成绩,减少其他判断,同样对Post数据进行抓包:

同样查看Post的数据:

因为这里就分析输入学号的情况所以其他都为空,这样我们就可以写出查询成绩的代码:

    score_healders = 'Connection': 'keep-alive',
                      'User - Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
                                      'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
                      'Content - Type': 'application / x - www - form - urlencoded',
                      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                      'Content - Length': '69',
                      'Host': 'jwc.ecjtu.jx.cn',
                      'Referer': 'http: // jwc.ecjtu.jx.cn / mis_o / main.php',
                      'Upgrade - Insecure - Requests': '1',
                      'Accept - Language': 'zh - CN, zh;q = 0.8'
                      
    score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(
        pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
    score_data = 'Name': '',
                  'StuID': num,
                  'Course': '',
                  'Term': '',
                  'ClassID': '',
                  'Submit': '%B2%E9%D1%AF'
                  

    score_response = sessions.post(score_url, data=score_data, headers=score_healders)
    content = score_response.content

这里解释一下上面的代码,上面的score_url 并不是浏览器上显示的地址,我们要获取真正的地址,在Chrome下右键–查看网页源代码,找到这么一行:

a href=query.php?start=1&job=see&=&Name=&Course=&ClassID=&Term=&StuID=xxxxxxx

这个才是真正的地址,点击这个地址转入的才是真正的界面,因为这里成绩数据较多,所以这里采用了分页显示,这个start=1说明是第一页,这个参数是可变的需要我们传入,还有StuID后面的是我们输入的学号,这样我们就可以拼接出Url地址:

score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num

同样使用Post方法传递数据并获取响应的内容:

score_response = sessions.post(score_url, data=score_data,headers=score_healders)
content = score_response.content

这里采用Beautiful Soup 4.2.0来解析返回的响应内容,因为我们要获取的是成绩,这里到教务处成绩查询界面,查看获取到的成绩在网页中是以表格的形式存在:


观察表格的网页源代码:

<table align=center border=1>
<tr><td bgcolor=009999>学期</td>
<td bgcolor=009999>学号</td>
<td bgcolor=009999>姓名</td>
<td bgcolor=009999>课程</td>
<td bgcolor=009999>课程要求</td>
<td bgcolor=009999>学分</td>
<td bgcolor=009999>成绩</td>
<td bgcolor=009999>重考一</td>
<td bgcolor=009999>重考二</td></tr>
...
...
</tr></table>

这里拿出第一行举例,虽然我不太懂Html但是从这里可以看出来<tr> 代表的是一行,而<td>应该是代表这一行中的每一列,这样就好办了,取出每一行然后分解出每一列,打印输出就可以得到我们要的结果:

from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'html.parser')
# 找到每一行
target = soup.findAll('tr')

这里分解每一列的时候要小心,因为这里表格分成了三页显示,每页最多显示30条数据,这里因为只是收集已经毕业的学生的成绩数据所以不对其他数据量不足的学生成绩的情况做统计,默认收集的都是大四毕业的学生成绩数据。这里采用两个变量ij分别代表行和列:

# 注:这里的print单纯是我为了验证结果打印在PyCharm的控制台上而已
i=0, j=0
for tag in target[1:]:
            tds = tag.findAll('td')
            # 每一次都是从列头开始获取
            j = 0
            # 学期
            semester = str(tds[0].string)
            if semester == 'None':
                break
            else:
                print(semester.ljust(6) + '\\t\\t\\t', end='')
            # 学号
            studentid = tds[1].string
            print(studentid.ljust(14) + '\\t\\t\\t', end='')
            j += 1
            # 姓名
            name = tds[2].string
            print(name.ljust(3) + '\\t\\t\\t', end='')
            j += 1
            # 课程
            course = tds[3].string
            print(course.ljust(20, ' ') + '\\t\\t\\t', end='')
            j += 1
            # 课程要求
            requirments = tds[4].string
            print(requirments.ljust(10, ' ') + '\\t\\t', end='')
            j += 1
            # 学分
            scredit = tds[5].string
            print(scredit.ljust(2, ' ') + '\\t\\t', end='')
            j += 1
            # 成绩
            achievement = tds[6].string
            print(achievement.ljust(2) + '\\t\\t', end='')
            j += 1
            # 重考一
            reexaminef = tds[7].string
            print(reexaminef.ljust(2) + '\\t\\t', end='')
            j += 1
            # 重考二
            reexamines = tds[8].string
            print(reexamines.ljust(2) + '\\t\\t')
            j += 1
            i += 1

这里查了很多别人的博客都是用正则表达式来分解数据,表示自己的正则写的并不好也尝试了但是没成功,所以无奈选择这种方式,如果有人有测试成功的正则欢迎跟我说一声,我也学习学习。

把数据保存到Excel

因为已经清楚了这个网页保存成绩的具体结构,所以顺着每次循环解析将数据不断加以保存就是了,这里使用xlwt写入数据到Excel,因为xlwt模块打印输出到Excel中的样式宽度偏小,影响观看,所以这里还加入了一个方法去控制打印到Excel表格中的样式:

file = xlwt.Workbook(encoding='utf-8')
table = file.add_sheet('achieve')
# 设置Excel样式
def set_style(name, height, bold=False):
    style = xlwt.XFStyle()  # 初始化样式
    font = xlwt.Font()  # 为样式创建字体
    font.name = name  # 'Times New Roman'
    font.bold = bold
    font.color_index = 4
    font.height = height
    style.font = font
    return style

运用到代码中:

for tag in target[1:]:
            tds = tag.findAll('td')
            j = 0
            # 学期
            semester = str(tds[0].string)
            if semester == 'None':
                break
            else:
                print(semester.ljust(6) + '\\t\\t\\t', end='')
                table.write(i, j, semester, set_style('Arial', 220))
            # 学号
            studentid = tds[1].string
            print(studentid.ljust(14) + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, studentid, set_style('Arial', 220))
            table.col(i).width = 256 * 16
            # 姓名
            name = tds[2].string
            print(name.ljust(3) + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, name, set_style('Arial', 220))
            # 课程
            course = tds[3].string
            print(course.ljust(20, ' ') + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, course, set_style('Arial', 220))
            # 课程要求
            requirments = tds[4].string
            print(requirments.ljust(10, ' ') + '\\t\\t', end='')
            j += 1
            table.write(i, j, requirments, set_style('Arial', 220))
            # 学分
            scredit = tds[5].string
            print(scredit.ljust(2, ' ') + '\\t\\t', end='')
            j += 1
            table.write(i, j, scredit, set_style('Arial', 220))
            # 成绩
            achievement = tds[6].string
            print(achievement.ljust(2) + '\\t\\t', end='')
            j += 1
            table.write(i, j, achievement, set_style('Arial', 220))
            # 重考一
            reexaminef = tds[7].string
            print(reexaminef.ljust(2) + '\\t\\t', end='')
            j += 1
            table.write(i, j, reexaminef, set_style('Arial', 220))
            # 重考二
            reexamines = tds[8].string
            print(reexamines.ljust(2) + '\\t\\t')
            j += 1
            table.write(i, j, reexamines, set_style('Arial', 220))
            i += 1

file.save('demo.xls')

最后稍加整合,写成一个方法:

# 获取成绩
# 这里num代表输入的学号,pagenum代表页数,总共76条数据,一页30条所以总共有三页
def getScore(num, pagenum, i, j):
    score_healders = 'Connection': 'keep-alive',
                      'User - Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
                                      'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36',
                      'Content - Type': 'application / x - www - form - urlencoded',
                      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                      'Content - Length': '69',
                      'Host': 'jwc.ecjtu.jx.cn',
                      'Referer': 'http: // jwc.ecjtu.jx.cn / mis_o / main.php',
                      'Upgrade - Insecure - Requests': '1',
                      'Accept - Language': 'zh - CN, zh;q = 0.8'
                      
    score_url = 'http://jwc.ecjtu.jx.cn/mis_o/query.php?start=' + str(
        pagenum) + '&job=see&=&Name=&Course=&ClassID=&Term=&StuID=' + num
    score_data = 'Name': '',
                  'StuID': num,
                  'Course': '',
                  'Term': '',
                  'ClassID': '',
                  'Submit': '%B2%E9%D1%AF'
                  

    score_response = sessions.post(score_url, data=score_data, headers=score_healders)
    # 输出到文本
    with open('text.txt', 'wb') as f:
        f.write(score_response.content)
    content = score_response.content
    soup = BeautifulSoup(content, 'html.parser')
    target = soup.findAll('tr')
    try:
        for tag in target[1:]:
            tds = tag.findAll('td')
            j = 0
            # 学期
            semester = str(tds[0].string)
            if semester == 'None':
                break
            else:
                print(semester.ljust(6) + '\\t\\t\\t', end='')
                table.write(i, j, semester, set_style('Arial', 220))
            # 学号
            studentid = tds[1].string
            print(studentid.ljust(14) + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, studentid, set_style('Arial', 220))
            table.col(i).width = 256 * 16
            # 姓名
            name = tds[2].string
            print(name.ljust(3) + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, name, set_style('Arial', 220))
            # 课程
            course = tds[3].string
            print(course.ljust(20, ' ') + '\\t\\t\\t', end='')
            j += 1
            table.write(i, j, course, set_style('Arial', 220))
            # 课程要求
            requirments = tds[4].string
            print(requirments.ljust(10, ' ') + '\\t\\t', end='')
            j += 1
            table.write(i, j, requirments, set_style('Arial', 220))
            # 学分
            scredit = tds[5].string
            print(scredit.ljust(2, ' ') + '\\t\\t', end='')
            j += 1
            table.write(i, j, scredit, set_style('Arial', 220))
            # 成绩
            achievement = tds[6].string
            print(achievement.ljust(2) + '\\t\\t', end='')
            j += 1
            table.write(i, j, achievement, set_style('Arial', 220))
            # 重考一
            reexaminef = tds[7].string
            print(reexaminef.ljust(2) + '\\t\\t', end='')
            j += 1
            table.write(i, j, reexaminef, set_style('Arial', 220))
            # 重考二
            reexamines = tds[8].string
            print(reexamines.ljust(2) + '\\t\\t')
            j += 1
            table.write(i, j, reexamines, set_style('Arial', 220))
            i += 1
    except:
        print('出了一点小Bug')
    file.save('demo.xls')

在模拟登陆操作后增加一个判断:

# 判断是否登陆
def isLogin(num):
    return_code = response.status_code
    if return_code == 200:
        if re.match(r"^\\d14$", num):
            print('请稍等')
        else:
            print('请输入正确的学号')
        return True
    else:
        return False

最后在__main__中这么调用:

if __name__ == '__main__':
    num = input('请输入你的学号:')
    if isLogin(num):
        getScore(num, pagenum=0, i=0, j=0)
        getScore(num, pagenum=1, i=31, j=0)
        getScore(num, pagenum=2, i=62, j=0)

在PyCharm下按alt+shift+x快捷键运行程序:

控制台会有如下输出(这里只截取部分,不要吐槽没有对齐,这里我也用了格式化输出还是不太行,不过最起码出来了结果,而且我们的目的是输出到Excel中不是吗)

然后去程序根目录找看看有没有生成一个叫demo.xls的文件,我的程序就放在桌面,所以去桌面找:

点开查看是否成功获取:

至此,大功告成

小结
刚开始接触Python一个星期的样子,这次写了这么一个简单的网络爬虫检验一下学习成果,虽然程序还有些许Bug,不过总归得到了一定收获,当然也为下一步学习打下了基础,嗯哼,为了接下来批量获取网络上美女图片并分类保存我会继续自学Python,荆轲刺秦王~

源码

人生苦短,我用python--一起来爬知乎娘(代码片段)

上次爬了教务处的成绩,接下来想去爬一爬知乎娘,好像大家都很喜欢爬知乎娘,GitHub上貌似已经有人把获取各种知乎数据的操作封装好了:zhihu-python,但是良辰表示还是想自己试一试,我就爬点简单的&#x... 查看详情

人生苦短,我用python

其实现在程序员学Python不是新鲜事,甚至不少人会把Python当作第一语言来学习。也难怪,Python的优点太多了,它语言简洁、开发效率高、可移植性强,并且可以和其他编程语言(比如C++)轻松无缝衔... 查看详情

人生苦短,我用python

二十余年,难有铸尖;圣诞前歇,方有此编。九十年代正是风云并起之时,正是从那个年代开始,人们铸就了许许多多的神话。Python正是这样的一个神话,无人能否定这种特殊语言在如今社会的发展优势和在未来社会的发展潜力... 查看详情

人生苦短,我用python--day9

目录                            paramiko模块  ssh工具  scp工具  查看详情

人生苦短,我用python

Python之路一、Python基础    二、数据库    三、前端     四、Django框架PythonWeb框架须知HTTP介绍Web框架介绍Django框架基础应用Django之ORMORM之简介与应用ORM之常用字段和参数ORM之关系字... 查看详情

python基础知识总结(小白福利来了)……人生苦短,我用python(代码片段)

python基础知识总结……人生苦短,我用python文章目录python基础知识总结……人生苦短,我用python前言注释数据类型变量名数值类型整数浮点数布尔型复数a+bi字符串替换replacestrip去掉切割split拼接join字符串大小写转化查... 查看详情

人生苦短我用python(代码片段)

1函数参数(1)收集参数:以一个星号*加上形参名的方式,表示这个函数的实参个数不定,可能0个可能n个。defvarParaFun(name,*param):print(‘位置参数是:‘,name)print(‘收集参数是:‘,param)print(‘第一个收集参数是:‘,param[0])varPar... 查看详情

python学习教程:人生苦短,我用python?入门前你要知道这些

有多少伙伴是因为一句 ‘人生苦短,我用Python’就要去学Python的? 之前也大家更新过Python学习教程普及过多次的Python相关知识,不过大家还是还得计划一下Python学习路线!Python入门前,一定要知道这些,你只有了... 查看详情

离线文字转语音(人生苦短,我用python)

...各样的问题;办法还是比困难多,经过多方求证;终于:人生苦短,我用Python就是这么简单;用python命令只要执行该文件,即可听到悦耳的播报但是因为后台和接口是PHP写的,怎么能无缝斜街呢?那么就需要用到system()和exec()之... 查看详情

《从实战学python》专栏目录——人生苦短,我用python(代码片段)

点击文末卡片,关注后找到博主的联系方式,可以免费阅读文章及源码或+yitiaoit04免费阅读专栏文章目录共24篇,持续更新中(每周一篇)📚20行代码实现信息自动发送📚还是单身?教你如何... 查看详情

python模拟登陆教务系统爬取成绩信息+绘制成绩分布图+导入mysql(代码片段)

这是一篇介绍如何通过Python实现模拟登陆学校教务系统并爬取成绩相关信息(学期,课程名,总成绩,课程性质,学分)然后绘制成绩分布折线图最后导入MySQL数据库中的文章为了利用充分利用selenium的功能和成功爬取数据࿰... 查看详情

人生苦短,我用vim!(代码片段)

大家好,我是Carl熟悉我的录友,应该都知道我是vim流,无论是写代码还是写文档(Markdown),都是vim,都没用IDE。但这里我并不是说IDE不好用,IDE在代码跟踪,引用跳转等等其实是很给力的࿰... 查看详情

字符串拼接,格式化输出深浅复制

#1.a=‘苦短‘b=‘Python‘,用4种方法,输出‘人生苦短,我用Python‘‘人生‘+a+‘,我用‘+b‘人生%s,我用%s‘%(a,b)‘‘.join([‘人生‘,a,‘,我用‘,b])‘人生{},我用{}‘.format(a,b) #2.列表li=[‘I‘,‘like‘,‘python‘],用2种方法... 查看详情

人生为啥会苦短呢?

李白说:“人生得意须尽欢,莫使金樽空对月”。人了嘛,应该及时行乐,你要是觉得苦短,那是因为你不懂得让自己变得忙起来,让你活的不那么清闲,你就不会牢骚满腹了,改快乐就快乐起来,你就能感受到生活的魅力了。... 查看详情

python爬虫获取强智科技教务系统学科成绩(模拟登录+成绩获取)

​​python爬取强智科技教务系统,以江苏科技大学为例:本人开发的系统作为参考:​​​​https://www.wjn1996.cn/estudy/tools/educationLogin.jsp?school=10289&search=1​​以本人账号为例: 程序如下,保存为test.py,cmd命令执行python3test.... 查看详情

“人生苦短,我用python“——web测试(代码片段)

Web应用测试对Web应用进行渗透测试HTTP对Web程序进行渗透测试所需模块urllib2库的使用urllib2.urlopen()urllib2.Request()其他模块文件urllib模块httplib2模块requests模块BeautifulSoup模块cookielib模块处理HTTP头部Cookie捕获HTTP基本认证数据包编写Web... 查看详情

python写爬虫-爬甘农大学校新闻(代码片段)

...混迹.有句话说:“Lifeisshort,youneedPython!” 翻译过来就是: 人生苦短,我用Python究竟它有多么强大, 多么简洁?抱着这个好奇心,趁不忙的几天.还是忍不住的小学了一下.(--其实学了还不到两天)随便用一个"HelloWorld 查看详情

python人生苦短,我学python(代码片段)

文章目录1.模块(module)模块化2.模块的使用3.包(Package)4.Python标准库4.1sys模块库4.2pprint模块库(perfectprint)4.3os模块库(operatingsystem)5.异常6.异常传播7.raise抛出异常8.自定义异常对象9.文件(file)9.1open()函数打开文件9.2read()函数读取文件9.3wr... 查看详情