突破抖音反爬虫机制,字体图标替换实现通过抖音UID获取真实抖音号
  • 分类:Python
  • 发表:2019-05-19
  • 围观(3,029)
  • 评论(8)

最近在研究抖音app的数据爬取,遇到了一个小小的问题。通过分析mitmproxy抓包返回的json数据发现,有的账号会显示UID,但是抖音号是“0”。按道理来说每个人都有唯一的抖音号。这是为什么呢?我首先想到的是查文档,看看API文档是如何定义的。你爬取别人的数据还指望别人给你写好文档给你查么?这很不现实。自己动手,丰衣足食。初步分析是因为部份抖音号含有字母等字符,不是纯数字的抖音号。所以导致json取到的数据不符合预期。不能直接抖音号我们可以转换思维通过别的途径达到目的啊。所谓条条大路通罗马鸭😀。

"适当"打码保护用户隐私

经过大量的抓包分析,我发现UID才是抖音数据库中作为主键的存在。每个人都有对应的唯一的UID,通过这个UID我们可以做很多事情。https://www.iesdouyin.com/share/user/{UID}-通过构造url,可以进入用户的个人主页。欸嘿,这个主页存在抖音号字段诶。这不就完事了,可以写一个通过UID获取抖音号爬虫啊,说干就干。

我们就拿Kriswu的抖音做个演示

页面分析

事实上说干就干这句话真的不能乱说,拍拍脑袋就做会搞死人啊。确切来说这个页面并不简单,我昨晚就搞到了很晚。但是这个反爬机制是值得写篇文章记录一下的。

首先我们先分析下页面元素,事实上抖音为了防止数据被爬取,所有的的数字数据都是用icon图标填充渲染的。这样一来爬虫获取到的数据就是一串unicode编码的代码,没办法获取真正的数据。但是学过网页开发的我知道icon代码与真实数字肯定是一一对应的啦。这就类似一个简单替换加密,只要把密码表分析出来,真实的抖音号就出来了呀。

html源代码,所有的数字都是标记填充

 

经验丰富的我知道,图标数据肯定是存在于字体文件的,在网页初始化的时候当作资源文件加载然后被渲染到页面。我们先把这个字体文件下载下来看一下。网页抓包打开 -> 页面重新载入,一套操作行云流水。让我来看看数据包里到底有没有这个图标字体文件?好像一眼就被我找到了😎。请求链接:https://s3.pstatp.com/ies/resource/falcon/douyin_falcon/static/font/iconfont_9eb9a50.woff

图标资源文件的加载请求

我们先把这个文件下载下来,拖入到百度字体编辑器分析一波。顺便说一波,百度这个编辑器还是很不错的,起码在线编辑这个就没得黑。

字体文件编辑

接下来只要找出对应关系生成密码表就行了,到此为止所有的准备工作已经完成,现在开始正式的编码。


代码实现

爬取网页: https://www.iesdouyin.com/share/user/{uid}
支持库: requests,re

这里说一下为什么不用BeautifulSoup做数据提取,按道理说这种情况用BeautifulSoup代码应该更加简洁。事实上确实如此,但是由于BeautifulSoup的框架原因在解析页面的时候会对页面进行转码到Unicode,输出的时候又转码为UTF-8。特别是我们的icon代码本身就是unicode,一来一回就把我给转晕了,输出的就是一个乱码。这个在BeautifulSoup文档里面也有提及,但是我就是看不懂 BeautifulSoup 编码。绕来绕去的,索性用正则表达式,也不会很费事其实。

获取网页源码

网页是通过Get请求获取的,但是注意一定要带上浏览器的headers,不然返回的是异常数据。

URL = "https://www.iesdouyin.com/share/user/{}"

HEADERS = {
    "Host": "www.iesdouyin.com",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Connection": "keep-alive"
}

def page_sourse(uid):
    req = requests.get(URL.format(uid), headers=HEADERS)
    short_id = re.search('''(?<=<p class="shortid">)(.*?)(?=</p>)''', req.text).group()
    parse_id = short_id.replace("抖音ID:     ", "").replace('''<i class="icon iconfont "> ''', "").replace(" </i>", "")
    print(parse_id)

>>>page_sourse("72221135360")
kriswu_&#xe618;&#xe618;&#xe616;&#xe61f;

在获取到源数据之后只要根据密码表进行替换就可以得到真实的抖音号啦,并也不算太难。

codes = {
    "&#xe603;": "0", "&#xe60d;": "0", "&#xe616;": "0",
    "&#xe602;": "1", "&#xe60e;": "1", "&#xe618;": "1",
    "&#xe605;": "2", "&#xe610;": "2", "&#xe617;": "2",
    "&#xe604;": "3", "&#xe611;": "3", "&#xe61a;": "3",
    "&#xe606;": "4", "&#xe60c;": "4", "&#xe619;": "4",
    "&#xe607;": "5", "&#xe60f;": "5", "&#xe61b;": "5",
    "&#xe608;": "6", "&#xe612;": "6", "&#xe61f;": "6",
    "&#xe60a;": "7", "&#xe613;": "7", "&#xe61c;": "7",
    "&#xe60b;": "8", "&#xe614;": "8", "&#xe61d;": "8",
    "&#xe609;": "9", "&#xe615;": "9", "&#xe61e;": "9"
}
def code_replace(parse_id):
    for key, value in codes.items():
        if key in parse_id:
            parse_id = parse_id.replace(key, value)
    return parse_id

总结

总的来说,只要思路清晰代码实现起来真的不会很难。但是第一眼遇到这种情况还是很懵逼的,无从下手。毕竟这种情况使用selenium操作浏览器也没办法保存icon图标。所以在开发爬虫的过程中,一定的网页开发基础是必须的,不然搞起来真的是无从下手啊。我简单查了一下,现在有很多网站的数据都是这样渲染进去的,这种反爬机制也是很普遍。这个方法主要用在数字字段上,毕竟如果文本也这样渲染的话浏览器的速度可能会被拖慢,或者网页的布局就不是那么美观。

总之这个思路是值得了解和学习的,做下记录,也不辜负昨晚熬夜调试代码的自己。

完整代码

import requests
import re

URL = "https://www.iesdouyin.com/share/user/{}"

codes = {
    "&#xe603;": "0", "&#xe60d;": "0", "&#xe616;": "0",
    "&#xe602;": "1", "&#xe60e;": "1", "&#xe618;": "1",
    "&#xe605;": "2", "&#xe610;": "2", "&#xe617;": "2",
    "&#xe604;": "3", "&#xe611;": "3", "&#xe61a;": "3",
    "&#xe606;": "4", "&#xe60c;": "4", "&#xe619;": "4",
    "&#xe607;": "5", "&#xe60f;": "5", "&#xe61b;": "5",
    "&#xe608;": "6", "&#xe612;": "6", "&#xe61f;": "6",
    "&#xe60a;": "7", "&#xe613;": "7", "&#xe61c;": "7",
    "&#xe60b;": "8", "&#xe614;": "8", "&#xe61d;": "8",
    "&#xe609;": "9", "&#xe615;": "9", "&#xe61e;": "9"
}

HEADERS = {
    "Host": "www.iesdouyin.com",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Connection": "keep-alive"
}


def page_sourse(uid):
    req = requests.get(URL.format(uid), headers=HEADERS)
    short_id = re.search('''(?<=<p class="shortid">)(.*?)(?=</p>)''', req.text).group()
    parse_id = short_id.replace("抖音ID:     ", "").replace('''<i class="icon iconfont "> ''', "").replace(" </i>", "")
    return parse_id


def get_shortid(uid):
    parse_id = page_sourse(uid)
    true_id = code_replace(parse_id)
    return true_id


def code_replace(parse_id):
    for key, value in codes.items():
        if key in parse_id:
            parse_id = parse_id.replace(key, value)
    return parse_id


if __name__ == '__main__':
    short_id = get_shortid("72221135360")
    print(short_id)

共有 8 条评论

  1. z434

    博主,看了你的几篇爬虫文章,还不错,你这个博客是用什么语言搭建的呢

    1. Weiney

      Continuity

      WordPress啊,这个显而易见

  2. sinbada

    实用

    1. Weiney

      Continuity

      有帮助就好耶,都在学习过程中,共同学习共同进步

    1. Weiney

      Weiney

      嘎嘎现在发现在安卓上用Appium跳转用户详情页并没有那么复杂,可以通过android-Scheme实现页面跳转
      url: snssdk1128://user/profile/{用户的uid}?refer=web&gd_label=click_wap_profile_bottom&type=need_follow&needlaunchlog=1
      具体代码:device.get(url)这样就很轻松跳转到用户详情页,而且最近抖音给页面加了访问频率限制,要用代理才行,不是很方便了

  3. zf

    能加个微信吗,平时可以请教你一些问题

    1. Weiney

      Weiney

      你把你微信号发我邮箱吧:admin@weiney.com我加您
      都是菜鸡,互相学习

Top