最近的項(xiàng)目在做一些數(shù)據(jù)方面的集成,五花八門的系統(tǒng)對應(yīng)的接口更是千奇百怪,數(shù)據(jù)集成的過程總結(jié)成八個字就是:逢山開路,遇水架橋。
恰好這兩天碰到一個問題,我們要集成的WEB系統(tǒng)沒有提供專門的數(shù)據(jù)集成接口,沒有API可以調(diào),數(shù)據(jù)庫更是不讓訪問,萬般無奈之下,我在想是否可以用python自動爬取頁面。web頁面有SSO,用的應(yīng)該是開源的CAS框架,并且后面的頁面全都是由動態(tài)JS、AJAX異步加載進(jìn)去的,這顯然不像普通的靜態(tài)頁面那樣直接用Scrapy上去就是一頓干,要完美的模擬登陸動作并且還要爬取后面的動態(tài)內(nèi)容,對頁面結(jié)構(gòu)和爬取內(nèi)容的分析是必不可少的。
工具
- Chrome
頁面分析工具非常簡單,只需要最新版本的Chrome瀏覽器即可 - python3
- requests
分析登錄頁面
打開登錄頁面,按F12打開Chrome自帶的分析工具,在Network選卡上可以看到當(dāng)前瀏覽器顯示頁面和提交登錄信息的詳情,如下圖

從截圖上可以看到,當(dāng)我們訪問app/這個url的日志,因?yàn)槲覀冞€沒有登錄過,SSO會把我們自動重定向到登錄頁面,所以http status是302重定向。
接下來我們在頁面上輸入賬號密碼,點(diǎn)登錄按鈕,通過頁面追蹤分析并模擬整個登錄過程,這個過程要尤其仔細(xì),因?yàn)楹芏郈AS在登錄頁面上埋了很多隱藏的標(biāo)記,一個地方模仿的不對可能就會登錄失敗然后又被重定向到開始的地方。

從分析可以看到,登錄按鈕提交的時候會用POST方式提交一個表格,而表格里面的除了賬號密碼等顯眼的字段以外還有一個lt,經(jīng)驗(yàn)告訴我們這個字段應(yīng)該隱藏在之前的登錄頁面上,用來校驗(yàn)登錄頁面的合法性,所以我們要從登錄頁面上找到并提取這個信息。同時還要注意http的消息頭,最好按照瀏覽器抓取的消息頭去構(gòu)造,因?yàn)榫W(wǎng)站同樣會校驗(yàn)這里面的信息。
下面是登錄的主要代碼,我們基于python3和requests包來處理https訪問請求,模擬瀏覽器的行為將認(rèn)證需要的信息構(gòu)造出來發(fā)給網(wǎng)站。
import requests
import urllib3
from lxml import etree
from ows_scrapy.ows_spider.write_csv import write_list_to_csv
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
s = requests.session()
s.trust_env = False # faster
cookie = None
username = 'username'
password = 'pwd'
def login():
header = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive',
'Cookie': 'JSESSIONID=73E10849812940333A4AD2A2ABAEFB7D8CFF3E76A45340FFB687FD587D2EB97A49FC5F156D09DB1E17F129465AB8D8EBACEC',
'Host': '<your web url>',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}
form_data = {
'username': username,
'password': password,
'_eventId': 'submit',
'pwdfirst': password[0:2],
'pwdsecond': password[2:5],
'pwdthird': password[5:]
}
login_url = 'https://<your web url>.com/app/'
print('login...')
res = s.get(login_url, headers=header, verify=False)
global cookie
cookie = res.cookies # 注意要保存cookie
print("get response from ows {0}, http status {1}".format(login_url, res.status_code))
login_url = res.url
header['Referer'] = login_url
header['Cookie'] = 'JSESSIONID=' + cookie['JSESSIONID']
form_data['lt'] = str(etree.HTML(res.content).xpath('//input[@name="lt"]/@value')[0]) #用xpath從頁面上提取lt
res = s.post(login_url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False)
cookie = res.cookies
print("post login params to {0}, http status {1}, cookie : {2}".format(login_url, res.status_code, cookie))
res = s.send(res.next, allow_redirects=False, verify=False)
cookie = res.cookies
print("redirect to {0}, http status {1}, cookie : {2}".format(login_url, res.status_code, cookie))
if res.status_code == 200 or res.status_code == 302:
if cookie is not None and cookie.get(name='JSESSIONID', path='/app') is not None :
print('Successful login.')
else:
print('WRONG w3id/password!')
sys.exit(0)
這里要特別注意一點(diǎn),因?yàn)閔ttp是無狀態(tài)的,web頁面要保存登錄狀態(tài)需要用到cookie,等成成功以后頁面的response里面會包含一個帶有有效標(biāo)記的cookie,登錄最終的目標(biāo)就是獲取并保存這個有效的cookie,這樣后續(xù)的訪問就不會被重定向到登錄頁。
在requests的方法里面只要向這樣吧cookie帶到請求里即可
res = s.post(login_url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False)
分析動態(tài)內(nèi)容頁面
在動態(tài)頁面里,頁面上顯示出來的內(nèi)容往往都是js或者AJAX異步獲取到的,跟靜態(tài)html頁面的分析過程有明顯的不同。用Chrome的分析工具也可以很容易的獲取到該信息。

在動態(tài)頁面加載完成后,我們從所有的請求中過濾XHR類型,從中找到我們要的那一次請求,然后在該請求的Preview里面就可以看到完整的相應(yīng)信息,同時該請求的URL也可以從Headers選卡中得到。
接下來要做的事情跟上面類似,構(gòu)造報(bào)文模擬瀏覽器向該網(wǎng)站發(fā)送請求:
def get_content(order_id):
form_data = {
'roarand': 'BW09el5W3mW2sfbGbtWe7mWlwBsWqXg6znppnqkW3woJ5fcz5DnhfWXGonqkLsd0',
'start': '0',
'limit': '20',
'orderid': order_id,
'serviceId': 'test_gscsocsecurityincidentmanage_log_getList2'
}
header = {
'Accept': 'text/plain, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
'Host': '<your web>.com',
'Origin': 'https://<your web>.com',
'Referer': 'https://<your web>.com/app/104h/spl/test/ID_480_1511441539904_workflowdetail.spl?orderid=SOC-20180220-00000003',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
print('Start to scan order id ' + order_id)
url = 'https://<your web>.com/app/pageservices/service.do?forAccessLog={serviceName:test_gscsocsecurityincidentmanage_log_getList2,userId:571bdd42-10ca-4ce1-b41c-8a3f6632141f,tenantId:104h}&trackId=fec68f8e-f30a-4fa1-a8b1-41d3dd11fa4c'
res = s.post(url, headers=header, params=form_data, cookies=cookie, allow_redirects=False, verify=False) #要加載上面登錄成功的cookie
print(res.content)
return res
要點(diǎn)其實(shí)就是從XHR里找到請求的URI,構(gòu)造請求報(bào)文頭和提交表格,最后務(wù)必要加上登錄成功的cookie,否則會被重定向到登錄頁面。
抓取動態(tài)頁面的方法還有很多,這種方法依賴的包相對較少,代碼比較靈活,在爬取復(fù)雜的登錄頁面的時候效果比較好,只是在分析頁面登錄機(jī)制的時候要尤其細(xì)心。