本文共 9287 字,大约阅读时间需要 30 分钟。
目的:采集抖音热度较高的视频 (要自动化)
抖音的防爬技术做的特别好,据说是专门有反爬部门。所以通过编写代码直接去访问抖音接口,不能达到目的。只能模拟真实用户行为,来得到数据。
我主要的实现方法是:通过 在 安卓模拟器里 模拟用户滑动,通过网络代理拦截滑动过程中产生的数据
开发中用到的工具:
硬件:需要一台空闲的电脑, 软件:auto.js、安卓模拟器、、按键精灵、抓包工具1 确定要从抖音的哪个接口里采集数据
考虑 从首页推荐列表 或 用户作品列表 取数据。
我用的fiddler抓包工具,抓取 首页推荐列表 接口,发现数据格式为Protobuf,是一个传输速度更快、占用空间更小的数据格式,解析这样的格式需要配套的文件。所以我们无法解析,放弃 页推荐列表。
尝试从用户作品列表接口 抓包,发现是json格式,而且可以拿到视频信息。所以决定从用户作品列表 采集。
2. 自动化工具 auto.js 模拟用户滑动抖音列表为了确保 采集的视频热度要高,我们并不是所有的用户作品都采集。所以我们模拟的用户行为:在首页的推荐视频里滑动,滑到点赞量大于10万的视频,向左滑动,进入该视频作者的作品列表。
首先要在电脑里 安装好 安卓模拟器,我用的是天天模拟器
在模拟器里 安装抖音 和 auto.js 应用,编写auto.js 自动化脚本,运行脚本。var myDate = new Date();var hours = myDate.getHours();if (hours >= 0) { console.log("去启动抖音"); launchApp("抖音"); sleep(7000) while (true) { 是否满足赞(); 左滑进入个人中心(); 判断是否出去(); 关闭崩溃应用(); toast("quit persion center ") 退出个人中心(); //Swipe(10, device.height / 2,device.width / 2, device.height / 2, 10, 300);//向右滑 sleep(2000); toast("hua dong cao zuo ") Swipe(device.width / 2, device.height / 1.5, device.width / 2, 10, 500); //向下滑 sleep(3000); 每10分钟重启(); 取消弹框(); 判断是否出去(); 关闭崩溃应用(); }}function 是否满足赞() { log("是否满足赞") try { //不满足1万的赞划走 while (isTrue()) { Swipe(device.width / 2, device.height / 1.5, device.width / 2, 10, 500); //向下滑 toast("Dig not satisfied") sleep(1500); 退出个人中心(); } } catch (e) { }}function isTrue() { // var u = id("aen").find() // var e = u.length - 2 // var tv = u[e]; return false; var like = 0; try { var b = id("com.ss.android.ugc.aweme:id/aer").find(); var a = b[1].desc() if (a && a.indexOf("喜欢") > -1) { like = a.substring(a.indexOf("喜欢") + 2, a.indexOf(",按钮")); toastLog(like); } } catch (e) { } if (like.indexOf("w") == -1) { return true; } else { return like.substr(0, like.indexOf("w")) < 5; }}function 左滑进入个人中心() { log("左滑进入个人中心") toast("left swipe ") Swipe(device.width / 1.1, device.height / 2, 10, device.height / 2, 10, 800); //向左滑 //id("text1").className("android.widget.TextView").textMatches(/(.*作品.*)/).findOnce().click(); sleep(2000); //关注或加载更多(); sleep(2000);}function 每10分钟重启() { log("每10分钟重启") var myDate = new Date(); if (myDate.getMinutes() % 10 == 0 && myDate.getSeconds() <= 30) { //每10分钟重启一次 app.openAppSetting("com.ss.android.ugc.aweme"); // text(app.getAppName("com.ss.android.ugc.aweme")).waitFor(); sleep(2000) let is_sure = textMatches(/(.*强.*|.*停.*|.*结.*|.*行.*)/).findOne(); if (is_sure.enabled()) { sleep(1000); textMatches(/(.*强.*|.*停.*|.*结.*|.*行.*)/).findOne().click(); sleep(1000); textMatches(/(.*确.*|.*定.*)/).findOne().click(); 清除缓存(); sleep(500); back(); back(); log("应用已被关闭"); sleep(1000); back(); sleep(1000) } else { log("应用不能被正常关闭或不在后台运行"); back(); } }}function 清除缓存() { log("清除缓存") sleep(20000) try { click("内部存储空间"); click("正在计算"); sleep(2000) } catch (e) { } try { textMatches(/(.*清除缓存.*)/).findOnce().click(); } catch (e) { }}//如果APP出现弹框,那么点击取消弹框function 取消弹框() { log("取消弹框") try { if (id("a7_").exists()) { id("a7_").findOnce().click(); console.log("tankuan1") } if (id("eem").exists) { console.log("tankuan2") id("eem").findOnce().parent().click() } if (id("ru").exists()) { id("ru").findOnce().click() console.log("tankuan3") } if (id("bsv").exists()) { console.log("tankuan4") id("bsv").findOnce().click(); } if (id("c4").exists()) { console.log("tankuan5") id("c4").findOnce().click() } } catch (e) { }}//判断APP是否已经退出,如果已经退出,那么重新启动APPfunction 判断是否出去() { log("判断是否出去") if (!packageName("com.ss.android.ugc.aweme").exists()) { back(); sleep(300); back(); sleep(300); back(); back(); toast("lack reload dou yin"); launchApp("抖音短视频"); sleep(15000); }}//处理app奔溃的弹框function 关闭崩溃应用() { log("关闭崩溃应用") try { if (textMatches(/(.*确.*|.*定.*)/).exists()) { textMatches(/(.*确.*|.*定.*)/).findOnce().click(); sleep(1000); } if (textMatches(/(.*以后再说.*)/).exists()) { textMatches(/(.*以后再说.*)/).findOnce().click(); sleep(1000); } if (textMatches(/(.*我知道了.*)/).exists()) { textMatches(/(.*我知道了.*)/).findOnce().click(); sleep(1000); } } catch (e) { }}function 退出个人中心() { log("退出个人中心") if (id("com.ss.android.ugc.aweme:id/ko").exists()) { id("com.ss.android.ugc.aweme:id/ko").findOnce().click(); } if (id("com.ss.android.ugc.aweme:id/a1l").exists()) { id("com.ss.android.ugc.aweme:id/a1l").findOne().click() } if (id("com.ss.android.ugc.aweme:id/a1x").exists()) { id("com.ss.android.ugc.aweme:id/a1x").findOne().click() } if (desc("返回").exists()) { desc("返回").findOne().click(); }}function 关注或加载更多() { try { sleep(2000); /*toastLog("11"); var totallike = id("adx").findOnce().text(); toastLog("1.2"); var totalfans = id("aw6").findOnce().text(); var uniqueid = id("f7w").findOnce().text(); var authorname = id("cid").findOnce().text(); toastLog("222")*/ var loadMore = false; var totallike, totalfans, uniqueid, authorname var arr = className("android.widget.TextView").find(); for (var i in arr) { var text try { text = arr[i].text(); } catch (e) { } if (text && text == "获赞") { totallike = arr[i - 1].text(); } if (text && text == "粉丝") { totalfans = arr[i - 1].text(); } if (text && text.indexOf("抖音号:") > -1) { uniqueid = text; authorname = arr[i - 1].text(); } } if (!uniqueid) { return } toastLog("id: " + uniqueid + " 集赞:" + totallike + " 粉丝:" + totalfans + " name:" + authorname) //总点赞数大于1000W 关注作者 if (authorname && totallike.indexOf("w") > -1) { if (parseInt(totallike.substr(0, totallike.length - 1)) >= 1000) { //关注作者 payAttention(totallike, totalfans, uniqueid, authorname); } } //总点赞数或粉丝数大于1亿 关注作者并抓取更多 if (authorname && totallike.indexOf("亿") > -1 || totalfans.indexOf("亿") > -1) { //关注作者 loadMore = payAttention(totallike, totalfans, uniqueid, authorname); } //粉丝量大于500W 关注作者, 如果粉丝量大于1000W 抓取作者更多作品 if (authorname && totalfans.indexOf("w") > -1) { var fansCount = parseInt(totalfans.substr(0, totalfans.length - 1)); if (fansCount >= 500 && fansCount < 1000) { //关注作者 payAttention(totallike, totalfans, uniqueid, authorname); } else if (fansCount >= 1000) { loadMore = payAttention(totallike, totalfans, uniqueid, authorname); } } if (loadMore) { sleep(4000); log("into swipe action ") for (var i = 0; i < 4; i++) { Swipe(device.width / 2, device.height / 1.5, device.width / 2, 10, 500); //向下滑 } } sleep(1000); } catch (e) { toastLog("attention err : " + e) }}//关注作者 function payAttention(totallike, totalfans, uniqueid, authorname) { http.get("http://testtest/schedule/adddouyinauthor?uniqueid=" + uniqueid + "&authorname=" + authorname + "&totallike=" + totallike + "&totalfans=" + totalfans, { }, function(res, err) { if (err) { console.error(err); return false; } var data = res.body.json(); log("return result : " + data.result) //关注作者状态:1已关注 0已拉黑取消关注 if (data.result == 1) { id("com.ss.android.ugc.aweme:id/d21").findOnce().click(); } else { id("com.ss.android.ugc.aweme:id/avq").findOnce().click(); return false; } }); return true;}
3.启动网络代理服务项目 拦截想要的数据,并发送到自己的服务器 入库
在自己修改后部署到 安装模拟器的那台电脑上。修改的核心代码:
代理项目部署后,在安卓模拟器上 设置代理及端口 (代理项目的部署相关详细的请参考GitHub学习)。当全部 部署和设置完成后,启动auto.js的自动化脚本,开始刷抖音。在刷到用户中心后,代理服务器会拦截数据 然后发送到 我们线上的服务器,继而 再做数据解析 ,入库。
数据解析部分代码如下:
4.用按键精灵,自动启动auto.js里的脚本
自动化脚本在安卓模拟器中运行的时候,有时会造成模拟器卡死,如果一旦卡死,我们必须人工去重启,这很麻烦。为了确保所有都自动化, 所以我们 定时每5个小时重启一次电脑,在电脑启动时,自动开启安卓模拟器里的auto.js脚本 和 开启 网络代理服务项目。
这里是 通过编写按键精灵脚本 实现 以上的启动
|
|以上就是我完成 自动化 采集抖音 的大概过程。其中编写相关脚本和 部署代理服务项目 花费了很多时间。
这里只描述了大概过程,有些详细细节 并没写到,比如:
采集的视频链接,只有1个小时的时效性。必须尽快下载到自己服务器。 即使是模拟真实用户行为,但是频繁的刷抖音,也会被限制 如:刷不到热门视频。 所以要限制每天程序的执行时间。|
| |以上就是我的开发过程,我相信还有其他更高效的方法。
转载地址:http://uomen.baihongyu.com/