【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)

【声明:本文章仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和作者无关】

前情提要

今日头条web版的请求主要参数是:as、cp、_signature。
as、cp 比较简单,直接使用 js 源码,或者用 python 编译都可以。_signature比较复杂。
依照经验来看,_signature 更新频率很频繁,有时半个月就更新。所以此篇文章代码不一定长期有效,但是解决方案通用。

url 分析

随便打开今日头条网页版一个界面,示例这里打开的是 “热点“ 分栏,https://www.toutiao.com/ch/news_hot/。
我们向下滑动页面,不断加载出新的内容。
F12,打开 Network 的 XHR 标签,继续下滑头条网页,观察网页请求链接。
以下为三个示例链接,我们分析一下:

https://www.toutiao.com/api/pc/feed/?max_behot_time=1593976908&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1B56F30D2499BB&cp=5F02A9E95BABBE1&_signature=_02B4Z6wo00d01zpHuZwAAIBCdwlrxaqqUH86Qr0AAJGcHAhZpQ3J5FlvtL7YPc7aHHkzMj8.4OCcbDzsZLdx9nyJFsORucCKvpjaNa7XZXlWKlGeT1Axyx3wjBwVHdSG-pNe9BUjC6ZDDLQ19d

https://www.toutiao.com/api/pc/feed/?max_behot_time=1593966482&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1F59FF0B2C99C0&cp=5F0269F95CD09E1&_signature=_02B4Z6wo00d013nPwKQAAIBCNIES.61rCDd5ysQAAIF5HAhZpQ3J5FlvtL7YPc7aHHkzMj8.4OCcbDzsZLdx9nyJFsORucCKvpjaNa7XZXlWKlGeT1Axyx3wjBwVHdSG-pNe9BUjC6ZDDLQ1f0

https://www.toutiao.com/api/pc/feed/?max_behot_time=1593958007&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1D55FC022299C2&cp=5F02E9A9DC22EE1&_signature=_02B4Z6wo00d01cR.V4gAAIBAiTGF0ZJPBfXEelMAAC4gHAhZpQ3J5FlvtL7YPc7aHHkzMj8.4OCcbDzsZLdx9nyJFsORucCKvpjaNa7XZXlWKlGeT1Axyx3wjBwVHdSG-pNe9BUjC6ZDDLQ1e2

经过比较发现关键变量有:max_behot_time、as、cp、_signature。

max_behot_time 分析

max_behot_time 的数值看似是时间戳,但是比较发现,并不是访问链接时的真实时间戳。
推断是由特定函数生成。
我们观察一下网页请求返回的 json 数据。发现除了返回的新闻内容之外,还有一个 next,包含 max_behot_time 的值。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第1张图片
通过比较发现,这个 next 中 max_behot_time 的值,正是页面下滑时,下一个请求 url 中 max_behot_time。
由于头条没有明确的页码,于是判断由 “max_behot_time” 的数值充当 “页码”。由于 next 的值可以直接获取,我们就不必分析其生成函数了。

as、cp 分析

F12,打开 Sources 全局搜索 ”as“ 。
因为词太短,我们发现了上百条数据。想找 as 的生成函数犹如大海捞针。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第2张图片
换个思路,我们可以查一下 “max_behot_time”,在关键函数周围观察一下有没有 as、cp 的生成函数。
F12,打开 Sources 全局搜索 ”max_behot_time“ 。只有一条函数,格式化代码后观察:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第3张图片
我们不必看 max_behot_time,正好它下方有 as、cp 的函数。为了判断是不是我们要的值,我们在函数结尾处打断点,刷新网页,查看 as、cp 的数值。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第4张图片
正是我们需要的 as、cp 的值,再观察函数,由 e 函数生成,即上图画红圈部分。关键函数为 _.default ,我们把鼠标放在 _.default 上跳转到相关函数。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第5张图片
这里就是 as、cp 的计算函数了。我把它用python编译了一下,方便调用。
i 那里的 o.default 那里可以打断点仔细观察函数,本质就是 md5 加密,感兴趣的朋友可以深入研究一下。

import hashlib
import time

def getHoney():
    t = int(time.time())
    e = hex(t).upper()[2:]
    md = hashlib.md5()
    md.update(str(t).encode('utf-8'))
    i = str(md.hexdigest()).upper()
    if len(e) != 8:
        return {'as': "479BB4B7254C150", 'cp': "7E0AC8874BB0985"}
    s = r = ''
    for k in range(0, 5):
        s = s + i[:5][k] + e[k]
        r = r + e[k+3] + i[-5:][k]
    return {'as': "A1" + s + e[-3:], 'cp': e[:3] + r + "E1"}

_signature 分析

F12,打开 Sources 全局搜索 ”_signature“ 。
我们看到两条结果。两条都看一下:第一条是构造函数,第二条只是调用了值。我们分析第一条。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第6张图片
在关键函数结尾行打断点,刷新页面。等待页面解析完成后,鼠标放在 _signature 上,看到了我们想要的值。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第7张图片
仔细观察,_signature 的值由 tacSign 函数生成。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第8张图片
鼠标放在 tacSign 上,点击上方的 f tacSign(e,t) 跳转到相关函数。见下图
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第9张图片
把上一个函数打的断点取消!然后在 tacsign 函数结尾行打断点,点击下图蓝色箭头 / F8 刷新界面。
在这里插入图片描述
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第10张图片
可以看到 i 是我们想要的值,由 window.byted_acrawler.sign(o) 生成,参数 o 为访问链接。(正常流程为先获取as、cp 值,然后构造链接作为参数 o 调用 window.byted_acrawler.sign 得到 _signature)
鼠标放在 window.byted_acrawler.sign 上,点击弹出的 f e(),跳转到目标函数。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第11张图片
跳转到这里,看到这个千万别蒙蔽,这只是一个超级大的函数而已,大概500行。
我们不必完全看懂,把整个 js 文件考出来即可。
(自己拷贝就好,我这里不贴完整代码了,近500行,只放一下开头和结尾)

var _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(f) {
    return typeof f
}
: function(f) {
    return f && "function" == typeof Symbol && f.constructor === Symbol && f !== Symbol.prototype ? "symbol" : typeof f
}
;
TAC = function() {
………………自己拷贝就好………………
}(),
TAC("484e4……………………”, []);

我们把上述代码保存为单独的文件,比如 sign.js。
在结尾加上两行代码测试一下输出:

sign = window.byted_acrawler.sign({url:"https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1593753712&max_behot_time_tmp=1593753712&tadrequire=true&as=A1E58E2F7E7DA3D&cp=5EFEFDEAE36D7E1"});
console.log(sign);

我是在 pycharm 中安装了 node.js 插件,所以可以在 pycharm 中直接运行。

真正的麻烦刚刚开始

我们运行之后发现一系列报错,需要添加一系列参数,下面一步一步来。
上述代码运行时报错如下:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第12张图片
【window is not defined】,那么我们在开头添加一下 window:

window = global;

运行一下,依旧报错如下:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第13张图片
【Cannot read property ‘href’ of undefined】,href未定义。
这里我们需要模拟环境,需要用到 jsdom。
安装好 node.js 后,在命令行模式下使用 npm install jsdom 安装。
如果没搞定的话,自行百度一下。
安装好后,写一个最简单的界面,然后添加头条的 href。
那么头条的 href 在哪里呢?我们打开头条页面,F12打开控制台,输入 “window.location” 后回车,可见下图:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第14张图片
我们在 window.location 中添加 href 即可,为了更安全,我们把 location 中其他参数也添加进去。

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`

Hello world

`); window = global; var document = dom.window.document; var params = { location:{ hash: "", host: "www.toutiao.com", hostname: "www.toutiao.com", href: "https://www.toutiao.com", origin: "https://www.toutiao.com", pathname: "/", port: "", protocol: "https:", search: "", }, }; Object.assign(window,params); window.document = document; …………这里是复制的近500行代码………… sign = window.byted_acrawler.sign({url:"https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1593753712&max_behot_time_tmp=1593753712&tadrequire=true&as=A1E58E2F7E7DA3D&cp=5EFEFDEAE36D7E1"}); console.log(sign);

运行一下,但是依旧报错。
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第15张图片
【Cannot read property ‘userAgent’ of undefined】,userAgent 未定义。
打开头条页面,F12打开控制台,输入 “window.navigator” 后回车,可见下图:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第16张图片
我们在 window.navigator 中添加 userAgent 即可,为了更安全,我们把 navigator 中其他参数也添加进去。

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`

Hello world

`); window = global; var document = dom.window.document; var params = { location:{ hash: "", host: "www.toutiao.com", hostname: "www.toutiao.com", href: "https://www.toutiao.com", origin: "https://www.toutiao.com", pathname: "/", port: "", protocol: "https:", search: "", }, navigator:{ appCodeName: "Mozilla", appName: "Netscape", appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", cookieEnabled: true, deviceMemory: 8, doNotTrack: null, hardwareConcurrency: 4, language: "zh-CN", languages: ["zh-CN", "zh"], maxTouchPoints: 0, onLine: true, platform: "Win32", product: "Gecko", productSub: "20030107", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", vendor: "Google Inc.", vendorSub: "", }, }; Object.assign(window,params); window.document = document; …………这里是复制的近500行代码………… sign = window.byted_acrawler.sign({url:"https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1593753712&max_behot_time_tmp=1593753712&tadrequire=true&as=A1E58E2F7E7DA3D&cp=5EFEFDEAE36D7E1"}); console.log(sign);

我们运行一下,有结果了!!

_02B4Z6wo00f010OgTfQAAIBAvF-yCbw6FDdDoklAAI.y8d

但是,这只是 _signature 的一部分。我们是不是遗漏了什么?
再全局搜索 window.byted_acrawler,我们在网页源码中发现有一段 js 生成的代码:

window.byted_acrawler && window.byted_acrawler.init({
    aid: 24,
    dfp: true,
    intercept: true, // 开启拦截器后,所有符合下面列表条件的 url 都会自动加上 _signature 参数
    // SDK 会拦截所有使用 XMLHTTPRequest 发送的请求,包括第三方库发出的,所以请严格设置 enablePathList
    enablePathList: [
      '/c/ugc/video/publish/'
    ],
    urlRewriteRules: [
      ['/c/ugc/video/publish/', '/toutiao/c/ugc/video/publish/']
    ]
  })

(不得不说头条更新真是频繁,7月3号还没有SDK拦截)
把上述代码sdk拦截去掉,然后插入 sign.js 中运行一下:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`

Hello world

`); window = global; var document = dom.window.document; var params = { location:{ hash: "", host: "www.toutiao.com", hostname: "www.toutiao.com", href: "https://www.toutiao.com", origin: "https://www.toutiao.com", pathname: "/", port: "", protocol: "https:", search: "", }, navigator:{ appCodeName: "Mozilla", appName: "Netscape", appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", cookieEnabled: true, deviceMemory: 8, doNotTrack: null, hardwareConcurrency: 4, language: "zh-CN", languages: ["zh-CN", "zh"], maxTouchPoints: 0, onLine: true, platform: "Win32", product: "Gecko", productSub: "20030107", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", vendor: "Google Inc.", vendorSub: "", }, }; Object.assign(window,params); window.document = document; …………这里是复制的近500行代码………… window.byted_acrawler && window.byted_acrawler.init({ aid: 24, dfp: true, }) sign = window.byted_acrawler.sign({url:"https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1593753712&max_behot_time_tmp=1593753712&tadrequire=true&as=A1E58E2F7E7DA3D&cp=5EFEFDEAE36D7E1"}); console.log(sign);

运行一下,报错如下:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第17张图片
【Cannot read property ‘width’ of undefined】,width 未定义。
打开头条页面,F12打开控制台,输入 “window.screen” 后回车,可见下图:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第18张图片
我们在 window.screen 中添加 width 即可,为了更安全,我们把 screen 中其他参数也添加进去。

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`

Hello world

`); window = global; var document = dom.window.document; var params = { location:{ hash: "", host: "www.toutiao.com", hostname: "www.toutiao.com", href: "https://www.toutiao.com", origin: "https://www.toutiao.com", pathname: "/", port: "", protocol: "https:", search: "", }, navigator:{ appCodeName: "Mozilla", appName: "Netscape", appVersion: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", cookieEnabled: true, deviceMemory: 8, doNotTrack: null, hardwareConcurrency: 4, language: "zh-CN", languages: ["zh-CN", "zh"], maxTouchPoints: 0, onLine: true, platform: "Win32", product: "Gecko", productSub: "20030107", userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", vendor: "Google Inc.", vendorSub: "", }, "screen":{ availHeight: 1040, availLeft: 0, availTop: 0, availWidth: 1920, colorDepth: 24, height: 1080, pixelDepth: 24, width: 1920, } }; Object.assign(window,params); window.document = document; …………这里是复制的近500行代码………… window.byted_acrawler && window.byted_acrawler.init({ aid: 24, dfp: true, }) sign = window.byted_acrawler.sign({url:"https://www.toutiao.com/api/pc/feed/?category=news_hot&utm_source=toutiao&widen=1&max_behot_time=1593753712&max_behot_time_tmp=1593753712&tadrequire=true&as=A1E58E2F7E7DA3D&cp=5EFEFDEAE36D7E1"}); console.log(sign);

在运行一下,没有报错了。返回值如下:

_02B4Z6wo00f01hue3ugAAIBB5GEhFmYyfxIbnNpAANnz6d

但还不是正常的长度,多方调查发现:是真实网页是带 cookie 访问的,我们的模拟环境没有 cookie,接下来我们添加 cookie。

function setCookie(name, value, seconds) {
    seconds = seconds || 0;
    var expires = "";
    if (seconds != 0 ) {
    var date = new Date();
    date.setTime(date.getTime()+(seconds*1000));
    expires = "; expires="+date.toGMTString();
    }
    document.cookie = name+"="+escape(value)+expires+"; path=/";
}
# 把自己浏览器的真实 cookie 复制过来即可
cookies = "XXXXXXXXXXXXXXXXXXXXXXXXX";
for(let cookie of cookies.split(";")){
    tmp = cookie.split("=");
    setCookie(tmp[0],tmp[1],1800);
}

运行一下,终于!得到了完整的 _signature 值。
在这里插入图片描述
页面可以正常访问,也能获取到数据:
【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版)_第19张图片

完整的 sign.js

考虑到利益相关就不放完整代码了

再次提醒

上述 js 文件在 node.js 运行正常,不要忘了安装 jsdom。
【声明:本文章仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和作者无关】

你可能感兴趣的:(【python爬虫js逆向】今日头条as、cp、_signature参数分析(2020.7.6最新版))