爬取目標
今日頭條首頁內容推薦內容(也就是下面紅線的部分)
也有爬取個人空間與頭條新聞的實現,見末尾。

相關接口

可以觀察到今日頭條首頁的第一頁請求與之后的請求的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