1. XenForo 1.5.14 中文版——支持中文搜索!现已发布!查看详情
  2. Xenforo 爱好者讨论群:215909318 XenForo专区

亲眼见证“发大水”:用R爬虫记录明星微博数据注水全过程

本帖由 漂亮的石头2020-10-16 发布。版面名称:知乎日报

  1. 漂亮的石头

    漂亮的石头 版主 管理成员

    注册:
    2012-02-10
    帖子:
    486,333
    赞:
    46
    [​IMG] KellyWeaver,公共卫生 / 营养流行病学

    上周三中午,我吃饭的时候打开微博,恰好撞见喜欢的一个姐姐十几秒前发博了。电光火石之间,我马上意识到:第 0 分钟开始就能看到数据,这是观察这个姐姐微博真实人气的绝佳机会,也是观察这个姐姐的微博是否发水的绝佳时间窗的起点。

    作为一个非著名过气网黄,我经常盯我自己文章的传播数据。根据过往经验总结,对于基于时间线传播的内容来说,从第 0 分钟开始的那十几分钟的数据有以下特点:

    • 内容不可能火到上热搜,所以热搜榜没有影响;
    • 机器人水军要去主动联系还要排期,因此一时半会也没法到位;
    • 粉丝数据组轮博刷评论,也要去群里和超话先布置任务什么的,因此也没法这么快就进来。

    因此,刚开始那十几分钟的数据,是最「干净」的。

    当后面热搜、水军、粉丝数据组进来之后,只要一和最初那十几分钟的干净数据一对比,一下子就都能现原形了。

    然而,要从第 0 分钟开始收集数据,靠人工盯和手抄是不太靠谱的。

    一方面,你不知道明星什么时候发微博;另一方面,手刷再手记得话,会占用自己大量的时间,记录效率也低。

    为了解决这两个问题,我想了想,作为一个流行病学搬砖工,我最会的就是 R 了,那就用 R 写个微博爬虫自动监视明星主页吧。

    如何在 R 中实现微博爬虫

    (对技术细节不感兴趣的观众朋友们,你们可以划过这部分,直接看下一个大标题「爬虫监视结果:娱乐圈人均发水,区别只在多少」)

    写爬虫,有一个基本格言,叫做「可见即可爬」。不过,微博 web 版对爬虫设置了登陆屏障,直接通过爬虫访问微博,只能抓回一个跳转中的网页:「Sina Visitor System」。

    幸好,微博移动端是可以被爬虫直接访问的。

    • 找到要爬取的具体网址

    观察微博移动端加载全过程的网络活动,可以发现:微博移动端的全部微博数据,都可以在一个 json 中找到。

    [​IMG]

    再观察这个 json 的地址:https://m.weibo.cn/api/container/getIndex?type=uid&value=1281047653&containerid=1076031281047653

    这里面有两个参数 value 和 containerid,有可能是动态的。

    经过确认,value= 后面的这个东西是固定的用户 ID,也就是每个人微博主页的 uid;containerid,不知道从哪儿来的,但是很幸运的是,每次加载,这个 containerid 都是不变的。

    作为一个学流行病学而非计算机专业的人,我直觉觉得这个 containerid 怎么生成对我来说应该是个很难查的问题。鉴于每次加载它这个链接都不会变,那本外行就懒得去研究怎么找到这个 containerid 了,直接复制这个 json 的链接拿来爬就是。

    URLs='https://m.weibo.cn/api/container/getIndex?type=uid&value=1757744065&containerid=1076031757744065'

    取回 json 之后,照例美化一下格式,方便阅读和后续数据提取:

    ### require a package to format json file from weibo require(jsonlite)### read json file for weibo lists json=readLines(jsonURL,encoding='UTF-8')### format the json file fetched json=prettify(json)writeLines(json,'D:/desktop/weibocrawler/json.txt')

    这里输出了 txt,是因为 jsonlite 这个轮子美化格式换行用的是直接加换行符,这种换行符 gsub() 是识别不了的,因此得弄成 txt 让 readLines() 重新读一遍,才能做成 gsub() 能够识别的分行的对象。

    [​IMG]

    观察这个 json,可以发现它一次会包含 10 条最近的微博,其中每条微博开始和结束的时候长这样,而且用黄色标识出来的这两个字段是唯一的:

    [​IMG]
    红线以上是上一条微博,红线以下是下一条微博

    所以,用这两个黄色的开始、结束标志作为分段,撰写代码如下:

    ### separate the 10 weibos page=readLines('json.txt')starts=grep('"card_type":9,',page)ends=grep('"show_type":0',page)
    • 将取回的 json 中含有的数据提取出来

    我前面遇到的主要问题是,我不知道明星啥时候发微博。所以,爬虫要能够监测新微博的出现,就不能指定具体哪条微博。

    此外,每条微博我都想监测起码两天以上的数据变化,如果只抓最新两三条微博,那么明星万一失心疯连着刷个五六条的,我的程序就不抓了,这可不行。

    固然,我可以每次抓回来列表之后取一下发表时间再算算这微博还要不要抓,但是这实在是太麻烦了。既然这个 json 文件一次能提供个人主页全部前 10 条微博的数据,那就每次爬取都把这最新 10 条微博的数据全都记录下来好了!后边如果有空,再慢慢迭代添加算时间的代码吧。

    接下来是每条微博的实时累计互动数据。观察 json 文件,可以发现我们要的数据在以下这些字段里:

    [​IMG]

    因此,通过如下代码,把这些数据用 gsub() 提取出来,然后放进数据库。

    #### create the data matrix for saving data: weibo ID, timestamp, repost, comment, like topwb=length(grep('"text":"置顶",',page))!=0db=matrix(nrow=length(starts),ncol=8)if(topwb){db=matrix(nrow=length(starts)-1,ncol=8)}colnames(db)=c('weibo_ID','weibo_text','timestamp','repost','comment','like','isRepost','fromChaoHua')#### fill in the data for(iin1:length(starts)){weibo=page[starts:ends]# remove pinned weibo if(length(grep('"text":"置顶",',weibo))!=0){next}# correct i for skipped weibos rowdb=length(db[,1])-(length(starts)-i)# get attributes wbid=gsub('"bid":"','',weibo[grep(' "bid": "',weibo)])wbid=gsub(',|"|\\s','',wbid)wbtext=gsub('"raw_text":"','',weibo[grep('"raw_text": "',weibo)])wbtext=gsub('",','',wbtext)repo=gsub("[^0-9]",'',weibo[grep('"reposts_count":',weibo)])comment=gsub("[^0-9]",'',weibo[grep('"comments_count":',weibo)])like=gsub("[^0-9]",'',weibo[grep('"attitudes_count":',weibo)])if(length(grep('"retweeted":1,',weibo))>0){repost=1}else{repost=0}if(grepl('超话',weibo[grep('"source":',weibo)][1])){fromch=1}else{fromch=0}# if a weibo is a repost, keep only the data from the user being observed db[rowdb,1]=as.character(wbid[length(wbid)])db[rowdb,2]=wbtext[length(wbtext)]db[rowdb,3]=as.numeric(Sys.time())db[rowdb,4]=repo[length(repo)]db[rowdb,5]=comment[length(comment)]db[rowdb,6]=like[length(like)]db[rowdb,7]=repostdb[rowdb,8]=fromch}

    第二部分就是简单转移了下数据,看起来很多此一举,其实是因为,如果一条微博是转发微博,那么前面的那些字段抓回来的数据都会有两套。我们只关心明星的数据,不关心原博的,所以需要只留第二个。

    理论上,写成类似 wbid[2]这样也行,但是我「有如无必要绝不写死参数」的强迫症,所以还是习惯性写成动态的最后一个。

    最后,把以上过程全部打包在一起,写成一个函数,方便调用:

    wbwatcher=function(jsonURL){<.........>return(db)}
    • 自动监视自动存数据

    光写一个函数,肯定是不够的。所以这里我们要搞一个自动轮询的循环。

    既然都写爬虫了,那就干脆一次多整点明星上去吧。我刚看完浪姐,那就以 30 位浪姐作为样本,然后再加上肉眼确认过不买水军的何老师作为阴性对照,加上大家公认的四字水后作阳性对照。

    [​IMG]

    接下来是循环本体:

    while(Sys.time()<"2020-11-10 23:59:59 CEST"){#definestoptimefor(iin1:length(urltable$URLs)){URL=urltable$URLsif(length(grep('m.weibo.cn/statuses/show?id=',URL,fixed=T))!=0){try({#usetry()topreventbreaksfromerrorscausedbynetworkfailuredb=wbpostwatcher(URL)})if(!is.na(db[1,1])){print(paste('Success!','Checked',urltable$names,'at',Sys.time()))}}else{try({db=wbwatcher(URL)#anothersimilarfunction,usedtowatchasingleweiboonly})if(!is.na(db[1,1])){print(paste('Success!','Checked',urltable$names,'at',Sys.time()))}}db=as.table(db)if(!is.na(db[1,1])){db=cbind(rep(urltable$names,length(db[,1])),db)colnames(db)[1]='name'write.table(db,"D:/desktop/weibocrawler/db_sisters.csv",sep=",",col.names=!file.exists("D:/desktop/weibocrawler/db_sisters.csv"),row.names=F,append=T)}else{print(paste('Error!at',Sys.time()))}Sys.sleep(5)#Pausequerytopreventbeingbannedbyweibo}}

    最后这一套程序执行下来的效果是这样的:

    [​IMG]

    爬虫监视结果:娱乐圈人均发水,区别只在多少

    正常情况下,在基本没有粉丝做数据、没有机器刷数据的时候,作为一种基于时间线的、与多个博主内容相互竞争传播的、时间越长排序越后被刷到的概率越低的内容,一条微博的转评赞数据增长曲线大体应该是这样的:

    [​IMG]

    即使是靠近半夜,也只是曲线下降的速度变快而已:

    [​IMG]

    请记住上面的正常的曲线长啥样。然后,坐稳了,我们要看不正常的了~

    以下是部分被监测的明星的微博评论数据变化趋势:

    有午夜惊魂型的,半夜十二点前在 638 秒内突然集中增加了 2000 条评论和 20000 个赞:

    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    也有光天化日公然刷量的:

    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]



    当然,请务必注意,前面这些图,虽然能强力提示这些明星的微博有水分,但并不能证明水是明星本人买的——毕竟,这些水也可能是广告公司、经纪公司、粉丝买的。

    比如金晨的微博转发之所以有异常增长,就很可能是她粉丝干的:

    [​IMG]
    [​IMG]
    [​IMG]

    而如果你问她们,为什么要给自己姐姐用机器刷数据,她们的回答一般是:为了给姐姐排面、为了让姐姐数据更好看——与此同时,却浑然不知或者假装不知,这样低劣的机刷数据,在稍微像样点的娱乐数据分析公司那里,都能一秒现原形。

    后记

    在这篇文章里举出的疑似发大水的曲线,还只是我观测到的所有曲线图中的一小部分。

    [​IMG]

    我的程序从德国的昨天早上跑到现在,一共抓住了十几位发了新微博的明星,有了他们的微博从第 0 分钟开始的传播数据全程变化。

    在这十几个人当中,除了我肉眼翻完过评论鉴定的何炅何老师仍然保持着阴性对照的优良作风之外,其他大部分人,包括一些非常有名的演员和歌手,都有这种几分钟内几千几万的异常的增长。

    唯一区别,只在这些刷的量和他们真实流量的比例多少:

    有的人 5/1,有的人 10~20/1,有的人甚至 100/1 以上。


    是的,数据造假是不光彩的。

    然而,在这泥沙俱下的流量经济时代之中,无论是初出茅庐急需认可的年轻艺人,还是已经功成名就的圈内前辈,只要还想继续从广告投放商那里赚钱,那么每个人都得被逼着接受这样的现实。

    这一风雨晦暝的时代,何时才能落下帷幕呢?

    愿我们仍能见证那一天的到来。

    阅读原文
     
正在加载...