【声明:本文章仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和作者无关】
今日头条web版的请求主要参数是:as、cp、_signature。
as、cp 比较简单,直接使用 js 源码,或者用 python 编译都可以。_signature比较复杂。
依照经验来看,_signature 更新频率很频繁,有时半个月就更新。所以此篇文章代码不一定长期有效,但是解决方案通用。
随便打开今日头条网页版一个界面,示例这里打开的是 “热点“ 分栏,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 的数值看似是时间戳,但是比较发现,并不是访问链接时的真实时间戳。
推断是由特定函数生成。
我们观察一下网页请求返回的 json 数据。发现除了返回的新闻内容之外,还有一个 next,包含 max_behot_time 的值。
通过比较发现,这个 next 中 max_behot_time 的值,正是页面下滑时,下一个请求 url 中 max_behot_time。
由于头条没有明确的页码,于是判断由 “max_behot_time” 的数值充当 “页码”。由于 next 的值可以直接获取,我们就不必分析其生成函数了。
F12,打开 Sources 全局搜索 ”as“ 。
因为词太短,我们发现了上百条数据。想找 as 的生成函数犹如大海捞针。
换个思路,我们可以查一下 “max_behot_time”,在关键函数周围观察一下有没有 as、cp 的生成函数。
F12,打开 Sources 全局搜索 ”max_behot_time“ 。只有一条函数,格式化代码后观察:
我们不必看 max_behot_time,正好它下方有 as、cp 的函数。为了判断是不是我们要的值,我们在函数结尾处打断点,刷新网页,查看 as、cp 的数值。
正是我们需要的 as、cp 的值,再观察函数,由 e 函数生成,即上图画红圈部分。关键函数为 _.default ,我们把鼠标放在 _.default 上跳转到相关函数。
这里就是 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"}
F12,打开 Sources 全局搜索 ”_signature“ 。
我们看到两条结果。两条都看一下:第一条是构造函数,第二条只是调用了值。我们分析第一条。
在关键函数结尾行打断点,刷新页面。等待页面解析完成后,鼠标放在 _signature 上,看到了我们想要的值。
仔细观察,_signature 的值由 tacSign 函数生成。
鼠标放在 tacSign 上,点击上方的 f tacSign(e,t) 跳转到相关函数。见下图
把上一个函数打的断点取消!然后在 tacsign 函数结尾行打断点,点击下图蓝色箭头 / F8 刷新界面。
可以看到 i 是我们想要的值,由 window.byted_acrawler.sign(o) 生成,参数 o 为访问链接。(正常流程为先获取as、cp 值,然后构造链接作为参数 o 调用 window.byted_acrawler.sign 得到 _signature)
鼠标放在 window.byted_acrawler.sign 上,点击弹出的 f e(),跳转到目标函数。
跳转到这里,看到这个千万别蒙蔽,这只是一个超级大的函数而已,大概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 中直接运行。
我们运行之后发现一系列报错,需要添加一系列参数,下面一步一步来。
上述代码运行时报错如下:
【window is not defined】,那么我们在开头添加一下 window:
window = global;
运行一下,依旧报错如下:
【Cannot read property ‘href’ of undefined】,href未定义。
这里我们需要模拟环境,需要用到 jsdom。
安装好 node.js 后,在命令行模式下使用 npm install jsdom 安装。
如果没搞定的话,自行百度一下。
安装好后,写一个最简单的界面,然后添加头条的 href。
那么头条的 href 在哪里呢?我们打开头条页面,F12打开控制台,输入 “window.location” 后回车,可见下图:
我们在 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);
运行一下,但是依旧报错。
【Cannot read property ‘userAgent’ of undefined】,userAgent 未定义。
打开头条页面,F12打开控制台,输入 “window.navigator” 后回车,可见下图:
我们在 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);
运行一下,报错如下:
【Cannot read property ‘width’ of undefined】,width 未定义。
打开头条页面,F12打开控制台,输入 “window.screen” 后回车,可见下图:
我们在 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 值。
页面可以正常访问,也能获取到数据:
考虑到利益相关就不放完整代码了
上述 js 文件在 node.js 运行正常,不要忘了安装 jsdom。
【声明:本文章仅供技术交流,请勿用于非法用途,如有其它非法用途造成损失,和作者无关】