概述
可关注微信订阅号 loak 查看实际效果。
代码已托管github,地址为:https://github.com/luozhengszj/LOLGokSpider ,包括了项目的所有代码。
本文主要介绍以下内容:
- 使用selenium 和 requests爬取王者荣耀官网、王者营地APP数据,使用BeautifulSoup和正则进行数据解析;
- 爬取的IP代理使用了redis搭建的代理池;
- 数据通过pymongo保存到Mongodb中,并且通过提供接口进行数据的查询与显示;
- 项目运行日志记录功能logging;
- centos服务器的定时任务;
- 数据的查询与显示可通过订阅号进行方便的查询。
爬虫实现
跟上一篇博文类似,我们想要爬取某些数据,同样首先要分析网站、APP程序的页面,确定要爬取的元素->分析网站及元素的加载->抓取数据
-
确定爬取的元素
我们百度搜索王者荣耀,进入其官网,可以发现有一个下拉选项“英雄资料”,地址为:https://pvp.qq.com/web201605/herolist.shtmlweb201605/herolist.shtml , 在这里我们可以看到所有英雄。
点击某个英雄,进入详情页面,我们可以发现官网有对该英雄的简介、技能、加点、出装、铭文的推荐。所以我们可以确定这些内容可以从这里进行爬取。再进行分析“王者营地”APP(博主分析时,版本为:3.44.204),选择 战绩->游戏工具->英雄榜,我们可以看到该APP对实时数据的显示,主要包括了上下中辅野的热度、胜率、登场率、ban率、技能、出装、铭文、克制、被克制的内容。
通过上面的分析,确定了在王者官网爬取英雄的铭文、出装、技能,在王者营地APP爬取 热度、胜率、登场率、ban率、克制、被克制 -
分析网站及元素的加载
- 王者荣耀官网的数据
王者荣耀官网的英雄列表:https://pvp.qq.com/web201605/herolist.shtml , 可以直接通过requests进行获取。
至于英雄的详细页面,例如 曜 :https://pvp.qq.com/web201605/herodetail/522.shtml , 分析页面我们可以发现,其实页面的大多数信息都是可以直接使用requsts进行请求获取,但是通过分析发现,出装的推荐,是通过ajax进行动态加载的,为了简单,直接采用了selenium进行操作了 - 王者营地
王者营地app的数据抓取,我们首先要安装fiddler,通过桌面的fiddler的证书,安装到手机与电脑同一wifi的证书中,并进行代理的设置,及可以通过fiddler分析请求。具体操作可以看我之前写过的博客:https://blog.csdn.net/luoz_java/article/details/90340115 , 里面介绍有详细的操作步骤。
设置完成后,打开APP,进入 战绩->游戏工具->英雄榜 ,分析fiddler显示的请求:
https://ssl.kohsocialapp.qq.com:10001/hero/getdetailranklistbyid , 返回的json数据包含了 热度、胜率、登场率、ban率;
https://ssl.kohsocialapp.qq.com:10001/hero/getheroextrainfo , 返回的json数据包含了克制、被克制等信息。
- 王者荣耀官网的数据
-
抓取数据
通过以上的分析,我们直接可以编写代码了。为了简单,王者荣耀官网数据的抓取,直接使用chrome selenium,而王者营地的则采用request模块。
官网爬取某一英雄详细信息:def get_one_hero_detail(hero_url, gok_hero): proxy = get_proxy() chrome_options.add_argument('--proxy-server=http://' + proxy) browser.get(hero_url) tmp1 = wait.until( EC.presence_of_element_located( (By.XPATH, '/html/body/div[3]/div[2]/div/div[2]/div[1]/div[2]/p[1]/span'))).text tmp2 = browser.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[2]/div[1]/div[2]/p[3]/span').text gok_hero.skill = ['主:' + tmp1, '副:' + tmp2] zh_skill = browser.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[2]/div[1]/div[2]/p[5]/span').text gok_hero.zh_skill = zh_skill mingwen1 = browser.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[3]/div[2]/ul/li[1]/p[1]/em').text mingwen2 = browser.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[3]/div[2]/ul/li[2]/p[1]/em').text mingwen3 = browser.find_element_by_xpath('/html/body/div[3]/div[2]/div/div[1]/div[3]/div[2]/ul/li[3]/p[1]/em').text gok_hero.mingwen = [mingwen1, mingwen2, mingwen3] browser.implicitly_wait(5) builds = browser.find_elements_by_xpath('//*[@id="Jname"]') list_tmp = [] for item in builds: list_tmp.append(item.get_attribute("innerHTML")) gok_hero.first_build = list_tmp[:6] gok_hero.second_build = list_tmp[6:12] time.sleep(1) return gok_hero
王者营地APP根据位置爬取数据及解析的代码:
all_hero_msg = [] def get_hero_rank(lu): retry_count = 5 proxy = get_proxy() while retry_count > 0: try: # 获取英雄列表 proxies = { "http": "http://" + proxy } data_tmp = gok_interface_log['post_data_20190623'] data_tmp.update({'position': lu}) herohtml = requests.post(url=gok_interface_log['post_url_20190623'], data=data_tmp, proxies=proxies).text return herohtml except urllib.error.URLError as e: if isinstance(e.reason, socket.timeout): retry_count -= 1 if retry_count == 2: # 出错3次, 删除代理池中代理 delete_proxy(proxy) proxy = get_proxy() except Exception as e: log.logger.error('get_hero_rank爬取失败!' + str(e)+lu) if retry_count == 3: delete_proxy(proxy) proxy = get_proxy() return None def parse_hero_rank(rank_data, version, position): rank_data = ast.literal_eval(rank_data) hero_rank_list = str(rank_data.get('data').get('list'))[1:-1] hero_items = ast.literal_eval(hero_rank_list) for item in hero_items: gok = GokClass() gok.version = version gok.day = gok_config['GOK_INSERT_TIME'] gok.heroid = item['heroId'] gok.heroname = item['heroInfo'][0]['heroName'] gok.herotype = position # 英雄走哪路 gok.herotypename = item['heroInfo'][0]['heroCareer'] gok.tRank = item['tRank'] gok.winpercent = item['winRate'] gok.gameactpercnt = item['showRate'] gok.banRate = item['banRate'] all_hero_msg.append(gok)
为了不报错,请各位还是直接查看仓库的完整代码吧~~
redis维护IP代理池
可以查看上一篇博文,里面有完整的操作。https://blog.csdn.net/luoz_java/article/details/92741358
pymongo
mongodb的可视化,我发现了一款很好的工具,就是 robo3t ,百度搜索即可,免费版的。
官网地址:https://docs.mongodb.com/
pymongo的操作主要为以下内容:
- 创建集合
#!/usr/bin/python3
import pymongo
client = pymongo.MongoClient(mongo_config['MONGO_URL'])
db = client[mongo_config['MONGO_DB']]
在 MongoDB 中,集合只有在内容插入后才会创建! 就是说,创建集合(数据表)后要再插入一个文档(记录),集合才会真正创建。
- 判断集合是否已存在
#!/usr/bin/python3
import pymongo
client = pymongo.MongoClient(mongo_config['MONGO_URL'])
db = client[mongo_config['MONGO_DB']]
collist = db. list_collection_names()
if "sites" in collist: # 判断 sites 集合是否存在
print("集合已存在!")
-
插入集合
- insert_one()
#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] search_set = db[mongo_config['USER_FIND_TYPE']] tmp = search_set.insert_one({'user_id': user_id, 'game_type': type}) # insert_one() 方法返回 InsertOneResult 对象,该对象包含 inserted_id 属性,它是插入文档的 id 值。 print(tmp.inserted_id) # 5b2369cac315325f3698a1cf if tmp: return 'success' return None
- insert_many()
#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] search_set = db[mongo_config['USER_FIND_TYPE']] mylist = [ { "name": "Taobao", "alexa": "100", "url": "https://www.taobao.com" }, { "name": "QQ", "alexa": "101", "url": "https://www.qq.com" }, { "name": "Facebook", "alexa": "10", "url": "https://www.facebook.com" }, { "name": "知乎", "alexa": "103", "url": "https://www.zhihu.com" }, { "name": "Github", "alexa": "109", "url": "https://www.github.com" } ] x = search_set.insert_many(mylist) # 输出插入的所有文档对应的 _id 值 print(x.inserted_ids) # [ObjectId('5b236aa9c315325f5236bbb6'), ObjectId('5b236aa9c315325f5236bbb7'), ObjectId('5b236aa9c315325f5236bbb8'), ObjectId('5b236aa9c315325f5236bbb9'), ObjectId('5b236aa9c315325f5236bbba')] # insert_many() 方法返回 InsertManyResult 对象,该对象包含 inserted_ids 属性,该属性保存着所有插入文档的 id 值。
- 插入指定 _id 的多个文档
#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] search_set = db[mongo_config['USER_FIND_TYPE']] mylist = [ { "_id": 1, "name": "RUNOOB", "cn_name": "菜鸟教程"}, { "_id": 2, "name": "Google", "address": "Google 搜索"}, { "_id": 3, "name": "Facebook", "address": "脸书"}, { "_id": 4, "name": "Taobao", "address": "淘宝"}, { "_id": 5, "name": "Zhihu", "address": "知乎"} ] x = mycol.insert_many(mylist) # 输出插入的所有文档对应的 _id 值 print(x.inserted_ids) # [1, 2, 3, 4, 5]
-
查询
- find_one()
- find()
- 查询指定字段的数据
- 根据指定条件查询
#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] mycol = mydb[mongo_config['USER_FIND_TYPE']] x = mycol.find_one() list_col = mycol.find() # 将要返回的字段对应值设置为 1 # 除了 _id 你不能在一个对象中同时指定 0 和 1,如果你设置了一个字段为 0,则其他都为 1,反之亦然。 for x in mycol.find({},{ "_id": 0, "name": 1, "alexa": 1 }): print(x) # 同时指定了 0 和 1 则会报错 for x in mycol.find({},{ "name": 1, "alexa": 0 }): print(x) # 根据条件查找 mydoc = mycol.find_one({'user_id': user_id, 'game_type': type})
- 高级查询
与或非,与不用说了。下面说或、非
limit()
排序
#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] mycol = mydb[mongo_config['USER_FIND_TYPE']] # 或 x = mycol.find_one({"$or": [{"name": hero_another_name}, {"another1": hero_another_name}]}) # 非 # 读取 name 字段中第一个字母为 "R" 的数据,正则表达式修饰符条件为 {"$regex": "^R"} : x = mycol.find({ "name": { "$regex": "^R" } }) # $in,常用于判断列表name是否存在luozheng元素 x = mycol.find_one({"name": {'$in':['luozheng']}}) # $regex,也可以写正则 # 适用于匹配,如果字段a的值为'abc',如果我们想知道name的值是否包含‘b’,可以这样做find({'name':{'$regex':'b'}}) x = mycol.find_one({"name": {'$regex':['luo']}}) # limit() myresult = mycol.find().limit(3) # 同时指定了 0 和 1 则会报错 for x in mycol.find({},{ "name": 1, "alexa": 0 }): print(x) # sort 升序:pymongo.ASCENDING ( 1 ) 、 降序:pymongo.DESCENDING ( -1 ) mydoc = mycol.sort([("field1",pymongo.ASCENDING), ("field2",pymongo.DESCENDING)])
-
更新
update_one()
update_many()#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] mycol = mydb[mongo_config['USER_FIND_TYPE']] # update_one 该方法第一个参数为查询的条件,第二个参数为要修改的字段。 mycol.update_one({ "alexa": "10000" }, { "$set": { "alexa": "12345" } }) # 实例将查找所有以 F 开头的 name 字段,并将匹配到所有记录的 alexa 字段修改为 123: myquery = { "name": { "$regex": "^F" } } newvalues = { "$set": { "alexa": "123" } } x = mycol.update_many(myquery, newvalues)
-
删除
delete_one()
delete_many()
drop()#!/usr/bin/python3 import pymongo client = pymongo.MongoClient(mongo_config['MONGO_URL']) db = client[mongo_config['MONGO_DB']] mycol = mydb[mongo_config['USER_FIND_TYPE']] # delete_one mycol.delete_one({ "name": "Taobao" }) # delete_many myquery = { "name": {"$regex": "^F"} } x = mycol.delete_many(myquery) # delete_many() 方法如果传入的是一个空的查询对象,则会删除集合中的所有文档 x = mycol.delete_many({}) # 删除集合 # 如果删除成功 drop() 返回 true,如果删除失败(集合不存在)则返回 false。 mycol.drop()
日志记录功能
参照博文: https://www.cnblogs.com/nancyzhu/p/8551506.html
-
介绍
logging提供了一组便利的函数,用来做简单的日志。它们是 debug()、 info()、 warning()、 error() 和 critical()。默认等级是WARNING,这意味着仅仅这个等级及以上的才会反馈信息,除非logging模块被用来做其它事情。
logging函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下(以严重程度递增排序):
级别 | 何时使用 |
---|---|
DEBUG | 详细信息,一般只在调试问题时使用。 |
INFO | 证明事情按预期工作。 |
WARNING | 某些没有预料到的事件的提示,或者在将来可能会出现的问题提示。例如:磁盘空间不足。但是软件还是会照常运行。 |
ERROR | 由于更严重的问题,软件已不能执行一些功能了。 |
CRITICAL | 严重错误,表明软件已不能继续运行了。 |
- 代码实例
import logging
from logging import handlers
class Logger(object):
level_relations = {
'debug':logging.DEBUG,
'info':logging.INFO,
'warning':logging.WARNING,
'error':logging.ERROR,
'crit':logging.CRITICAL
}#日志级别关系映射
def __init__(self,filename,level='info',when='D',backCount=3,fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
self.logger = logging.getLogger(filename)
format_str = logging.Formatter(fmt)#设置日志格式
self.logger.setLevel(self.level_relations.get(level))#设置日志级别
sh = logging.StreamHandler()#往屏幕上输出
sh.setFormatter(format_str) #设置屏幕上显示的格式
th = handlers.TimedRotatingFileHandler(filename=filename,when=when,backupCount=backCount,encoding='utf-8')#往文件里写入#指定间隔时间自动生成文件的处理器
#实例化TimedRotatingFileHandler
#interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种:
# S 秒
# M 分
# H 小时、
# D 天、
# W 每星期(interval==0时代表星期一)
# midnight 每天凌晨
th.setFormatter(format_str)#设置文件里写入的格式
self.logger.addHandler(sh) #把对象加到logger里
self.logger.addHandler(th)
if __name__ == '__main__':
log = Logger('all.log',level='debug')
log.logger.debug('debug')
log.logger.info('info')
log.logger.warning('警告')
log.logger.error('报错')
log.logger.critical('严重')
Logger('error.log', level='error').logger.error('error')
centos服务器的定时任务
编辑定时任务:crontab -e
查看定时任务:crontab -l
如果是命令需要先后执行,可以使用 &&
如果是后台运行并且多命令,记得先运行命令在nohup。
例如以下是先杀进行,在启动进程:
#定时重启服务
2 0 * * * ps -ef | grep wxWeb.py | grep -v grep | awk '{print $2}' | xargs kill -9
4 0 * * * cd /home/LOLGokSpider/Web && nohup /home/LOLGokEnv/bin/python /home/LOLGokSpider/Web/wxWeb.py > /home/LOLGokSpider/Web/wxRun.log 2>&1 &
个人博客:Loak 正 - 关注人工智能及互联网的个人博客
文章地址:爬虫实战(二)—利用requests、selenium爬取王者官网、王者营地APP数据及pymongo详解