老婆大人每個月都要上一個網站上去查數據,然后做報表。
為了減輕老婆大人的工作壓力,所以我決定做個小程序,減輕我老婆的工作量。
准備工作
1.tesseract-ocr
這個工具用來識別驗證碼,非常好用。
ubuntu上安裝:
sudo apt-get install tesseract-ocr
非常簡單。
2.pytesseract和PIL(pillow)
pytesseract用來在python中調用tesseract-ocr,PIL(pillow)用來加載圖片,安裝方法如下:
pip3 install pytesseract pip3 install pillow
也非常簡單。
如果安裝pillow的時候報如下錯誤:
ValueError: zlib is required unless explicitly disabled using --disable-zlib, aborting
那么我們更新一下pip即可
sudo pip3 install --upgrade pip
如果pip速度很慢,可以改用國內的源,在命令后面加上 -i http://pypi.douban.com/simple (百度一下一大把),但pillow好像國內鏡像都沒有,只能用蝸牛速度從自帶的源下載咯...
一切准備就緒。
分析網站
我們的目標網址是:http://222.217.19.16:3512/Site/LzsfySite/Default.aspx
預覽圖:
看上去很low啊...心疼我老婆....看來我必須快點完成這個小程序了!
經過簡單的分析可以得到關鍵信息:
1.表單的提交地址:http://222.217.19.16:3512/Site/LzsfySite/Default.aspx
2.驗證碼地址:http://222.217.19.16:3512/Main/AspCode/ZhuChengXu/AuthenImage.aspx
3.表單的格式:
1 { 2 '__LASTFOCUS' : '', 3 '__EVENTTARGET' : 'ctl00$ContentPlaceHolder1$Login1$btnLogin', 4 '__EVENTARGUMENT' : '', 5 '__VIEWSTATE' : __VIEWSTATE, 6 '__EVENTVALIDATION' : __EVENTVALIDATION, 7 'ctl00$ContentPlaceHolder1$Login1$txtUsr' : 用戶名, 8 'ctl00$ContentPlaceHolder1$Login1$txtPwd' : 用戶密碼, 9 'ctl00$ContentPlaceHolder1$Login1$txtYZM' : 驗證碼 10 }
其中4、5、6行是訪問首頁的時候,在首頁的源代碼中返回的參數
但__EVENTARGUMENT常年為空,所以干脆直接寫死空字符串即可;__VIEWSTATE和__EVENTVALIDATION則需要對html進行解析。
7、8、9則對應用戶名、密碼和驗證碼,用戶名密碼可以寫死,驗證碼則需要用到tesseract-ocr進行識別。
4.表單提交的報文頭
1 { 2 'Accept' : b'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 3 'Accept-Encoding' : 'gzip, deflate, lzma', 4 'Accept-Language' : 'zh-CN,zh;q=0.8', 5 'Cache-Control' : 'max-age=0', 6 'Connection' : 'keep-alive', 7 'Content-Length' : 表單內容長度, 8 'Content-Type' : 'application/x-www-form-urlencoded', 9 'Cookie' : cookie內容, 10 'Host' : '222.217.19.16:3512', 11 'Origin' : 'http://222.217.19.16:3512', 12 'Referer' : 'http://222.217.19.16:3512/Site/LzsfySite/Default.aspx', 13 'Upgrade-Insecure-Requests' : '1', 14 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 OPR/38.0.2220.41}' 15 }
其中7行可以根據構造的表單報問題長度來計算,9行需要從cookie中獲取。
主要技術
獲取cookie
python3中獲取cookie的方式很簡單,用http.cookiejar。
cookiejar擴展閱讀:https://docs.python.org/3.0/library/http.cookiejar.html
import urllib.request import urllib.parse import http.cookiejar #登錄的主頁面 hosturl = 'http://222.217.19.16:3512/Site/LzsfySite/Default.aspx' #設置一個cookie處理器,它負責從服務器下載cookie到本地,並且在發送請求時帶上本地的cookie cj = http.cookiejar.LWPCookieJar() cookie_support = urllib.request.HTTPCookieProcessor(cj) opener = urllib.request.build_opener(cookie_support, urllib.request.HTTPHandler) urllib.request.install_opener(opener) #打開登錄主頁面(目的是從頁面下載cookie,這樣我們在再送post數據時就有cookie了,否則發送不成功) hostOpen = urllib.request.urlopen(hosturl) #解析cookie cookieText = '' for item in cj: cookieText = cookieText + item.name + '=' + item.value + '&' cookieText = cookieText[0:-1] print(cookieText)
這樣我們就可以得到cookie啦。
識別驗證碼
這個也簡單,我們先把它下載到本地,然后用pytesseract來解析它:
import urllib.request import pytesseract from PIL import Image #驗證碼圖片地址 checkCodeUrl = 'http://222.217.19.16:3512/Main/AspCode/ZhuChengXu/AuthenImage.aspx' #下載驗證碼 checkCodeOpen = urllib.request.urlopen(checkCodeUrl) data = checkCodeOpen.read() local = open('image.gif', 'wb') local.write(data) local.close() #pytesseract解析 img = Image.open('image.gif') checkCode = pytesseract.image_to_string(img) print(checkCode)
哈哈哈哈就這么簡單暴力~
誒等等!好像有點不對。我們多執行幾次,然后對比一下輸出和圖片
...出現了英文,什么鬼...再來
...這次是正確的。再試試...
又不對了。
多試幾次,發現驗證碼的識別率不太高。
在識別率不高的情況下,那么我們只有開個循環,多識別幾次驗證碼,然后多提交幾次表單即可。——總有一次會正確滴~~
#以下是偽代碼 def 提交方法(): 識別驗證碼 構造表單 提交表單 解析服務器返回報文 if 登錄成功: return true else: return false while not 提交方法(): 等待1000秒 print('登錄成功啦')
解析html
我這里用的是python自帶的HTMLParser,這種簡單暴力的辦法非常好用。 ^_^
from html.parser import HTMLParser import urllib.request #主頁面 hosturl = 'http://222.217.19.16:3512/Site/LzsfySite/Default.aspx' #打開登錄主頁面 hostOpen = urllib.request.urlopen(hosturl) #解析__VIEWSTATE和__EVENTVALIDATION #這里用了HTMLParser的庫。 #自定義的DefaultHTMLParser繼承了HTMLParser #在調用此類型對象的feed方法對二進制字節流解析時, #若遇到tag的開始標簽則會觸發handle_starttag方法, #若遇到tag中的內容時則會觸發handle_data方法 class DefaultHTMLParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.hasLogin = False #如果是input標簽,則判斷其id屬性是否是__VIEWSTATE或__EVENTVALIDATION #如果是二者之一,則在對象.xxxx屬性中存入對應值 #這里假定一定能夠從中讀取到__VIEWSTATE和__EVENTVALIDATION #沒有做錯誤處理 def handle_starttag(self, tag, attrs): iid = '' value = '' if tag == 'input': for attr in attrs: if attr[0] == 'id': iid = attr[1] break;
#用exec來設置屬性值,節省代碼量^_^ if iid in ('__VIEWSTATE', '__EVENTVALIDATION'): for attr in attrs: if attr[0] == 'value': exec('self.' + iid + " = attr[1]") def handle_data(self, data): #根據能否找到跳轉語句判斷是否登陸 if data.find('window.location=\'../../Main/AspCode/ZhuChengXu/ShowSelect.aspx\'') != -1: self.hasLogin = True #get方法,用來獲取屬性值。
#這里偷懶用了eval——eval的效率不太高,但非常省代碼量。
#如果對執行速度要求比較高建議不要用這個方法喔。 def get(self, attr): result = eval('self.' + attr) return result p = DefaultHTMLParser() p.feed(hostOpen.read().decode('GB2312')) print(p.get('__VIEWSTATE')) print(p.get('__EVENTVALIDATION'))
提交表單
根據之前的內容,我們已經獲取了提交登錄表單所需要的一切信息。
所以我們可以開始構造一個表單並提交
1 import zlib 2 import urllib.request 3 import urllib.parse 4 5 #表單提交的url 6 hosturl = 'http://222.217.19.16:3512/Site/LzsfySite/Default.aspx' 7 8 #構造表單 9 formData = { 10 '__LASTFOCUS' : '', 11 '__EVENTTARGET' : 'ctl00$ContentPlaceHolder1$Login1$btnLogin', 12 '__EVENTARGUMENT' : '', 13 '__VIEWSTATE' : '__VIEWSTATE', 14 '__EVENTVALIDATION' : '__EVENTVALIDATION', 15 'ctl00$ContentPlaceHolder1$Login1$txtUsr' : '用戶名', 16 'ctl00$ContentPlaceHolder1$Login1$txtPwd' : '密碼', 17 'ctl00$ContentPlaceHolder1$Login1$txtYZM' : 'xxxx' 18 } 19 #對formData進行url編碼 20 formData = urllib.parse.urlencode(formData) 21 22 #構造登陸用header 23 headers = { 24 'Accept' : b'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 25 'Accept-Encoding' : 'gzip, deflate, lzma', 26 'Accept-Language' : 'zh-CN,zh;q=0.8', 27 'Cache-Control' : 'max-age=0', 28 'Connection' : 'keep-alive', 29 'Content-Length' : len(formData.encode('GB2312')), 30 'Content-Type' : 'application/x-www-form-urlencoded', 31 'Cookie' : 'cookieText', 32 'Host' : '222.217.19.16:3512', 33 'Origin' : 'http://222.217.19.16:3512', 34 'Referer' : 'http://222.217.19.16:3512/Site/LzsfySite/Default.aspx', 35 'Upgrade-Insecure-Requests' : '1', 36 'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36 OPR/38.0.2220.41}' 37 } 38 39 #開始登陸 40 loginRequest = urllib.request.Request(hosturl, formData.encode('GB2312'), headers) 41 loginResponse = urllib.request.urlopen(loginRequest) 42 #返回的數據是壓縮過的,所以要用zlib進行解碼 43 loginResponseData = zlib.decompress( loginResponse.read(), 16+zlib.MAX_WBITS).decode('GB2312') 44 45 print(loginResponseData)
需要注意的是,12-17行以及31行這里要填入前幾節說明的解析的內容。否則服務器會返回500的響應碼喔。
上述內容基本上涵蓋了做一個爬蟲所需要的知識。
擴展內容
但我的工作還沒完,我還得給我老婆生成一個excel,並發送到她郵箱!
所以,下面是關於寫excel和發送email的擴展內容,不感興趣的同學可以跳過啦。
快捷寫入excel
我們可以先手動做一個有標題,但內容為空的excel模板,像這樣:
注意,這里是第四個sheet。然后將其保存為empty.xls
在這里我使用python的xlutils對此報表進行寫入。(擴展閱讀:http://xlutils.readthedocs.org/en/latest/)
先安裝。
sudo pip3 install xlutils
簡單示例:
1 from xlutils.copy import copy 2 import xlrd 3 import xlwt 4 from xlwt.Style import easyxf 5 6 7 #打開文件,formatting_info=true表示讀入單元格style信息 8 file = xlrd.open_workbook('empty.xls',formatting_info=True) 9 #用xlutils.copy的copy方法獲取一個報表對象 10 w = copy(file) 11 12 #定義居中對齊格式示例 13 alignment = xlwt.Alignment() 14 alignment.horz = xlwt.Alignment.HORZ_CENTER 15 style = xlwt.XFStyle() 16 style.alignment = alignment 17 18 #write方法的第一個參數對應要寫入的行數,第二個參數對應要寫入的列數,二者都是從0開始計算的 19 #用居中對齊格式寫入第3張sheet的3行7列單元格 20 w.get_sheet(3).write(2,6, '2016 年 7 月 21 日至2016 年 8 月 20 日', style) 21 #用居中對齊格式寫入第3張sheet的16行3列單元格 22 w.get_sheet(3).write(15,2, '2016 年 8 月 21 日' , style) 23 24 #定義邊框示例 25 borders = xlwt.Borders() 26 borders.left = 1 27 borders.right = 1 28 borders.top = 1 29 borders.bottom = 1 30 style = xlwt.XFStyle() 31 style.borders = borders 32 style.alignment = alignment 33 34 #填充數據 35 for i in range(1, 18): 36 w.get_sheet(3).write(9,i,int(100), style) 37 w.get_sheet(3).write(10,i,int(100), style) 38 39 40 #寫入公式示例 41 for i in range(1,18): 42 column = chr(ord('A')+i) 43 w.get_sheet(3).write(13, i, xlwt.Formula('SUM(' + column + '10:' + column + '13)'),style) 44 45 #保存為新文件 46 w.save('報表.xls')
然后我們就可以得到如下表格啦~~ python真的是非常簡單又暴力...
發送帶有附件的email
這個更簡單...smtplib在ubuntu下的python是自帶的。
示例如下:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication print('准備郵件....') #qq郵箱用戶名和密碼,自帶星號屏蔽 #必須在賬戶設置開啟smtp服務才能登錄 _user = "27*****68@qq.com" _pwd = "***********" _to = "10*****09@qq.com" #初始化消息 msg = MIMEMultipart() msg["Subject"] = "2016年9月份統計報表" msg["From"]= _user msg["To"] = _to #這是文字部分 part = MIMEText("詳見附件...") msg.attach(part) #這是附件部分 part = MIMEApplication(open('報表.xls','rb').read()) #filename最好設置成英文,否則容易出亂碼 part.add_header('Content-Disposition', 'attachment', filename="baobiao.xls") msg.attach(part) #開始發送 print('from ' + _user + ' to ' + _to + '...') #必須要用SSL方式加密 smtp = smtplib.SMTP_SSL('smtp.qq.com') smtp.login(_user, _pwd) smtp.sendmail(_user, _to, msg.as_string()) smtp.quit() print('發送完畢')
所做的一切都非常簡單!所以python是世界上最好的語言! 笑....
綜合上述技術,刪刪改改增增減減,最后成果展示
最后,感謝我老婆,讓我有學習python的動力。
本章完。