python不用多介紹啦,前幾天看新聞?wù)fpython在浙江都列入高考了,可見這門語言有多火。但是博主一直是秉承著需要什么就拿來用什么的想法,一直也沒怎么接觸python(之前實(shí)在沒遇到什么case push博主去擼擼python。
背景
最近偶然遇到這么一個(gè)case,博主所在的APM項(xiàng)目,非常依賴于不同機(jī)型不同iOS版本的測(cè)試,但是手頭的機(jī)器實(shí)在過少,所以一直使用Testin的測(cè)試服務(wù)。最近新版本的測(cè)試結(jié)果出來了,網(wǎng)站提供的下載入口長下面這樣:

點(diǎn)擊列表的詳情進(jìn)去后才能找到下載日志的入口:

以前每次測(cè)試完成后都需要手動(dòng)每個(gè)設(shè)備點(diǎn)進(jìn)去下載每個(gè)日志,足足需要這樣重復(fù)操作去下載好幾十個(gè)日志,作為一個(gè)懶漢程序員實(shí)在不能忍受這么浪費(fèi)生命的事情:)
這不最近python這么火么,趁機(jī)上手?jǐn)]一次python爬蟲,希望執(zhí)行一個(gè)腳本就能下載全部的日志文件。
python入門的教程是在廖雪峰大神的博客看的,快速了解了下python的語法,腳本語言不用深究,能拿來干活就行。具體網(wǎng)上了解python爬蟲的正確姿勢(shì)的過程在這就不表了。
分析路徑
工欲善其事必先利其器,python爬蟲既然是和網(wǎng)絡(luò)打交道那charles是必不可少的,除此之外還有chrome。
我們倒著分析,打開charles,然后再瀏覽器中一步步點(diǎn)擊網(wǎng)頁,最終到達(dá)包含下載日志入口的設(shè)備詳情頁后,這里有個(gè)小技巧,我們看下載按鈕的名字叫“下載日志zip包”便可以在charles中(command + F)全局搜索這個(gè)關(guān)鍵字:

果然出現(xiàn)了一條結(jié)果,雙擊進(jìn)去。

我們看到這里包含了日志下載的鏈接,同時(shí)我們發(fā)現(xiàn)了這條post請(qǐng)求同時(shí)傳入了兩個(gè)參數(shù)adaptId和reportId。我們記住這個(gè)url。
再往回倒,我們是點(diǎn)擊詳情按鈕進(jìn)來這個(gè)頁面的,同樣的方法:


果然在這個(gè)列表這個(gè)頁面發(fā)現(xiàn)了列表中設(shè)備的adaptId和reportId,這個(gè)網(wǎng)頁的post請(qǐng)求包含了一系列篩選的參數(shù),在這里是adaptId和curPage從名字我們已經(jīng)能猜到參數(shù)的含義,我們記住這個(gè)url。
那么,現(xiàn)在只剩下adaptId了。
emmm..這時(shí)候在charles的全局搜索中搜索出的adaptId已經(jīng)包含了太多的結(jié)果,沒有參考價(jià)值。TestIn給我們發(fā)的郵件中有這個(gè):

大膽猜測(cè)這個(gè)鏈接返回的結(jié)果中包含了adaptId,果然在charles的返回的結(jié)果中找到了這個(gè)參數(shù)。

注意看圖,這是一個(gè)302重定向的請(qǐng)求,第二條請(qǐng)求的結(jié)果才是最后的結(jié)果。
登錄
郵件中查看報(bào)告的鏈接是登錄賬號(hào)密碼后才能查看的?,F(xiàn)在的web服務(wù)器一般用cookie來標(biāo)識(shí)瀏覽器的請(qǐng)求,這里關(guān)于cookie的知識(shí)不過多介紹了,不了解的讀者自行g(shù)oogle。
這里博主陷入了一個(gè)坑,因?yàn)閏ookie的機(jī)制是在登錄的請(qǐng)求中,服務(wù)器校驗(yàn)賬號(hào)密碼后會(huì)在response的header中加入set-cookie的字段,把cookie放入其中,博主的思路是模擬登錄后,拿到這個(gè)set-cookie的字段然后再在后續(xù)的請(qǐng)求的request的header中加上cookie字段。然而理論是理論,實(shí)踐總是有點(diǎn)差異,這里這種方式死活行不通,我猜測(cè)這里web服務(wù)器的cookie的值設(shè)定有更多的邏輯,博主的方式一定是少設(shè)置了某個(gè)值。
被坑了半天后,博主發(fā)現(xiàn)其實(shí)python的requests庫已經(jīng)有接口幫忙做了cookie校驗(yàn)這個(gè)事情!類似下面這樣:
s = requests.session()
s.post(login_url, login_param)
# 后續(xù)用s(session)發(fā)起的請(qǐng)求自動(dòng)附帶cookie信息
這個(gè)教訓(xùn)告訴我們凡事先google,看有沒有現(xiàn)成的解決方案:)
登錄的接口該怎么找呢?? 這里有個(gè)技巧(別問博主第一次寫爬蟲為什么知道這么多技巧)
在chrome的登錄頁右鍵檢查,然后故意輸錯(cuò)賬號(hào)密碼,嘗試登錄。

如圖中紅框所示,點(diǎn)擊network,然后在底下就能找到登錄接口的url,同時(shí)能找到post請(qǐng)求的表單數(shù)據(jù)FormData。這樣子登錄的參數(shù)也出來了email和pwd。
所有的邏輯走通后下面這張圖就出來了:

解析HTML內(nèi)容&邏輯代碼
上面截圖的請(qǐng)求返回結(jié)果的HTML的紅圈中有我們需要的內(nèi)容,所以如何在html中解析內(nèi)容也是十分關(guān)鍵的,這里采用python自帶的Xpath庫,十分的好用,教程看這里。
下面兩行代碼通過//input[@id='adaptId']/@value這個(gè)字符串就取到了adaptId,可以說是十分簡(jiǎn)單粗暴了,這里不詳細(xì)展開Xpath的用法了。
# 2.獲取adaptid
report_res = s.get(report_url)
adaptId = etree.HTML(report_res.text).xpath("http://input[@id='adaptId']/@value")[0]
還剩下一下新建,刪除文件夾,解壓zip文件的操作,這里就不細(xì)說了,網(wǎng)絡(luò)庫用的是requests。這里把Testin的測(cè)試報(bào)告當(dāng)做參數(shù)傳入,這個(gè)鏈接每次測(cè)試都不一樣,其他的鏈接都要固定的。下面是最后的腳本代碼:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 需傳入一個(gè)參數(shù) >>>>>> 報(bào)告鏈接的url字符串
# 舉例 >>>>>> ./getTestinLog.py http://realauto.testin.cn/xxxxx
import os
import sys
import shutil
import urllib.request
import zipfile
import glob
import requests
from lxml import etree
# 一定要穿一個(gè)郵件中報(bào)告鏈接的參數(shù)url!
if len(sys.argv) < 2:
print('請(qǐng)輸入郵件中報(bào)告的url作為參數(shù)!')
sys.exit()
# 郵件中查看鏈接報(bào)告的url 用來獲取adaptId
report_url = sys.argv[1]
login_url = 'xxxxxx'
page_url = 'xxxxxx'
device_detail_url = 'xxxxxx'
login_param = {'email': 'xxx',
'pwd': 'xxx'}
if os.path.isdir('./log_tmp'):
shutil.rmtree('./log_tmp')
if os.path.isdir('./log_final'):
shutil.rmtree('./log_final')
os.mkdir('./log_tmp')
os.mkdir('./log_final')
# 1.模擬登陸操作獲得cookie
s = requests.session()
s.post(login_url, login_param)
# 2.獲取adaptid
report_res = s.get(report_url)
adaptId = etree.HTML(report_res.text).xpath("http://input[@id='adaptId']/@value")[0]
print('>>>>>>adaptId ' + adaptId)
# 3.依次訪問5個(gè)有設(shè)備信息的列表
for page in range(5):
page = page + 1
print('>>>>>>this is page:', page)
page_param = {'adaptId': adaptId, 'curPage': page}
page_res = s.post(page_url, data=page_param)
reportDetail = etree.HTML(page_res.text).xpath('//a[@style="background-color:#37bc9b;"]/@href')
# sub_url是一個(gè)列表中每個(gè)設(shè)備條目的信息
# 4. 從sub_url中獲取每個(gè)設(shè)備的下載鏈接
for sub_url in reportDetail:
reportId = sub_url.split('&')[2].split('=')[1]
detail_param = {'adaptId': adaptId,
'reportId': reportId}
detail_res = s.post(device_detail_url, data=detail_param)
download_info = etree.HTML(detail_res.text).xpath("http://div[@class='right_btn']/a[2]/@href")[0]
download_info_arr= download_info.split('\'')
download_url = download_info_arr[1]
download_name = download_info_arr[3]
# 獲取到了下載的鏈接
print('download:' + download_url + ' name:' + download_name)
# 5.下載日志zip文件
urllib.request.urlretrieve(download_url, download_name)
# 解壓
with zipfile.ZipFile(download_name, "r") as zip_ref:
zip_ref.extractall('./log_tmp')
# 重命名
for tmp_name in glob.glob('./log_tmp/*'):
os.rename(tmp_name, './log_final/'+download_name.split('zip')[0]+'log')
# 刪除zip文件
os.remove(download_name)
# 刪除臨時(shí)文件夾
shutil.rmtree('./log_tmp')
運(yùn)行結(jié)果
直接上圖吧:


總結(jié)
博主認(rèn)為在python爬蟲中最重要的是想清楚你的目標(biāo)和達(dá)到這個(gè)目標(biāo)所需要的路徑,也就是用charles和chrome分析的過程,至與網(wǎng)絡(luò)操作和文件操作的接口現(xiàn)查python接口就好了,最重要的是分析的邏輯。
同時(shí)python現(xiàn)在的庫非常豐富,python用來處理數(shù)據(jù)也是很方便,善用python能自動(dòng)化很多東西。
雖然寫這個(gè)爬蟲腳本花了點(diǎn)時(shí)間,但是以后每次獲取日志就不需要手動(dòng)一個(gè)個(gè)去點(diǎn)擊下載了,博主認(rèn)為這是非常值得去做的一件事。日常的開發(fā)中如果有一些能自動(dòng)化的事情還是交給腳本去做,能大大的提高工作效率。