抖音App和服務(wù)端交互使用的是HTTPS協(xié)議,使用Fiddler很容易可以捕獲到數(shù)據(jù),如下圖所示。 抖音App和服務(wù)端交互使用的是HTTPS協(xié)議,使用Fiddler很容易可以捕獲到數(shù)據(jù),如下圖所示。 不過想要自己模擬一個(gè)有效的請(qǐng)求可不是那么容易了,因?yàn)樗褂昧撕灻麢C(jī)制,在所有請(qǐng)求中都有as和cp兩個(gè)簽名參數(shù),除非得知簽名算法否則我們無法構(gòu)造出有效的請(qǐng)求。 這里我們使用模擬操作抖音App的方式,讓App幫我們發(fā)出有效的請(qǐng)求,然后我們攔截服務(wù)器的HTTP應(yīng)答數(shù)據(jù),再從中提取我們感興趣的信息。 下面結(jié)合一個(gè)實(shí)際的案例介紹下整個(gè)過程,根據(jù)客戶的需求,要采集一些指定用戶加關(guān)后的系統(tǒng)推薦“你可能感興趣”的數(shù)據(jù)(如下圖所示)用于商品營銷。 點(diǎn)擊“查看更多”可以看到更多的系統(tǒng)推薦用戶列表數(shù)據(jù),如下圖所示。 我們按如下步驟模擬操作App: 1.啟動(dòng)抖音。 2.點(diǎn)擊搜索按鈕。 3.輸入搜索關(guān)鍵詞(抖音用戶ID),點(diǎn)擊搜索。 4.找到匹配的用戶,點(diǎn)擊關(guān)注。 5.點(diǎn)擊系統(tǒng)推薦“查看更多”,模擬多次向上滑動(dòng)屏幕,直至數(shù)據(jù)加載完畢(屏幕出現(xiàn)“暫時(shí)沒有更多了”)。 于此同時(shí),我們使用抓包腳本(可以使用Fiddler的Customize Rules,也可以使用Mitmproxy),捕獲并過濾URL中含有/user/recommend/的HTTP應(yīng)答數(shù)據(jù),從JSON數(shù)據(jù)中提取系統(tǒng)推薦的用戶信息(如下圖所示)。 模擬操作抖音App的腳本核心代碼如下所示: view plaincopy to clipboardprint? from com.dtmilano.android.viewclient import ViewClient def search_douyin_for_recommend_user(douyin_id): """采集指定抖音賬號(hào)的關(guān)注推薦數(shù)據(jù) """ log(u'準(zhǔn)備采集"{}"對(duì)應(yīng)的關(guān)注推薦數(shù)據(jù)'.format(douyin_id)) # 連設(shè)備 serialno = None if serialno: os.system('adb connect {}'.format(serialno or '')) time.sleep(3) device, serialno = ViewClient.connectToDeviceOrExit(serialno=serialno) vc = ViewClient(device, serialno, autodump=False) # 強(qiáng)制關(guān)閉抖音 log(u'強(qiáng)制關(guān)閉抖音.') device.shell('am force-stop com.ss.android.ugc.aweme') time.sleep(2) # 啟動(dòng)抖音 log(u'啟動(dòng)抖音.') device.shell('am start -n com.ss.android.ugc.aweme/.main.MainActivity') time.sleep(5) # 暫停視頻播放 log(u'點(diǎn)擊屏幕,暫停視頻播放.') device.touch(514, 1048) # 點(diǎn)擊搜索按鈕 vc.dump() search_btn = vc.findViewById('com.ss.android.ugc.aweme:id/amj') if search_btn: log(u'點(diǎn)擊搜索按鈕,跳轉(zhuǎn)到搜索頁面.') search_btn.touch() vc.dump() # 點(diǎn)擊搜索輸入框 search_input = vc.findViewById('com.ss.android.ugc.aweme:id/ad_') if search_input: log(u'點(diǎn)擊搜索框,準(zhǔn)備輸入關(guān)鍵詞.') search_input.touch() # 輸入抖音ID log(u'輸入搜索關(guān)鍵詞: {}.'.format(douyin_id)) device.type(douyin_id.encode('UTF-8')) # 點(diǎn)擊搜索按鈕 search_btn = vc.findViewById('com.ss.android.ugc.aweme:id/cp8') if search_btn: log(u'提交搜索.') search_btn.touch() time.sleep(2) vc.dump() ## 切換到用戶 #user_tab = vc.findViewWithText(u'用戶') #user_tab.touch() # 找到匹配的 matches = [] def find_matches(view): if view.getClass() == 'android.widget.TextView': text = view.getText() if douyin_id.lower() in text.lower(): # 找到匹配的了 log(u'找到匹配的: {}'.format(text)) matches.append(view) else: #print text pass vc.traverse(transform=lambda view: find_matches(view)) if matches: # 有沒有已關(guān)注按鈕 btn = vc.findViewWithText(u'已關(guān)注') if btn: # 先取消關(guān)注 log(u'之前關(guān)注過,先取消關(guān)注.') btn.touch() time.sleep(1) user_matched = matches[0] log(u'點(diǎn)擊進(jìn)入個(gè)人主頁.') user_matched.touch() time.sleep(1) # 點(diǎn)關(guān)注 vc.dump() follow_btn = vc.findViewById('com.ss.android.ugc.aweme:id/aei') if follow_btn: # 點(diǎn)擊關(guān)注 log(u'點(diǎn)擊關(guān)注') follow_btn.touch() time.sleep(1) # 點(diǎn)擊查看更多 vc.dump() viewmore_btn = vc.findViewById('com.ss.android.ugc.aweme:id/bqn') if viewmore_btn: # 點(diǎn)擊查看更多 log(u'點(diǎn)擊查看更多系統(tǒng)推薦') viewmore_btn.touch() time.sleep(1) i = 0 while True: # 上滑動(dòng) device.drag((345, 1762), (345, 550), duration=100) log(u'上滑以加載更多') i += 1 if i % 5 == 0: # 拖動(dòng)10次判斷一下是否還有更多 vc.dump() if vc.findViewWithText(u'暫時(shí)沒有更多了'): log(u'暫時(shí)沒有更多了, "{}"的關(guān)注推薦數(shù)據(jù)采集完畢.'.format(douyin_id)) # 采集成功了 return True failed_tip = vc.findViewWithText(u'加載失敗,點(diǎn)擊重試') if failed_tip: log(u'加載失敗,點(diǎn)擊重試.') failed_tip.touch() else: # 沒有找到查看更多按鈕 log(u'沒有找到查看更多按鈕') else: # 沒有找到加關(guān)注按鈕 log(u'沒有找到加關(guān)注按鈕') else: # 沒有找到匹配的用戶 log(u'沒有找到匹配的用戶') else: # 沒有找到搜索提交按鈕 log(u'沒有找到搜索提交按鈕.') else: # 沒有找到搜索輸入框 log(u'沒有找到搜索輸入框.') else: # 沒有找到搜索按鈕 log(u'沒有找到搜索按鈕.') 上述腳本的運(yùn)行截圖如下所示:
|
|