查成绩,算分数,每年的综合测评都是个固定的过程,作为软件开发者,这些过程当然可以交给代码去做,通过脚本进行网络请求获取数据,然后直接进行计算得到基础分直接填表就好了,查成绩再手动计算既容易出错也繁琐,所以本篇的内容就是开发一个爬虫脚本取抓取成绩表,至于综合测评计算,这个没什么意义这里就不说了,分数都有了就都够了。

我们的目的就是通过编写脚本,模仿浏览器进行请求获取源码,再进行解析本地化(或者直接计算)

要抓取到数据,其实方案不止一种,这里会介绍两种不同的方案,达到同样的目的:

  • 模仿浏览器进行请求(速度快)
  • 操作浏览器进行请求(速度慢)

先说第一种,这种方案是普遍的爬虫技术,因为爬取的内容不多,对速度要求也不够,所以就是很简单的一个爬虫过程:

  1. 分析请求
  2. 模仿请求

对于普通的校园网,一般不做流量限制,所以就算请求频繁,也基本不用担心IP被封禁,所以编写爬虫代码可以不用太过担心。先说我所在学校的校园网,是杭州方正软件公司开发的。

① 分析请求

分析请求很简单,就是使用浏览器进行请求,然后分析每个请求所发送和接收的信息,这里最简单应该是使用chrome的开发者模式(F12打开)

输入用户名和密码,勾选已认真阅读,接着点击登陆,这样右边的网络窗口中会检查到所有的网络请求,我们只需要找到对应登陆的一个(这里会带有表单):

这个时候,我们可以通过一些测试工具,尝试进行请求对应的这个地址,并且把表单提交上去试试登陆能否成功,如果成功的话,脚本也就可以模拟这个请求,这里用的是chrome商店的一个工具Postman,用法很简单:

登陆成功之后,我们再进行查询成绩:

这里可以看到这次得到了两个新的请求(上图红框的前两个)

仔细观察会发现,第一个请求头中的Referer指向的是第二个请求的地址,所以可以知道,第二个请求是先于第一个请求发送的。其次,我们发现这个请求中也有表单。

再看第二个请求:

它的Referer指向第三个请求,而这个第三个请求实际上登陆成功之后,就已经存在了,它就是请求到主界面的,而这个请求的类型是Get,所以也表明,第三个请求没有传递任何信息给这个请求。

整理可以知道,流程是这样的:

登陆成功后跳转:http://202.192.72.4/xs_main.aspx?xh=2013034743130

点击查询成绩按钮请求:http://202.192.72.4/xscj_gc.aspx?xh=2013034743130&xm=%B3%C2%D6%BE%B7%AB&gnmkdm=N121605 (Get)

点击查询在校成绩请求:http://202.192.72.4/xscj_gc.aspx?xh=2013034743130&xm=%u9648%u5fd7%u5e06&gnmkdm=N121605 (Post)

所以,我们先来模拟第二个,这个请求是Get类型,所以直接请求即可,但是会发现请求会失败,原因是服务器不能知道我们已经进行登陆了:

所以最先想到的办法是带上第一个请求得到的Cookie,但是也是不行,这个时候要用到上面说的Referer标识,这个标识会告诉服务器请求来源,因为登陆成功会在服务器进行登记,这个标记会让服务器知道请求来源于登陆成功的账号:

此时请求返回正常,我们在源码中可以发现有两个隐藏的<input>标签:

这两个标签传递的,其实是第三个请求的参数,这个时候,模拟第三个请求,并且添加对应的Referer(第二个请求的URL),会发现请求也成功了:

这个请求中的url中的一个参数xm被我更改为1了,原本使用的是一种unicode加密编码,把用户名编码过去了,但是实际上这个参数并没有实际意义,%u的格式会破坏Python程序,所以这里直接改成1了。

② 模仿请求

请求分析完毕,就可以开始写代码了:

用到的包:

  1. import requests, xlwt, os
  2. from bs4 import BeautifulSoup

登录:

  1. def login(s, number, password):
  2. print '正在登录账号:'+number
  3. url = 'http://202.192.72.4/default_gdsf.aspx'
  4. data = {'__EVENTTARGET': 'btnDL',
  5. 'TextBox1': number,
  6. 'TextBox2': password,
  7. '__VIEWSTATE': '/wEPDwULLTExNzc4NDAyMTBkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZXlfXxYBBQVjaGtZRIgvS19wi/UKxQv2qDEuCtWOjJdl',
  8. 'chkYD': 'on',
  9. '__EVENTVALIDATION': '/wEWCgKFvrvOBQLs0bLrBgLs0fbZDAK/wuqQDgKAqenNDQLN7c0VAuaMg+INAveMotMNAuSStccFAvrX56AClqUwdU9ySl1Lo85TvdUwz0GrJgI='}
  10. s.post(url, data)
  11. return s.cookies

登录操作没有给后面的请求传递任何参数,这里的Cookies不是必须的,但是登录是必须的,这样告诉服务器我们后面的请求才是合法的。

点击查询成绩按钮:

  1. def get_data_for_grade(s, number, password):
  2. url = 'http://202.192.72.4/xscj_gc.aspx?xh=' + number + '&xm=%B3%C2%D6%BE%B7%AB&gnmkdm=N121605'
  3. referer = 'http://202.192.72.4/xs_main.aspx?xh=' + number
  4. cookies = login(s, number, password)
  5. response = s.get(url=url, headers={'Referer': referer}, allow_redirects=False, cookies=cookies)
  6. source = response.text
  7. soup = BeautifulSoup(source, 'html.parser')
  8. view_state = soup.find('input', attrs={'id': '__VIEWSTATE'})['value']
  9. event_validation = soup.find('input', attrs={'id': '__EVENTVALIDATION'})['value']
  10. states = {'view_state': view_state, 'event_validation': event_validation, 'cookies': cookies, 'origin': url}
  11. return states

第五行队请求设置Referer,接着通过BeautifulSoup解析源码得到两个隐藏的<input>标签里面value值,第三个请求要用到。

查询所有成绩请求:

  1. def check_info(s, number, password):
  2. url = 'http://202.192.72.4/xscj_gc.aspx?xh=' + number + '&xm=1&gnmkdm=N121605'
  3. states = get_data_for_grade(s, number, password)
  4. print '登录成功,正在拉取成绩'
  5. data = {
  6. '__VIEWSTATE': states['view_state'],
  7. 'ddlXN': '',
  8. 'ddlXQ': '',
  9. 'Button2': '',
  10. '__EVENTVALIDATION': states['event_validation']
  11. }
  12. response = s.post(url, data=data, cookies=states['cookies'], headers={'Referer': states['origin']},
  13. allow_redirects=False)
  14. return response.text

得到成绩单源码之后,就可以进行解析了,这里解析存放到xls表格中:

  1. def writeToFile(source):
  2. print '正在写入文档'
  3. wb = xlwt.Workbook(encoding='utf-8', style_compression=0)
  4. soup = BeautifulSoup(source, "html.parser")
  5. span = soup.find('span', attrs={'id': 'Label5'})
  6. sheet = wb.add_sheet('成绩单', cell_overwrite_ok=True)
  7. table = soup.find(attrs={'id': 'Datagrid1'})
  8. lines = table.find_all('tr')
  9. for i in range(len(lines)):
  10. tds = lines[i].find_all('td')
  11. for j in range(len(tds)):
  12. sheet.write(i, j, tds[j].text)
  13. try:
  14. os.remove(span.text + '.xls')
  15. except:
  16. pass
  17. wb.save(span.text + '.xls')

最后遍历学号进行爬取,这里只爬取默认账号密码的成绩:

  1. for i in range(1, 55):
  2. num = ''
  3. s = requests.session()
  4. try:
  5. if i <= 9:
  6. writeToFile(check_info(s, num[:12] + str(i), num[:12] + str(i)))
  7. else:
  8. writeToFile(check_info(s, num[:11] + str(i), num[:11] + str(i)))
  9. except:
  10. pass
  11. s.close()


第二种方案,是通过模拟浏览器来进行登录,点击按钮等操作获取成绩,这里用到的是自动化测试框架Selenium

这种方案的优点是我们不需要像第一种那样要去分析请求,只需要告诉浏览器要怎么做就行了,但是缺点是速度慢。

  1. # -*- coding: utf-8 -*-
  2. from selenium import webdriver
  3. from selenium.webdriver.common.by import By
  4. from selenium.webdriver.support.wait import WebDriverWait
  5. from selenium.webdriver.common.action_chains import ActionChains
  6. from selenium.webdriver.support import expected_conditions as EC
  7. from selenium.common.exceptions import NoSuchElementException
  8. from selenium.common.exceptions import NoAlertPresentException
  9. from bs4 import BeautifulSoup
  10. import xlwt
  11. import os
  12.  
  13. class Script():
  14. def setUp(self):
  15. self.driver = webdriver.Chrome()
  16. self.driver.implicitly_wait(10)
  17. # self.driver.maximize_window()
  18. self.base_url = "http://202.192.72.4/"
  19. self.verificationErrors = []
  20. self.accept_next_alert = True
  21.  
  22. def test_jb(self, num):
  23. driver = self.driver
  24. driver.get(self.base_url + "/default_gdsf.aspx")
  25. driver.find_element_by_id("TextBox1").clear()
  26. driver.find_element_by_id("TextBox1").send_keys(num)
  27. driver.find_element_by_id("TextBox2").clear()
  28. driver.find_element_by_id("TextBox2").send_keys(num)
  29. driver.find_element_by_id("chkYD").click()
  30. driver.find_element_by_id("btnDL").click()
  31. WebDriverWait(driver, 5).until(EC.alert_is_present()).accept()
  32. self.open_and_click_menu(driver)
  33. retry = 0
  34. while retry <= 2:
  35. try:
  36. driver.switch_to.frame(driver.find_element_by_id('iframeautoheight'))
  37. WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.XPATH, "//input[@id='Button2']")))
  38. break
  39. except:
  40. print '重新点击按钮'
  41. driver.switch_to.parent_frame()
  42. self.open_and_click_menu(driver)
  43. retry += 1
  44. else:
  45. print '重试失败'
  46.  
  47. source = driver.page_source
  48. driver.find_element_by_xpath("//input[@id='Button2']").click()
  49.  
  50. def source_change(driver):
  51. if source == driver.page_source:
  52. return False
  53. else:
  54. return driver.page_source
  55.  
  56. self.writeToFile(WebDriverWait(driver, 10).until(source_change))
  57. driver.quit()
  58.  
  59. def open_and_click_menu(self, driver):
  60. menu1 = WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.XPATH, "//ul[@class='nav']/li[5]")))
  61. menu2 = driver.find_element_by_xpath("//ul[@class='nav']/li[5]/ul/li[3]")
  62. ActionChains(driver).move_to_element(menu1).move_to_element(menu2).click(menu2).perform()
  63.  
  64. def is_element_present(self, how, what):
  65. try:
  66. self.driver.find_element(by=how, value=what)
  67. except NoSuchElementException as e:
  68. return False
  69. return True
  70.  
  71. def is_alert_present(self):
  72. try:
  73. self.driver.switch_to_alert()
  74. except NoAlertPresentException as e:
  75. return False
  76. return True
  77.  
  78. def close_alert_and_get_its_text(self):
  79. try:
  80. alert = self.driver.switch_to_alert()
  81. alert_text = alert.text
  82. if self.accept_next_alert:
  83. alert.accept()
  84. else:
  85. alert.dismiss()
  86. return alert_text
  87. finally:
  88. self.accept_next_alert = True
  89.  
  90. def tearDown(self):
  91. self.driver.quit()
  92. self.assertEqual([], self.verificationErrors)
  93.  
  94. @staticmethod
  95. def writeToFile(source):
  96. wb = xlwt.Workbook(encoding='utf-8', style_compression=0)
  97. soup = BeautifulSoup(source, "html.parser")
  98. span = soup.find('span', attrs={'id': 'Label5'})
  99. sheet = wb.add_sheet('成绩单', cell_overwrite_ok=True)
  100. table = soup.find(attrs={'id': 'Datagrid1'})
  101. lines = table.find_all('tr')
  102. for i in range(len(lines)):
  103. tds = lines[i].find_all('td')
  104. for j in range(len(tds)):
  105. sheet.write(i, j, tds[j].text)
  106. try:
  107. os.remove(span.text + '.xls')
  108. except:
  109. pass
  110. wb.save(span.text + '.xls')
  111.  
  112. if __name__ == "__main__":
  113. # unittest.main()
  114. s = Script()
  115.  
  116. for i in range(1, 50):
  117. num = ''
  118. s.setUp()
  119. try:
  120. if i <= 9:
  121. s.test_jb(num[:12] + str(i))
  122. else:
  123. s.test_jb(num[:11] + str(i))
  124. except:
  125. pass

这种方法的意义只是熟悉一下自动化测试框架,因为速度实在太慢了,也就不详细介绍了,这里粗略说一下,其实原理就是通过查到网页中对应的控件,进行点击或者悬浮于上面等等的操作,一步一步的到达最后的成绩单,要做的是控制整个流程,明确在什么时候应该停一下等控件出现,什么时候要去点击。

而且到目前为止,这个框架还是有一些Bug的,比如火狐浏览器的驱动无法实现在一个按钮上Hover的操作等等。

Python--校园网爬虫记的更多相关文章

  1. Python开发爬虫之静态网页抓取篇:爬取“豆瓣电影 Top 250”电影数据

    所谓静态页面是指纯粹的HTML格式的页面,这样的页面在浏览器中展示的内容都在HTML源码中. 目标:爬取豆瓣电影TOP250的所有电影名称,网址为:https://movie.douban.com/t ...

  2. python分布式爬虫打造搜索引擎--------scrapy实现

    最近在网上学习一门关于scrapy爬虫的课程,觉得还不错,以下是目录还在更新中,我觉得有必要好好的做下笔记,研究研究. 第1章 课程介绍 1-1 python分布式爬虫打造搜索引擎简介 07:23 第 ...

  3. Python简单爬虫入门三

    我们继续研究BeautifulSoup分类打印输出 Python简单爬虫入门一 Python简单爬虫入门二 前两部主要讲述我们如何用BeautifulSoup怎去抓取网页信息以及获取相应的图片标题等信 ...

  4. Ubuntu下配置python完成爬虫任务(笔记一)

    Ubuntu下配置python完成爬虫任务(笔记一) 目标: 作为一个.NET汪,是时候去学习一下Linux下的操作了.为此选择了python来边学习Linux,边学python,熟能生巧嘛. 前期目 ...

  5. Python简单爬虫入门二

    接着上一次爬虫我们继续研究BeautifulSoup Python简单爬虫入门一 上一次我们爬虫我们已经成功的爬下了网页的源代码,那么这一次我们将继续来写怎么抓去具体想要的元素 首先回顾以下我们Bea ...

  6. [Python] 网络爬虫和正则表达式学习总结

    以前在学校做科研都是直接利用网上共享的一些数据,就像我们经常说的dataset.beachmark等等.但是,对于实际的工业需求来说,爬取网络的数据是必须的并且是首要的.最近在国内一家互联网公司实习, ...

  7. python简易爬虫来实现自动图片下载

    菜鸟新人刚刚入住博客园,先发个之前写的简易爬虫的实现吧,水平有限请轻喷. 估计利用python实现爬虫的程序网上已经有太多了,不过新人用来练手学习python确实是个不错的选择.本人借鉴网上的部分实现 ...

  8. GJM : Python简单爬虫入门(二) [转载]

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  9. Python分布式爬虫原理

    转载 permike 原文 Python分布式爬虫原理 首先,我们先来看看,如果是人正常的行为,是如何获取网页内容的. (1)打开浏览器,输入URL,打开源网页 (2)选取我们想要的内容,包括标题,作 ...

  10. Python 网页爬虫 & 文本处理 & 科学计算 & 机器学习 & 数据挖掘兵器谱(转)

    原文:http://www.52nlp.cn/python-网页爬虫-文本处理-科学计算-机器学习-数据挖掘 曾经因为NLTK的缘故开始学习Python,之后渐渐成为我工作中的第一辅助脚本语言,虽然开 ...

随机推荐

  1. MyBatis 源码分析——介绍

    笔者第一次接触跟MyBatis框架是在2009年未的时候.不过那个时候的他并不叫MyBatis,而是叫IBatis.2010年的时候改为现在的名字--MyBatis.这几年过去了,对于笔者来讲有一点陌 ...

  2. [转载] HTTP协议状态码详解(HTTP Status Code)

    转载自:http://www.cnblogs.com/shanyou/archive/2012/05/06/2486134.html 使用ASP.NET/PHP/JSP 或者javascript都会用 ...

  3. ZLG_GUI和3D显示的移植

    最近学习NRF51822,想在OLED上移植个强大的GUI ,本来想学习emWIN的,甚至想直接学习自带GUI的嵌入式操作系统RTThread,但是......哎,太懒了.....现在觉得ZLG_GU ...

  4. HDU 3783 ZOJ

    ZOJ Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submiss ...

  5. HDU 2186 悼念512汶川大地震遇难同胞——一定要记住我爱你

    悼念512汶川大地震遇难同胞——一定要记住我爱你 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java ...

  6. phpcms基础知识和配置

    一.设置界面 1.站点设置:相当于服务器上的站点 (1)站点修改:“关键词”和“描述”的修改,便于网络优化和搜索引擎对本网站的搜索. (2)模板的修改,可以自己加模板,引用自己模板 2.基本设置:所有 ...

  7. 特性Attribute 的使用

    [IdentityAuthorize]           public ActionResult Index()        {             return View("~/V ...

  8. Java三大修饰符

    1.static 修饰: 修饰属性:类变量,全类共有 修饰方法:静态方法,静态方法中不能直接访问非静态的方法和属性 静态方法只能被静态方法覆盖,并且没有多态 静态的方法或者属性不依赖于对象:类名.方法 ...

  9. 我的Java开发之路

    拉拉溜溜学习了半年了.才发现自己现在才进入面向对象.

  10. 浅谈Java单例模式

    关于基本的懒汉式,饿汉式等写法网上介绍多如牛毛,这里不再赘述,直接讨论加了volatile关键字的双重锁(Double check),静态内部类以及枚举等写法,如有不对,恳请读者指出,欢迎讨论. 1. ...