TOC
王者营地数据爬取
这两周应大佬吩咐去王者营地 app 爬取相关数据,在这里复盘一下自己在爬取过程中的思考。
目标
爬取 app 中每局的数据至 excel ,包含战局比分,事件时间名称,以及最后死亡时间;大致效果如下:
执行过程
在此之前,没有任何的爬虫经验,对于爬虫仅有的认知是如果目标程序是通过 http 协议通信,则可以通过数据抓包工具,抓取 http 数据包分析内容,找到想要的数据;而后通过模拟 http 请求来爬取需要的数据;
于是自己大概捋了一下执行过程:
- 了解并学会使用一个抓包工具;
- 尝试学习并完成一个简单的 python 爬虫,先爬一爬,热热身;
- 开始抓包分析;
- 爬取结果;
捋好思路就开始干了,了解了一下抓包工具,发现了 Fiddle,就立刻决定用它了。原因无他,看到过身边的同事在用;于是开始学习工具,找到了一篇写的不错的入门文章,半小时就能上手了。
接着了解了一下 python 爬虫小程序,模仿教程写了下面的第一个 python 爬虫小程序了;
## 获取指定网页的 html 数据
import urllib.request
data = urllib.request.urlopen("https://read.douban.com/provider/all").read()
data = data.decode("utf-8")
## 正则表达式分析获取 html 文件内容
import re
pat = '<div class="name">(.*?)</div>'
data = re.compile(pat).findall(data)
## 将数据写入文件中
fs = open("e:/document/1.txt","w")
for i in range(0,len(data)):
fs.write(data[i]+"\n")
fs.flush()
开始抓包,发现 app 抓包并不像浏览器抓包那样简单,花了两天时间折腾环境,尝试抓包依旧没有任何进展;转化一下思路,app 中有微信转发功能,将战局转发至微信,而后通过浏览器打开,这样就能够处理了。
浏览器打开的页面效果如下:
从页面中可以轻松找到战局比分,事件发生时间,事件名称等信息,且所有事件信息都是已经再 html 中生成好了,可通过正则表达式直接分析 html 获取。
而获取死亡时间需要操作步骤如下:
- 点击我方详细数据,获取死亡时间
- 点击敌方详细数据,获取死亡时间
- 比对获取最后死亡时间
问题来了,如何模拟点击,此前听说过 selenium 自动化测试,印象中它能够完成这样的功能;于是经过两个晚上的现学现用,发现虽然能够达到目的,但是操作过于繁琐,爬取效率很低,决定放弃,重新另找思路; html 中死亡时间的数据,需要点击「详细信息」才会更新,如何能够不通过点击获取到数据? 想到能不能像分析 http 数据包一样,分析页面 js 逻辑,看看最后数据是如何获取的,再模拟来获取即可。于是就开始了 js 源码的分析。
分析 js
首先通过 Chrome debug 窗口的 EventListener 找到点击事件的 js 代码入口
经过关键字搜索查找,终于找到了获取死亡时间的 js 函数
分析这个函数,只要弄清楚参数 t 和 eventInfo 的数据结构以及来源则可以自己实现逻辑了。然后查看了一下 localStorage 中的存储,意外发现了数据内容,
接着就要分析如何拿到 key 值,通过检索关键字 「analyze_」 发现了相应代码,从代码可以看出 key 只需要获取 url 的参数拼接即可完成。
最后分析 value 的内容,看看如何获取指定的死亡时间; 从上面找到的 getDeadTime() 函数,可以知道死亡时间是从 eventInfo.battle.killInfo 中获取;对比 localStorage 数据也发现了相应的结构;并且根据页面数据比对,发现了事件数据即为 keyEventArr;这样更加确认了找到的 killInfo 就是需要的死亡事件数据。
死亡时间数据已确认拿到,最后一步,怎么区分死亡时间所属事件? 从 getDeadTime() 方法发现是通过 objectId 匹配死亡时间与事件的,自然想到如何获取 obejctId,经过一系列 debug 依旧没有进度;而后想到了通过事件时间来匹配,毕竟每个事件发生时间是不会重复的;观察数据发现 localStorage 中的时间是一串数字,我们获取的时间经过了转换;继而开始找时间转换的函数,发现就在 getDeadTime() 中的 getTimeStr() 方法中。简单搜索就找到了相应的代码了。至此,这个数据链路也就疏通了;
最后,捋一下编码思路,就可以开始撸码了。
- 通过 selenium 用 Chrome 打开链接;
- 获取 localStorage key 值;
- 获取 localStorage value 值;
- 通过 selenium 获取战局比分,事件时间,事件名称;
- 通过事件时间与 localStorage value 值匹配获取死亡时间;
- 将数据写入 excel;
代码
import time
import re
import os
import math
import json
from selenium import webdriver
from urllib import parse
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
class GameData:
gameNo = 0
blueScore = 0
redScore = 0
eventTime = []
eventName = []
killTime = []
def __init__(self,gn,b,r,et,en,kt):
self.gameNo = gn
self.blueScore = b
self.redScore = r
self.eventTime = et
self.eventName = en
self.killTime = kt
def getUrlParm(driver,url):
return parse.parse_qs(parse.urlparse(url).query)
def getLocalStorage(params):
return r'analyze_"{}"'.format(params['gamesvrentity'][0] + params['relaysvrentity'][0] + params['gameseq'][0]+params['playerid'][0])
def getKeyEventList(localStorage):
command = r"return localStorage.getItem('{}')".format(localStorage)
eventList = json.loads(driver.execute_script(command))
return eventList['keyEventArr']
def getEventKillTime(eventList,eventTime):
result = '-'
for event in eventList:
if (event['eventType'] == 'battle'):
if(convertTime(event['battle']['startTime']) == eventTime):
result = getMaxTimeInKillInfo(event['battle']['killInfo'])
return result
def getMaxTimeInKillInfo(killInfos):
maxTime = 0
for killInfo in killInfos:
if (maxTime < int(killInfo['time'])):
maxTime = int(killInfo['time'])
return convertTime(maxTime)
def getData(driver,url):
blueScore = driver.execute_script('return document.getElementsByClassName(\"blue\")[0].innerHTML')
redScore = driver.execute_script("return document.getElementsByClassName(\"red\")[0].innerHTML")
f = 'function a(url) {var result = [];elements = document.getElementsByClassName(url);for(var i = 0;i < elements.length;i++) {result[i] = elements[i].innerText;}return result;}'
script = f + 'return a(\"time_txt\")'
eventTime = driver.execute_script(script)
script = f + 'return a(\"txt\")'
eventName = driver.execute_script(script)
killTime = []
urlParam = getUrlParm(driver,url)
keyEventList = getKeyEventList(getLocalStorage(urlParam))
for et in eventTime:
killTime.append(getEventKillTime(keyEventList,et))
return GameData(urlParam['gameseq'][0],blueScore,redScore,eventTime,eventName,killTime)
def createExcel(gamedataList):
wb = Workbook()
ws = wb.active
writeTitle(ws)
for i in range(0,len(gamedataList)):
writeGameData(ws,gamedataList[i])
wb.save(filename = 'result.xlsx')
def writeTitle(ws):
ws.title = '战报分析'
ws.cell(row = 1,column = 1,value = '战局')
ws.cell(row = 1,column = 2,value = '战局比分(blue:red)')
ws.cell(row = 1,column = 3,value = '事件时间')
ws.cell(row = 1,column = 4,value = '事件名称')
ws.cell(row = 1,column = 5,value = '死亡时间')
ws.column_dimensions['A'].width = 17
ws.column_dimensions['B'].width = 19.5
ws.column_dimensions['D'].width = 17
def writeGameData(ws,gamedata):
beginIndex = ws.max_row + 1
for i in range(0,len(gamedata.eventTime)):
ws.cell(row = beginIndex + i,column = 1,value = gamedata.gameNo)
ws.cell(row = beginIndex + i,column = 2,value = gamedata.blueScore+':'+gamedata.redScore)
ws.cell(row = beginIndex + i,column = 3,value = gamedata.eventTime[i])
ws.cell(row = beginIndex + i,column = 4,value = gamedata.eventName[i])
ws.cell(row = beginIndex + i,column = 5,value = gamedata.killTime[i])
def getURL():
data = open('.\\source.txt','r',encoding='UTF-8').read()
pat = r'(https://.*&playerid=[0-9a-zA-Z]{9})'
return re.compile(pat).findall(data)
def convertTime(t):
t = int(t) / 1000
e = math.floor(t / 60)
n = math.floor(t - 60 * e)
r = ""
r = "0" + str(e) if e < 10 else str(e)
r += ":0" + str(n) if n < 10 else ":" + str(n)
return r
if __name__ == "__main__":
urlList = getURL()
driver = webdriver.Chrome()
driver.implicitly_wait(5)
gameDataList = []
for url in urlList:
driver.get(url)
time.sleep(4)
gameDataList.append(getData(driver,url))
createExcel(gameDataList)
driver.close()