地图
地图
文章目录
  1. 1. 背景交代
    1. 1.1 工具和环境:
    2. 1.2 教程整体思路:
  2. 2. 教程正文
    1. 2.1 抓包分析
      1. 2.1.1 网页抓包
      2. 2.1.2 fiddler抓包
    2. 2.2 写爬虫
      1. 2.2.1 房间号列表
      2. 2.2.2 获取主播id和竹子数
    3. 2.3 存储到数据库Mongodb
    4. 2.4 提取分析数据
    5. 2.5 数据可视化
      1. 2.5.1 echarts.js
      2. 2.5.2 移动端简单支持
    6. 2.6 部署到网站
  3. 3. 总结

pandaTV竹子Top20教程

这次我会带来一个pandaTV竹子Top20的教程,从零开始。

1. 背景交代

1.1 工具和环境:

  1. python库:requests、pymongo等
  2. 开发环境:python3.5、windows10
  3. 数据库:mongodb
  4. 数据可视化工具echarts.js
  5. 抓包分析工具fiddler
  6. 一个虚拟主机或者github pages

1.2 教程整体思路:

  1. 首先我们要抓取数据(主播id和竹子数),那么我们就要抓包分析。
  2. 当我们知道从哪里可以获取数据后,就要写爬虫。写爬虫的时候要考虑以下问题,比如并发,增量等。
  3. 当我们抓取到所需数据时,我们需要保存数据,我们使用了数据库(其实是杀鸡用牛刀了)。
  4. 现在我们要根据需求来提取分析数据了,然后返回我们需要的数据。
  5. 得到所需数据后,我们要展示这些数据,要使用一些数据可视化工具。
  6. 写好展示页面后,我们要将它部署到网站上去,会讲到一些方法。

2. 教程正文

2.1 抓包分析

我们先分析一下需求,我们希望得到的数据:

  1. 因为pandaTV房间号是不固定的,我们要先获取热门直播里面的前20个主播的房间号。
  2. 得到房间号以后,我们要得到每个房间号里的主播id和竹子数量。

如果不想抓包分析,那可以直接使用selenium,但是selenium是万不得已才会用的,我第一次写的版本就是selenium版的,运行速度很慢。因为它的原理就是模拟人控制浏览器,要一个一个点开网页。

2.1.1 网页抓包

那么我们接下来开始抓包分析,pandaTV使用了ajax,我们使用开发者工具来抓包分析一下。放上chrome开发者工具教程

我们打开F12,点到Network标签,把preserve log钩打上,下面的选择XHR,开始抓包分析。我们先输入网址 www.panda.tv,然后点全部,然后随便选个房间进去,此处以我的房间为例。

pandaTV抓包
这里我直接给出需要的地址,即第4步的地址,其他的地址暂时不需要。那么网址格式大概

http://www.panda.tv/api_room_v2?roomid=" + 房间号
比如 http://www.panda.tv/api_room_v2?roomid=473005

我们直接访问这个网址,得到json格式的数据,里面有我们需要的主播id和竹子数。

2.1.2 fiddler抓包

那我们得到了输入房间号返回数据的地址,接下来我们要获取热门直播的地址,那网页版是无法获取的,我们需要抓取Android客户端的api,这里用到的工具是fiddler,给出fiddler教程

为了节省时间,我直接给出我们需要的地址。pageno是页数,pagenum是每页多少人,同样得到json格式数据。

http://api.m.panda.tv/ajax_live_lists?pageno=1&pagenum=20&status=2&order=person_num

还有一个Github上的非官方API,也挺不错的。pandatvAPI

2.2 写爬虫

2.2.1 房间号列表

我们根据Android客户端抓取到的url获取房间号列表

1
2
3
4
5
6
7
8
9
import requests

#得到推荐前20的房间号
def getTop20List():
top20List = []
url = "http://api.m.panda.tv/ajax_live_lists?pageno=1&pagenum=20&status=2&order=person_num"
info = ((requests.get(url)).json())["data"]["items"]
[top20List.append(data_id["id"]) for data_id in info]
return top20List

那么我们再分析一下,因为这是当前的热门直播前20,主播的作息时间是不固定的,所以我们涵盖不了所有的主播。那么我们要实现增量抓取,即每次爬取的房间号要保存下来,这样反复几次,可以涵盖几乎所有的热门主播。这里我们使用pickle,这是一个很方便的库,方便的保存读取房间号列表,然后实现增量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pickle, os.path

#记录爬取的房间号
def recordRoomList(roomNumberList):
with open('roomList.pickle', 'wb') as f:
pickle.dump(roomNumberList, f)

#读取文件中的房间号
def readRoomList():
with open('roomList.pickle', 'rb') as f:
roomNumberList = pickle.load(f)
return roomNumberList


#增量判断
def incrementRoom(roomNumberList):
if(not os.path.isfile("roomList.pickle")):
recordRoomList(roomNumberList)
return roomNumberList
existRoomList = readRoomList()
for item in roomNumberList:
if(item in existRoomList):
continue
else:
existRoomList.append(item)
recordRoomList(existRoomList)
return existRoomList

2.2.2 获取主播id和竹子数

现在我们得到了房间号列表,根据我们抓包分析得到的地址,一个一个地请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

#得到该房间的主播id和竹子
def getBambooAndName(roomNumberList):
roomInfoList = []
prefix = "http://www.panda.tv/api_room_v2?roomid="
for u in roomNumberList:
singleRoom = []
singleRoom.append(u)
info = (requests.get(prefix + str(u))).json()
if(info):
print(info['data']['hostinfo']['name'])
singleRoom.append(info['data']['hostinfo']['name'])
singleRoom.append(round(int(info['data']['hostinfo']['bamboos'])/1e6, 2))
else:
singleRoom.append(["none", "none"])
print("not get")
roomInfoList.append(singleRoom)
return roomInfoList

请注意,此处我并没有简单地用两个list,而是使用了list in list的形式。why?这是因为为了后面的数据库而设计的。我这里的形式是[[room1],[room2],…]

2.3 存储到数据库Mongodb

首先需要安装mongodb数据库,然后我们语言是python,所以使用pymongo库。
pymongo教程

我们要以字典形式存储进数据库,而且要先设计好数据库,我的设计如下。
mongodb框架设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pymongo import MongoClient
from datetime import datetime

#获取字典形式
def getDict(roomInfoList):
roomInfoDict = {}
for item in roomInfoList:
singleRoom = {"name": item[1], "bamboos": item[2], "date": datetime.utcnow()}
roomInfoDict["room" + str(item[0])] = singleRoom
return roomInfoDict

#存储到mongodb
def saveToMongo(roomInfoDict):
client = MongoClient()
db = client.pandaTV
for k,v in roomInfoDict.items():
collection = db[k]
collection.insert(v)
client.close()

最终存储进mongodb的数据是这样的,我们给每个post都加了一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
> show collections
room10003
room10009
room100130
room10015
...

> db.room7000.find()
{ "_id" : ObjectId("57a2f91deccca320542a9e64"), "bamboos" : 21.4, "date" : ISODate("2016-08-04T08:13:16.973Z"), "name" : "LPL熊猫TV官方直播" }
{ "_id" : ObjectId("57a2ff93eccca31b38213ac1"), "name" : "LPL熊猫TV官方直播", "date" : ISODate("2016-08-04T08:40:51.872Z"), "bamboos" : 21.4 }
{ "_id" : ObjectId("57a45105eccca305c45cb33a"), "bamboos" : 21.64, "name" : "LPL熊猫TV官方直播", "date" : ISODate("2016-08-05T08:40:37.707Z") }
{ "_id" : ObjectId("57a5c283eccca30f6c94b0a0"), "bamboos" : 22.04, "name" : "LPL熊猫TV官方直播", "date" : ISODate("2016-08-06T10:57:07.491Z") }
...

这里要注意有个坑,需要先安装mongodb数据库,在运行爬虫之前,需要先开启数据库。

1
$ mongod

2.4 提取分析数据

关于这部分比较简单,大致就是读取数据库,按照时间戳来排序,得到最新的数据,然后返回Top20的一个Dict。特别需要注意的是,因为python的dict类型是无序的,所以即使你是排好序逐一添加,最后的Dict也是无序的。那么又因为我的Dict结构要方便数据可视化工具echarts.js读取,所以我设计成这样:

1
2
3
4
Dict = {
"name": ['xx', 'xx', ...]
"bamboos": [xx, xx, ...]
}

我直接把有序的列表作为values,这样就可以不关注Dict的坑了。还有就是要两个列表组合排序的问题,我使用了zip和sorted。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pymongo import MongoClient
import json

def writeJsonFile(data,filename):
with open(filename+".json", 'w') as f:
json.dump(data, f)

def get20Rank(db):
bamboosList = []
nameList = []
rankDict = {}
mycollections = db.collection_names()
for item in mycollections:
latest = db[item].find().sort('date',-1)[0]
bamboosList.append(latest["bamboos"])
nameList.append(latest["name"])
bamboosList, nameList = zip(*sorted(zip(bamboosList, nameList), reverse=True))
rankDict["name"] = nameList[0:19]
rankDict["bamboos"] = bamboosList[0:19]
writeJsonFile(rankDict, "bamboos_rank")

这样我们得到了包含数据的json文件。

2.5 数据可视化

2.5.1 echarts.js

关于数据可视化的选择有很多,web方面有highchart、echarts、d3之类。因为看中文爽,还是用了echarts。
echarts官网
我们需要异步加载数据,异步加载实例

这边还有个坑,就是因为x轴的数据太密集,会导致显示不全,解决办法:

1
2
3
4
5
6
xAxis: [
axisLabel: {
interval: 0,
rotate: 340
},
]

2.5.2 移动端简单支持

我使用了viewport来稍微支持了下移动端,即判断innerWidth的大小(显示屏)来设置参数。当屏幕宽度大于500px时,横向排版;小于500px时,纵向排版。

2.6 部署到网站

我们在调试echarts的时候,会遇到一个坑,即不能跨域,因为我们是读取本地json文件的。我安装了一个xampp来调试,比较方便。

因为我有一个配置好的虚拟主机,所以直接将json文件和html页面放上去就可以看到了。如果只是一个单纯的服务器,需要自己搭建服务器。或者放到github pages上也可以。

3. 总结

放上Github地址: panda-bamboos-rank
展示地址: http://behappy.cc/extension/panda/rank.html

以上。

2016/8/9

 

赏杯咖啡吧
如果你并不富裕,请不要捐赠!
  • 微信扫一扫