怎么用爬虫记录明星微博数据注水全过程
第 0 分钟开始就能看到数据,这是观察这个姐姐微博真实人气的绝佳机会,也是观察这个姐姐的微博是否发水的绝佳时间窗的起点。
根据过往经验总结,对于基于时间线传播的内容来说,从第 0 分钟开始的那十几分钟的数据有以下特点:
内容不可能火到上热搜,所以热搜榜没有影响;机器人水军要去主动联系还要排期,因此一时半会也没法到位;粉丝数据组轮博刷评论,也要去群里和超话先布置任务什么的,因此也没法这么快就进来。因此,刚开始那十几分钟的数据,是最「干净」的。
当后面热搜、水军、粉丝数据组进来之后,只要一和最初那十几分钟的干净数据一对比,一下子就都能现原形了。
然而,要从第 0 分钟开始收集数据,靠人工盯和手抄是不太靠谱的。
一方面,你不知道明星什么时候发微博;另一方面,手刷再手记的话,会占用自己大量的时间,记录效率也低。
为了解决这两个问题,我想了想,作为一个流行病学搬砖工,我最会的就是 R 了,那就用 R 写个微博爬虫自动监视明星主页吧。
如何在 R 中实现微博爬虫
(对技术细节不感兴趣的观众朋友们,你们可以划过这部分,直接看下一个大标题「爬虫监视结果:娱乐圈人均发水,区别只在多少」)
写爬虫,有一个基本格言,叫做「可见即可爬」。不过,微博 web 版对爬虫设置了登陆屏障,直接通过爬虫访问微博,只能抓回一个跳转中的网页:「Sina Visitor System」。
幸好,微博移动端是可以被爬虫直接访问的。
找到要爬取的具体网址观察微博移动端加载全过程的网络活动,可以发现:微博移动端的全部微博数据,都可以在一个 json 中找到。
经过确认,value= 后面的这个东西是固定的用户 ID,也就是每个人微博主页的 uid;containerid,不知道从哪儿来的,但是很幸运的是,每次加载,这个 containerid 都是不变的。
作为一个学流行病学而非计算机专业的人,我直觉觉得这个 containerid 怎么生成对我来说应该是个很难查的问题。鉴于每次加载它这个链接都不会变,那本外行就懒得去研究怎么找到这个 containerid 了,直接复制这个 json 的链接拿来爬就是。
URLs = '/d/file/gt/2023-10/quscvlsffxx json 之后,照例美化一下格式,方便阅读和后续数据提取:
### require a package to format json file from weiborequire(jsonlite) ### read json file for weibo listsjson=readLines(jsonURL,encoding = 'UTF-8') ### format the json file fetchedjson=prettify(json)writeLines(json,'D:/desktop/weibocrawler/json.txt')
这里输出了 txt,是因为 jsonlite 这个轮子美化格式换行用的是直接加换行符,这种换行符 gsub() 是识别不了的,因此得弄成 txt 让 readLines() 重新读一遍,才能做成 gsub() 能够识别的分行的对象。
观察这个 json,可以发现它一次会包含 10 条最近的微博,其中每条微博开始和结束的时候长这样,而且用黄色标识出来的这两个字段是唯一的:
红线以上是上一条微博,红线以下是下一条微博
所以,用这两个黄色的开始、结束标志作为分段,撰写代码如下:
### separate the 10 weibospage=readLines('json.txt')starts=grep(' "card_type": 9,',page)ends=grep(' "show_type": 0',page)将取回的 json 中含有的数据提取出来
我前面遇到的主要问题是,我不知道明星啥时候发微博。所以,爬虫要能够监测新微博的出现,就不能指定具体哪条微博。
此外,每条微博我都想监测起码两天以上的数据变化,如果只抓最新两三条微博,那么明星万一失心疯连着刷个五六条的,我的程序就不抓了,这可不行。
固然,我可以每次抓回来列表之后取一下发表时间再算算这微博还要不要抓,但是这实在是太麻烦了。既然这个 json 文件一次能提供个人主页全部前 10 条微博的数据,那就每次爬取都把这最新 10 条微博的数据全都记录下来好了!后边如果有空,再慢慢迭代添加算时间的代码吧。
接下来是每条微博的实时累计互动数据。观察 json 文件,可以发现我们要的数据在以下这些字段里:
因此,通过如下代码,把这些数据用 gsub() 提取出来,然后放进数据库。
#### create the data matrix for saving data: weibo ID, timestamp, repost, comment, like topwb=length(grep('"text": "置顶",',page))!=0 db=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 datafor (i in 1:length(starts)) { weibo=page[starts[i]:ends[i]] # 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]=repost db[rowdb,8]=fromch}
第二部分就是简单转移了下数据,看起来很多此一举,其实是因为,如果一条微博是转发微博,那么前面的那些字段抓回来的数据都会有两套。我们只关心明星的数据,不关心原博的,所以需要只留第二个。
理论上,写成类似 wbid[2]这样也行,但是我「有如无必要绝不写死参数」的强迫症,所以还是习惯性写成动态的最后一个。
最后,把以上过程全部打包在一起,写成一个函数,方便调用:
wbwatcher = function(jsonURL) { <.........> return(db)}自动监视自动存数据
光写一个函数,肯定是不够的。所以这里我们要搞一个自动轮询的循环。
既然都写爬虫了,那就干脆一次多整点明星上去吧。我刚看完浪姐,那就以 30 位浪姐作为样本,然后再加上肉眼确认过不买水军的何老师作为阴性对照,加上大家公认的四字水后作阳性对照。
接下来是循环本体:
while (Sys.time()<"2020-11-10 23:59:59 CEST") { # define stop time for (i in 1:length(urltable$URLs)) { URL=urltable$URLs[i] if (length(grep('m.weibo.cn/statuses/show?id=',URL,fixed = T))!=0) { try({ # use try() to prevent breaks from errors caused by network failure db=wbpostwatcher(URL) }) if (!is.na(db[1,1])) { print(paste('Success!', 'Checked', urltable$names[i], ' at',Sys.time())) } } else { try({ db=wbwatcher(URL) # another similar function, used to watch a single weibo only }) if (!is.na(db[1,1])) { print(paste('Success!', 'Checked', urltable$names[i], ' at',Sys.time())) } } db=as.table(db) if (!is.na(db[1,1])) { db=cbind(rep(urltable$names[i],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) # Pause query to prevent being banned by weibo }}
最后这一套程序执行下来的效果是这样的:
爬虫监视结果:娱乐圈人均发水,区别只在多少
正常情况下,在基本没有粉丝做数据、没有机器刷数据的时候,作为一种基于时间线的、与多个博主内容相互竞争传播的、时间越长排序越后被刷到的概率越低的内容,一条微博的转评赞数据增长曲线大体应该是这样的:
即使是靠近半夜,也只是曲线下降的速度变快而已:
请记住上面的正常的曲线长啥样。然后,坐稳了,我们要看不正常的了~
以下是部分被监测的明星的微博评论数据变化趋势:有午夜惊魂型的,半夜十二点前在 638 秒内突然集中增加了 2000 条评论和 20000 个赞:
也有光天化日公然刷量的:
当然,请务必注意,前面这些图,虽然能强力提示这些明星的微博有水分,但并不能证明水是明星本人买的——毕竟,这些水也可能是广告公司、经纪公司、粉丝买的。比如金晨的微博转发之所以有异常增长,就很可能是她粉丝干的:
而如果你问她们,为什么要给自己姐姐用机器刷数据,她们的回答一般是:为了给姐姐排面、为了让姐姐数据更好看——与此同时,却浑然不知或者假装不知,这样低劣的机刷数据,在稍微像样点的娱乐数据分析公司那里,都能一秒现原形。
后记
在这篇文章里举出的疑似发大水的曲线,还只是我观测到的所有曲线图中的一小部分。
我的程序从德国的昨天早上跑到现在,一共抓住了十几位发了新微博的明星,有了他们的微博从第 0 分钟开始的传播数据全程变化。
在这十几个人当中,除了我肉眼翻完过评论鉴定的何炅何老师仍然保持着阴性对照的优良作风之外,其他大部分人,包括一些非常有名的演员和歌手,都有这种几分钟内几千几万的异常的增长。
唯一区别,只在这些刷的量和他们真实流量的比例多少:
有的人 5/1,有的人 10~20/1,有的人甚至 100/1 以上。
是的,数据造假是不光彩的。然而,在这泥沙俱下的流量经济时代之中,无论是初出茅庐急需认可的年轻艺人,还是已经功成名就的圈内前辈,只要还想继续从广告投放商那里赚钱,那么每个人都得被逼着接受这样的现实。这一风雨晦暝的时代,何时才能落下帷幕呢?愿我们仍能见证那一天的到来。
可根据微博ID找手机号?新“社工库”又来了吗?
“很多人的手机号码泄露了,根据微博账号就能查到手机号……已经有人通过微博泄露查到我的手机号码,来加我微信了。”
这是3月19日,安全界大佬“安全_云舒”在微博上放出的一条重磅消息。
并且,据称甚至连微博CEO王高飞本尊也不幸中招,真是泄密起来“一视同仁”,特公平。
更有用户表示,发现5.38亿条微博用户信息在暗网出售,涉及到的账号信息包括用户ID、账号发布的微博数、粉丝数、关注数、性别、地理位置等。
其中,1.72亿条有账户基本信息,售价0.177比特币。
微博官方回应
针对数据泄露事件,微博很快就回应承认属实,并表示这起数据泄露不涉及身份证、密码,对微博服务没有影响。
微博安全总监罗诗尧回应称,这些手机号是2019年通过通讯录上传接口被暴力匹配的,内部发现后第一时间已报警。
微博称,此次数据泄露应该追溯到2018年底,当时,有用户通过微博相关接口通过批量手机批量上传通讯录,匹配出几百万个账号昵称,再加上通过其他渠道获取的信息一起对外出售。
此外,微博表示一直有提供“根据通讯录手机号查询微博好友昵称”的服务,用户授权后可以使用。但微博不提供用户性别和身份证号等信息,也没有“根据用户昵称查手机号”的服务。
但网络安全圈有很多大牛对微博安全总监罗诗尧的回应并不认可,有人测试了暗网上的数据,给出了流程。
Telegram上的数据灰产
Telegram是一款匿名聊天软件。如果在这款聊天软件上搜索“社工库”,你便能找到网传出售5亿微博用户数据的灰产商家。
据数位在“社工库”查找自己信息的人士反映,他们查到的个人资料几乎都属实,不仅手机号是真实的,甚至连微博密码都是真实的。
经过验证发现,“社工库”出售的不仅仅是微博相关数据,它还支持QQ查手机/手机查QQ,微博uid查手机/手机反查微博,贴吧账号查手机/手机查贴吧账号。
此外,“社工库”甚至可以可以提供密码、快递、开房、户籍、地址、身份证 、邮箱、账户、个人征信报告等全方位的数据隐私查询服务。
这几乎意味着,如果你想要窥探某个人,而“社工库”又拥有他的数据,你只需要花一定数量的金钱,就能够得到你想要的信息——甚至是那些社会名流,也不能幸免。
交易流程
比如:查名人微博1、我们先去李X乐老师微博,打开他的主页,通过chrome右键打开“源码”模式,获取到李老师的OID:
2、根据李老师的OID,去查询具体信息
李老师的手机号、QQ号已经被查到了:
3、拿到了手机号,就可以通过【精准查询】查询密码了。此处作者使用自己的手机号查询:
可以看到,通过手机号查询得知,我已经暴露了自己的多个密码、真实姓名!
此灰产工具宣称自己是“全网独家数据”,支持QQ查手机/手机查QQ,微博uid查手机/手机反查微博,贴吧账号查手机/手机查贴吧账号,LOL昵称查对应QQ、手机、姓名等。
“社工库”里啥都有,比如与“定位”有关的位置信息:
结尾:安全第一
此次微博到底是“撞库”或“漏水”,以及暗网上这些出售的东西,真真假假,老编也不曾试过,在此也不做评论。
只是,在2016年,微博即与脉脉就抓取用户数据等曾对簿公堂,当时,脉脉也被微博控告称通过用户手机通讯录来非法获取通讯录和新浪微博用户的对应关系。
互联网上,无论是“独行大侠”、“山寨土匪”还是“正规军”,暗地里可能无时不刻都在各种撞库,如今各种“社工库”是越来越多,越碰撞越大。
各位还是小心谨慎为好,最简单最好的防范方式就是不要同一套用户名密码到处使用。狡兔尚懂三窟,兽犹如此,你还那么懒惰,该情何以堪?
切记一条:凡是上了网的东西,就不再是秘密,别管怎么拍着胸脯跟你吹嘘固若金汤,绝不会外泄。老编的密码就曾上了CSDN的泄漏名单,还真查到了。
国内还有个很不好的“风气”,无论什么APP,什么平台,都特喜欢,或是直接强制要求绑定手机号(政策原因),而手机号又都是实名的,造成捆绑信息泄漏也真实防不胜防,“下游”拿到的数据都是100%真实身份的,一个个都是偷着乐。
所以你大概也明白为什么那么多骚扰电话都知道你:买房了、买车了、怀孕了、生孩子了、孩子毕业班了…吧?
这个话题如果要引开说,又要涉及到“隐身术”又是长篇累牍,就不多说了。我想大部分人其实也都已经选了“投降”,看破红尘,任人宰割,互联网时代,大数据画像时代,哪里防得住窥视呢,除非做野人吧?