今日头条sign-node环境

爬取目标

今日头条首页内容推荐内容(也就是下面红线的部分)

也有爬取个人空间与头条新闻的实现,见末尾。

相关接口

可以观察到今日头条首页的第一页请求与之后的请求的url是不大一样的。

第一次请求

https://www.toutiao.com/api/pc/feed/?min_behot_time=0&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1D52F17E1F6715&cp=5F71C6D781553E1&_signature=_signature
// 为了方便观察,已经缩短了_signature参数的长度。

第二次请求及以后的请求

https://www.toutiao.com/api/pc/feed/?max_behot_time=1601263428&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1254F17E13671F&cp=5F715637211FEE1&_signature=_signature
// 为了方便观察,已经缩短了_signature参数的长度。

变动的query参数有max_behot_time,as,cp以及signature。

关于参数逆向

一: max_behot_time

max_behot_time很容易观察出来,这个参数来自与上一次请求的数据中的max_behot_time。

如果是第一次请求的话,这个参数就变成了min_behot_time, 并且其值是定值。

好了,这个就这么搞定了 

二: signature

第二个便是看signature是怎么生成的了。

 

我们通过发起者(initator)找到一个调用函数(如下所示,Reqwest是随机选择的 )

 在此函数的内部下一个断点,然后页面向下滑动下。

 很显然,这个页面要生成的参数到这里都已经生成好了。这时候我们需要通过call stack看看这个函数的调用者们

 

很快就发现了生成_signature参数的地方了

653行的o便是生成的_signature参数了。

就也是如下面的代码所示。

var  url = "https://www.toutiao.com/toutiao/api/pc/feed/?max_behot_time=1601241738&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1E57F277116DED&cp=5F71169DAE7D3E1";
var signature = window.byted_acrawler.sign({url: url});

貌似我们只要把 window.byted_acrawler.sign 的算法弄下来,就可以生成对应的_signature参数了。

 

 

我们进入window.byted_acrawler.sign内部看一看

 明显的代码混淆。逆向并不是要将他的代码都读懂,如果你想这么做的话,那么你就中了他的圈套了。

 

正确的做法应该是是拷贝相关的代码,先尝试在浏览器上运行,如果没有报错,那么说明这个代码没有明显的环境依赖。

如果报错了,就要尝试补环境了,缺哪补哪。

如果浏览器上运行正常,可以拿到node环境进行运行了,node环境要尽量模拟浏览器环境即可。

 

 

首先我们复制下acrawler.js的所有的代码。

开一个新的标签页。(建议地址栏输入 about:blank, 这样便可以得到完全的空白页了。这样就不会受其他网页的干扰了)

粘贴下之前复制的acrawler的js代码到console中并执行,执行完毕后并没有报错,并且window.byted_acrawler.sign已经有了

做个试验,看看生成的_signature对不对。

var  url = "https://www.toutiao.com/toutiao/api/pc/feed/?max_behot_time=1601241738&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1E57F277116DED&cp=5F71169DAE7D3E1";
var signature = window.byted_acrawler.sign({url: url});

生成倒是生成了,倒是长度有些短。长度短的原因是因为没有cookie的缘故。

可以在本地开一个服务器,然后通过document.cookie 获取到今日头条网页的cookie。

使用

"你复制到的cookie".forEach((ele)=>{document.cookie = ele});

便可以将今日头条下所有的cookie复制到本地的网页中了。

这时候生成的_signature参数便是正常的长度了。

浏览器试验完成,现在如果这个js可以在node环境运行的话,那就完美了。

如何在node环境中运行呢?首先这个js是肯定会检测浏览器环境的,包括检测canvas,创建元素啥的。

自己一个个写?太麻烦了。

这里要用到的一个第三方库,那便是jsdom。这是一个利用来模拟浏览器环境的node第三方库。

const jsdom = require("jsdom");
const { JSDOM } = jsdom;
// 
//  
// node_modules/jsdom@16.4.0/api.js
// 
const options = {
    url: "https://www.toutiao.com/",
    // to config userAgent, source code must be altered 
    // "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"

}
const { window } = new JSDOM("", options);
var glb;
// window.glb = glb;
// var global = undefined;
var process = undefined;
var module = undefined;
var exports = undefined;
var { StyleSheet, MediaList, CSSStyleSheet, CSSRule, CSSStyleRule, CSSMediaRule, CSSImportRule, CSSStyleDeclaration, XPathException, XPathExpression, XPathResult, XPathEvaluator, onafterprint, onbeforeprint, onbeforeunload, onhashchange, onlanguagechange, onmessage, onmessageerror, onoffline, ononline, onpagehide, onpageshow, onpopstate, onrejectionhandled, onstorage, onunhandledrejection, onunload, onblur, onerror, onfocus, onload, onresize, onscroll, onabort, onautocomplete, onautocompleteerror, oncancel, oncanplay, oncanplaythrough, onchange, onclick, onclose, oncontextmenu, oncuechange, ondblclick, ondrag, ondragend, ondragenter, ondragexit, ondragleave, ondragover, ondragstart, ondrop, ondurationchange, onemptied, onended, oninput, oninvalid, onkeydown, onkeypress, onkeyup, onloadeddata, onloadedmetadata, onloadstart, onmousedown, onmouseenter, onmouseleave, onmousemove, onmouseout, onmouseover, onmouseup, onwheel, onpause, onplay, onplaying, onprogress, onratechange, onreset, onsecuritypolicyviolation, onseeked, onseeking, onselect, onsort, onstalled, onsubmit, onsuspend, ontimeupdate, ontoggle, onvolumechange, onwaiting, _registeredHandlers, _eventHandlers, _globalObject, _resourceLoader, _globalProxy, _document, _origin, _sessionHistory, _virtualConsole, _runScripts, _top, _parent, _frameElement, _length, _pretendToBeVisual, _storageQuota, _commonForOrigin, _currentOriginData, _localStorage, _sessionStorage, _selection, getSelection, length, frameElement, frames, self, parent, top, document, external, location, history, navigator, locationbar, menubar, personalbar, scrollbars, statusbar, toolbar, performance, screen, origin, localStorage, sessionStorage, customElements, setTimeout, setInterval, clearTimeout, clearInterval, postMessage, atob, btoa, stop, close, getComputedStyle, captureEvents, releaseEvents, console, name, status, devicePixelRatio, innerWidth, innerHeight, outerWidth, outerHeight, pageXOffset, pageYOffset, screenX, screenLeft, screenY, screenTop, scrollX, scrollY, alert, blur, confirm, focus, moveBy, moveTo, open, print, prompt, resizeBy, resizeTo, scroll, scrollBy, scrollTo, glb } = window;

上面的写法骗过今日头条还是挺容易的。

三:as cp参数的生成。

可以看到as cp参数其实是变化的

as cp生成的位置与_signature参数的位置还是挺近的(648行)

this._setParams方法除了对max_behot_time参数的设置,还有对as,cp参数的更新

上面的a函数便可以生成as,cp参数,粘贴到编辑器上,缺啥补啥即可。(约200行,就不粘贴到这里)

四:JSDOM如何修改user-agent?

首先说说为什么要修改user-agent?

这是因为_signature中会使用user-agent,在发送请求时,headers中的user-agent要与_signature中的保持一致。

const jsdom = require("jsdom");
const {JSDOM} = jsdom;
const resourceLoader = new jsdom.ResourceLoader({
    strictSSL: false,
    userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
  });
const { window } = new JSDOM(``, { 
    resources: resourceLoader, 
    url: "https://www.toutiao.com/"
});
console.log(window.navigator.userAgent);
console.log(window.location.href);

  

相关源码

首页

链接:https://pan.baidu.com/s/1WVnGF8UntdYkLvz37dw5EQ
提取码:04qr

个人空间(run.py)与特定url(run2.py)

 链接:https://pan.baidu.com/s/17pswj3IcLn9qhDK8x-9DCA
 提取码:xyai

原文地址:https://www.cnblogs.com/re-is-good/p/13744733.html