大家好,我是“猫玛尼”,一名程序员。
昨天有位读者留言,说起“有人用爬虫统计元卫男 收到的打赏金额”。我之前也逛过雪球,所以知道他说的那个人,叫元卫南,他的留言里面是写错字了。所以最终这个事情叫做:统计雪球大V元卫南收到的打赏金额。
我喜欢技术也喜欢钱,觉得这个事情蛮有意思的,所以我也来尝试一下。声明一下,没有对元卫南大V不敬,也不想引起什么误会,纯粹是学术+爱好的结合,如侵则删。
对技术不感兴趣的直接拉到文末看结论就行。
虽然使用Python的Scrapy框架会很方便,不过有些不了解它的人可能会有点懵,所以我决定不使用爬虫框架,直接用Python的requests类库来完成这个任务。
雪球,是一个投资理财论坛,可以发帖,看到帖子的人可以根据个人喜好,对楼主点赞、评论和打赏。
雪球,有登录功能,因为登录会增加整个任务的复杂度,所以我先考虑简单的不登录的方式能不能拿到数据,能拿到则最好,不能则继续走登录的方式。
第一次讲解爬虫的具体案例,我会讲的细致一点。
打开Chrome浏览器,打开“开发者工具”,并切换到“Network”Tab页面。使用“开发者工具”可以很方面的调试网页,获取更多的信息。我是直接使用快捷键“option+command+J”打开的。Mac Pro笔记本怎么打开,见下图:
然后,在顶部的网址输入栏,输入网址“https://xueqiu.com/”,由于我们的目标非常的明确:统计雪球大V元卫南收到的打赏金额。那么我们直接在雪球网的搜索框输入“元卫南”,然后找到他的原创帖子列表(这个就是我们常说的爬虫入口),并观察“开发者工具栏”中的“Network”Tab页面的请求数据。具体如下:
可以很清楚的看到,里面只有一个正常的请求“https://xueqiu.com/statuses/original/timeline.json?user_id=2227798650&page=1”,这里其实可以做一些额外的分析,比如我们可以猜测2227798650这个user_id代表的就是元卫南,page=1表示的是们目前处在分页列表的第一页。这个请求返回的是一个Json格式的数据。以我的经验,Json数据,是最友好的,对后续的数据处理很方便,而且据我观察,Json数据的格式,一般不会变化,比较稳定,有效的缩减了我们以后的维护精力。
直接观察列表,发现只有标题、摘要、发布时间、阅读量,并没有文章的打赏数据。那么我们再看看刚才发现的那个请求的Json数据,里面有没有打赏相关的数据。Json数据的最小单元,截图如下:
不需要太强的英语能力,就能分析红圈里面的各个字段。再观察里面有没有类似打赏信息的字段,发现并没有。看来打赏数据并不在列表里面,那么我们可以得出初步的结论,打赏数据基本就在列表点进去的帖子详情里面。
为了提高我们的效率,我们需要多看几个帖子,找到那种有打赏的,且最好打赏还比较多的那种帖子。
怎么找这种帖子也是一门学问,我是这么想的,阅读量大,说明基数大,那么一般来说打赏的可能性也就大,那么打赏的数据也就多了。这是一种提高做事效率的合理推测,虽然存在反例来打我脸,但这并不影响,我们如此去思考问题。
我向后翻阅他的原创列表,发现在第二页就有阅读量很高,且标题也比较引人注意的一片文章。直接点进去,运气不错,有20个人点赞。截图如下:
我发现,雪球基本都是Ajax动态请求服务端API加载数据的,这就让我比较方便了,哈哈哈……
我们继续分析这些请求,找一下到底是哪个请求返回了打赏数据。
很巧,第一个接口“https://xueqiu.com/statuses/reward/list_by_user.json?status_id=116503994&page=1&size=20”里面就有,而且让我高兴的是,他居然有一个打赏总额的字段,不过经我测算,打赏总额字段的值,跟每个人打赏金额加起来的总和,总是相差1%,我猜测可能是雪球平台拿走了这1%,剩下99%是给原创坐着的(这个1%的说明,后来我在雪球的公告里面找到了)。这个接口跟分页相关的两个字段page、size,经过分析我们知道是打赏的分页列表,所以如果要用一个一个对打赏相加的方式来计算这个帖子的打赏总额的话,还要考虑分页的问题,不是不可以做,只是会比较麻烦。不过这里显然不需要这样繁琐,因为我前面说了,已经有了一个总金额字段,只是相差1%,这个最后我们计算的时候考虑进去就行。
这里面还有个关键字段“status_id”,这个值其实就是列表数据API里面的ID值。所以这个接口我们就很好仿照了,“https://xueqiu.com/statuses/reward/list_by_user.json”这部分不用变,后面的参数status_id的值取自列表数据API的ID,由于我们不需要做分页捞取数据,是直接使用“reward_amount”的值,那么page就固定为1,size固定为20,就行。
雪球上面这几个接口的金额数据,都是乘上了100,所以在最后的时候我们要除以100,这个要记住。
到这里,思路就清晰了,我们只要遍历元卫南的所有帖子,把每个帖子的打赏金额加起来,就是元卫南收到的打赏总额。
理清楚了思路,接下来就是写代码了啊。
先写个列表API数据的模拟请求代码,运行了下,直接报错了啊……
看报错信息,是在25行Json解析返回数据的时候出错了,我拿URL直接去从来没有登录过雪球网的火狐里面请求,也是直接是报错:
直觉告诉我,我前面以为的不需要登录是错的,实际是需要用到登录后的cookies信息,才能正常访问API的。那么为什么我开始的时候,在Chrome就可以直接访问呢?原来之前我是一直都用Chrome登录雪球网,查看我的自选股什么的,浏览器帮我保存了相关cookies在我的电脑上,如果我清除浏览器数据,效果就会和在从来没登录过的火狐一样。既然如此,那我就在Chrome拿到我的cookies数据,继续编写我的代码。模拟请求带上cookies就成功获取到了数据。
其他的,就是具体的代码实现了,比如列表分页、每一页列表的每一项数据获取等。具体直接上代码(今天比较临时,后续我打算将整理后的代码放到我的github上面):
"""
统计雪球大V元卫南收到的打赏金额
元卫南的原创专栏地址:https://xueqiu.com/2227798650/column
元卫南的原创专栏分页列表数据第一页的API:https://xueqiu.com/statuses/original/timeline.json?user_id=2227798650&page=1
元卫南的原创专栏某一篇文章的打赏数据的API:https://xueqiu.com/statuses/reward/list_by_user.json?status_id=118077741&page=1&size=14
列表的每一项有一个超链接,通过这个超链接可以组装这一项对应的详情的打赏API
"""
import json
import requests
class RewardOfYwn:
def __init__(self):
self.domain = 'https://xueqiu.com' # 雪球的域名
self.page_list_api = 'https://xueqiu.com/statuses/original/timeline.json?user_id={user_id}&page={page_num}' # 雪球某个人的帖子分页数据API
self.reward_api = 'https://xueqiu.com/statuses/reward/list_by_user.json?status_id={status_id}&page=1&size=20' # 某个帖子的打赏数据API
self.user_id = '2227798650' # 元卫南的用户ID
self.headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:63.0) Gecko/20100101 Firefox/63.0',
'Cookie': '这里请用你的Cookie',
} # 构造请求头,主要是user-agent、Cookie
self.sum_after_deduct_fee = 0 # 雪球扣除1%的服务费后元卫南拿到的打赏总额
self.sum_from_other_person = 0 # 所有人给元卫南打赏了多少钱,即在元卫南拿到的钱的基础上加上1%的服务费
def crawl(self):
"""
主要的爬取逻辑
:return: 收到的打赏总额sum_after_deduct_fee,球友打赏的总额sum_from_other_person,
两者相差1%的服务费,记得总额都要除以100,因为雪球金额的表示法是乘以100的,比如98.12雪球表示成了9812
"""
first_page_response = requests.get(self.page_list_api.format(user_id=self.user_id, page_num=1), headers=self.headers) # 先请求第一页的数据,获取总共有多少页等相关信息
first_page_json_data = json.loads(first_page_response.text) # 解析第一页的数据
max_page = first_page_json_data['maxPage'] # 原创文章最多有几个分页列表
total_reward = self.sum_reward_in_page(first_page_json_data) # 第一页的打赏总额
for page_num in range(2, max_page + 1):
response = requests.get(self.page_list_api.format(user_id=self.user_id, page_num=page_num), headers=self.headers) # 分页请求
json_data = json.loads(response.text) # 解析数据
total_reward += self.sum_reward_in_page(json_data)
return {'sum_after_deduct_fee': total_reward / 100, 'sum_from_other_person': total_reward / 0.99 / 100}
def sum_reward_in_page(self, json_data):
result = 0
if json_data is not None and 'list' in json_data and len(json_data['list']) > 0:
for row in json_data['list']:
reward_response = requests.get(self.reward_api.format(status_id=row['id']), headers=self.headers) # 获取打赏数据
reward_data = json.loads(reward_response.text) # 解析打赏数据
result += int(reward_data['reward_amount']) # 累加打赏的钱
return result
if __name__ == '__main__':
obj = RewardOfYwn()
print(obj.crawl())
排版乱了,我目前还不知道怎么排版,待我研究一下。
最终统计到的信息如下(截止2018-12-10):
收到的打赏金额为:25317.10 元
球友打赏的总额是:25572.83 元
雪球收取的服务费:255.73 元
(最后一位有四舍五入)
“统计雪球大V元卫南收到的打赏金额”,只是很多类似事情里面具有代表性的一件,还有其他比如:统计某个大V的每篇文章的阅读数,统计阅读数最高的那些文章的标题经常出现的词是什么,通过统计这些文章的点赞、转发、收藏等情况,判断一个帖子的热度,还有行情相关的数据,股票关注的热度等等……只要有想法,数据在,一般都是可以去好好挖掘的。
本文涉及到了具体的一个大V——元卫南,我没有恶意,纯属技术交流,如侵则删。
欢迎围观公号《猫玛尼》