最近在學python,正好做一個刷課腳本來練習一下。熟悉一下python的語法。過程中也遇到不少問題。下面就把整個過程記錄下來。
python刷課腳本實際上是模擬瀏覽器的訪問過程,因爲選課需要不斷的刷新,所以這裏可以利用腳本自動提交表單,解放雙手
一,對教務系統進行分析
這裏用chrome的調試功能f12
首先是模擬登錄。這裏我們看到有驗證碼,對於驗證碼,我試過python的文字識別,但是不理想。所以只能手工輸入。
1.驗證碼處理
觀察源碼
<img id="icode" src="CheckCode.aspx" onclick="reloadcode();" alt="看不清,換一張" title="看不清,換一張" border="0" style="POSITION:absolute;TOP:5px;LEFT:130px">
這個我們可以看出驗證碼是通過CheckCode.aspx顯示的。那麼我們就把驗證碼下載來,保存爲圖片就行。
2.cookie處理
這個是比較重要的,因爲我們登錄上去需要保持登錄狀態。我們先分析一下登錄的過程
當我們訪問教務系統網站的時候,我們看到的網址是http://www.scut.edu.cn/jw2005/
實際上,真實的網址是http://222.201.132.117/(julnrg55o3ukpeqcudqgbl45)/default2.aspx
觀察登錄的post請求
觀察url編碼方式,可以看出是gb2312的字符進行了url編碼。由於python的默認編碼的是ascii編碼,那麼如果我們直接對其進行url編碼,那麼得到的字符是錯誤的。
我自己的python設置了utf-8編碼,utf-8用3個字符代表一個漢字,所以url編碼之後的結果是:
>>> urllib2.quote('有')
'%E6%9C%89'
所以我們需要把中文編碼爲gbk,然後再進行url編碼
>>> urllib2.quote('有'.decode('utf8').encode('gbk'))
'%D3%D0'
先把‘有’以utf8進行解碼,然後編碼成gbk,最後再進行url編碼。
如果默認編碼方式不是utf8,那麼可以這樣寫:str.decode(sys.stdin.encoding).encode('gbk')
sys.stdin.encoding表示輸出的默認編碼
'__EVENTARGUMENT':'',
'ddl_ywyl':'',
'__VIEWSTATE':'',#這個viewstate簡直無力吐槽,起碼有上萬個字符,編輯器都裝不下,最後我用正則在網頁源碼裏面提取,添加進請求裏面。
'ddl_kcxz':'',
'ddl_kcgs':'',
'ddl_xqbs':'2',
'ddl_sksj':'',
'TextBox1':'',
'dpkcmcGrid:txtChoosePage':'1',
'dpkcmcGrid:txtPageSize':'120'
'__EVENTARGUMENT':'',
'ddl_ywyl':'',
'__VIEWSTATE':self.getViewState(page),
'ddl_kcxz':'',
'ddl_kcgs':'',
'ddl_xqbs':'2',
'ddl_sksj':'',
'TextBox1':'',
'dpkcmcGrid:txtChoosePage':'1',
'dpkcmcGrid:txtPageSize':'120',
code:'on',#這裏的code是課程代碼,每門課都不一樣
'Button1':self.encodeGBK(' 提交 '),
'dpDataGrid2:txtChoosePage':'1',
'dpDataGrid2:txtPageSize':'120'
#!/usr/bin/env python
# coding=utf-8
__metaclass__=type
import urllib2
import urllib
import time
import sys
import re
reload(sys)
sys.setdefaultencoding('utf-8')
class Spider:
def __init__(self):
self.baseUrl='http://222.201.132.117/'
self.CheckCodeUrl='CheckCode.aspx'
self.PostUrl='default2.aspx'
self.Agent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/40.0.2214.111 Chrome/40.0.2214.111 Safari/537.36'
#開啓debug
httpHandler = urllib2.HTTPHandler(debuglevel=0)
self.opener = urllib2.build_opener(httpHandler)
self.attempt=0
self.setRealUrl()
def setRealUrl(self):
'獲取真實網站,包含cookie'
request = urllib2.Request(self.baseUrl)
request.add_header('User-Agent', self.Agent)
urlList=self.opener.open(request).geturl().split('/')
urlList.pop()
self.baseUrl='/'.join(urlList)+'/'
def getCheckCode(self):
'獲取驗證碼'
#設置agent
request = urllib2.Request(self.baseUrl+self.CheckCodeUrl)
request.add_header('User-Agent', self.Agent)
#下載驗證碼
file=self.opener.open(request)
pic=file.read()
path='code.gif'
CheckCode=open(path,'wb')
CheckCode.write(pic)
CheckCode.close()
def setInfo(self):
'設置身份信息'
#學號
self.uid=raw_input('輸入學號:')
#密碼
self.pwd=raw_input('輸入密碼:')
#驗證碼
#self.getCheckCode()
#self.code=raw_input('輸入驗證碼:(驗證碼在本軟件所在文件夾)')
self.code=''
def getPage(self):
'登錄教務系統,並且獲取網頁'
postdata=urllib.urlencode({
'__VIEWSTATE':'dDwyODE2NTM0OTg7Oz5VGnjXV87Z19Dm3QbgRgvcptEYyA==',
'txtUserName':self.uid,
'TextBox2': self.pwd,
'txtSecretCode':self.code,
'RadioButtonList1':self.encodeGBK('學生'),#用gbk編碼
'Button1':'',
'lbLanguage':'',
'hidPdrs':'',
'hidsc':''
})
headers={
'User-Agent':self.Agent,
'Referer':self.baseUrl
}
req = urllib2.Request(
url = self.baseUrl+self.PostUrl,
data = postdata,
headers = headers
)
try:
response = urllib2.urlopen(req)
page=response.read().decode('gbk').encode('utf-8')#對gbk編碼的數據進行轉碼
return page
except urllib2.URLError, e:
self.run()
if hasattr(e, 'code'):
print 'The server couldn\'t fulfill the request.'
print 'Error code: ', e.code
elif hasattr(e, 'reason'):
print 'We failed to reach a server.'
print 'Reason: ', e.reason
else:
print 'No exception was raised.'
# everything is fine
def encodeGBK(self,str):
'對字符串用gbk進行編碼'
return str.decode('utf-8').encode('gbk')
def getSelect(self):
'獲取選課的網址'
href=re.findall(r'xf_xsqxxxk.+?N121103',self.getPage())
chinese=self.getChinese(href[0].decode('utf8'))
url=re.sub(u"[\u4e00-\u9fa5]+",urllib2.quote(self.encodeGBK(chinese)),href[0].decode('utf8'))
return url
def getChinese(self,str):
'從字符串獲取中文'
word=re.findall(u"[\u4e00-\u9fa5]+",str)
return word[0]
def getViewState(self,page):
'從網頁中獲取viewstate'
return re.search('<input type="hidden" name="__VIEWSTATE" value="(.+?)" />',page).group(1)
def getList(self):
'獲取全部課程列表'
url=self.baseUrl+self.getSelect()
request=urllib2.Request(url)
request.add_header('User-Agent', self.Agent)
request.add_header('Referer', self.baseUrl+'/xs_main.aspx?xh='+self.uid)
response = self.opener.open(request)
page=response.read().decode('gbk').encode('utf-8')#對gbk編碼的數據進行轉碼
postdata=urllib.urlencode({
'__EVENTTARGET':'dpkcmcGrid:txtPageSize',
'__EVENTARGUMENT':'',
'ddl_ywyl':'',
'__VIEWSTATE':self.getViewState(page),
'ddl_kcxz':'',
'ddl_kcgs':'',
'ddl_xqbs':'2',
'ddl_sksj':'',
'TextBox1':'',
'dpkcmcGrid:txtChoosePage':'1',
'dpkcmcGrid:txtPageSize':'120'
})
headers={
'User-Agent':self.Agent,
'Referer':url
}
request = urllib2.Request(
url = url,
data = postdata,
headers = headers
)
try:
response = self.opener.open(request)
page=response.read().decode('gbk').encode('utf-8')#對gbk編碼的數據進行轉碼
return page,url
except urllib2.URLError:
self.run()
def postCourse(self,course,teacher,date):
'獲取課程信息,並且提交'
data=self.getList()
url=data[1]
page=data[0]
pat='<tr.+?</tr>'
pat_code='<input id=".+?" type="checkbox" name="(.+?)" />'
pattern=re.compile(pat,re.S)#開啓dotall模式
codes=re.findall(pattern,page)#找出課程代碼
code=''
flag=1
for c in codes:
if course in c and teacher in c and date in c:
l=re.findall(pat_code,c)
code=l[0]
flag=0
break
if(flag):
print '未找到相應課程,請重新輸入'
exit()
print code
self.submit(code,url,page,date)
if(self.checkSelect(course,page,code,url,date)):
return True
else:
return False
<span style="white-space:pre"> </span>
def submit(self,code,url,page,date):
'提交選課表單'
postdata=urllib.urlencode({
'__EVENTTARGET':'',
'__EVENTARGUMENT':'',
'ddl_ywyl':'',
'__VIEWSTATE':self.getViewState(page),
'ddl_kcxz':'',
'ddl_kcgs':'',
'ddl_xqbs':'2',
'ddl_sksj':'',
'TextBox1':'',
'dpkcmcGrid:txtChoosePage':'1',
'dpkcmcGrid:txtPageSize':'120',
code:'on',
'Button1':self.encodeGBK(' 提交 '),
'dpDataGrid2:txtChoosePage':'1',
'dpDataGrid2:txtPageSize':'120'
})
headers={
'User-Agent':self.Agent,
'Referer':url
}
request = urllib2.Request(
url = url,
data = postdata,
headers = headers
)
try:
response = self.opener.open(request)
page=response.read().decode('gbk').encode('utf-8')#對gbk編碼的數據進行轉碼
except urllib2.URLError:
self.run(course,teacher,date)
def checkSelect(self,course,page,code,url,date):
'判斷選課是否成功'
pat='<legend>已選課程</legend><table.+?'+course+'.+?</table>'
pattern=re.compile(pat,re.S)
lenth=len(re.findall(pattern,page))
if(lenth==1):
print '選課成功'
return False
else:
time.sleep(1)#等待一秒
self.attempt += 1
print '嘗試選課'+str(self.attempt)
self.submit(code,url,page,date)
return True
def run(self,course,teacher,date):
self.setInfo()
while(self.postCourse(course,teacher,date)):
pass
spider=Spider()
course=raw_input("輸入課程:")
teacher=raw_input("輸入教師:")
date=raw_input("輸入週一~週五")
spider.run(course,teacher,date)