1. 程式人生 > >php+phantomjs實現今日頭條的首頁推送抓取

php+phantomjs實現今日頭條的首頁推送抓取

多人 -m tom 部件 length ole this 地方 有時

第一次搞爬蟲,經驗不足,爬出來的效果也不是很好,記錄一下吧。

認識的哥們最近在爬今日頭條的數據,不過他是做java的。之前也想用php做點爬數據的東西,於是直接也搞今日頭條,萬一有不明白的地方還能有個人商量。話不多說,上點幹貨。

關於爬蟲,我之前的認知是,curl+正則,有點模糊,下面一步一步說吧

一、觀察頁面

  今日頭條的首頁推送數據,是通過ajax獲取的,打開頁面調試我們可以看到下圖

  技術分享圖片

  請求是每次滾動條滾動到底部觸發的,然後我們右鍵新頁面打開這個鏈接

  技術分享圖片

 不難發現這是個接口,返回json數據,在線格式化一下,結果如下

  

 1 {
 2     "has_more": true
, 3 "message": "success", 4 "data": [{ 5 "chinese_tag": "財經", 6 "media_avatar_url": "//p1.pstatp.com/large/4d00054b126ceaf920", 7 "is_feed_ad": false, 8 "tag_url": "news_finance", 9 "title": "重要裏程碑!中國政府采購服務器列入“中國芯”", 10 "single_mode": false, 11 "middle_mode": false
, 12 "abstract": "盡管美國對中興銷售零部件和軟件的禁令將被解除。近日有媒體註意到,在中央國家機關發布的新采購名單中,服務器產品的技術要求格外引人註目。", 13 "tag": "news_finance", 14 "label": ["網絡安全", "龍芯", "CPU", "信息安全", "英特爾"], 15 "behot_time": 1527057631, 16 "source_url": "/group/6558562819279684109/", 17 "source": "環球網", 18
"more_mode": false, 19 "article_genre": "article", 20 "comments_count": 628, 21 "group_source": 2, 22 "item_id": "6558562819279684109", 23 "has_gallery": false, 24 "group_id": "6558562819279684109", 25 "media_url": "/c/user/5954781019/" 26 }, { 27 "single_mode": true, 28 "abstract": "一個動作,一句話語,一個眼神,這些看似微小的細節,有時都有著豐富的內涵,傳遞出意味深長的信號。中美華盛頓磋商過去已有幾天,但美方最近釋放的兩個小細節,感覺拼出了一個更完整的中美談判成果。", 29 "middle_mode": true, 30 "more_mode": true, 31 "tag": "news_world", 32 "label": ["中美關系", "中興", "國際"], 33 "comments_count": 233, 34 "tag_url": "news_world", 35 "title": "這兩個小細節,拼出一個更完整的中美談判成果", 36 "chinese_tag": "國際", 37 "source": "牛彈琴", 38 "group_source": 2, 39 "has_gallery": false, 40 "media_url": "/c/user/3647305700/", 41 "media_avatar_url": "//p2.pstatp.com/large/1566/2791711243", 42 "image_list": [{ 43 "url": "//p3.pstatp.com/list/pgc-image/15269858649775498ea28fe" 44 }, { 45 "url": "//p3.pstatp.com/list/pgc-image/15269858650368af9b6b970" 46 }, { 47 "url": "//p3.pstatp.com/list/pgc-image/1526985864933a9921fd8d0" 48 }], 49 "source_url": "/group/6558354654705484295/", 50 "article_genre": "article", 51 "item_id": "6558354654705484295", 52 "is_feed_ad": false, 53 "behot_time": 1527057631, 54 "image_url": "//p3.pstatp.com/list/190x124/pgc-image/15269858649775498ea28fe", 55 "group_id": "6558354654705484295", 56 "middle_image": "http://p3.pstatp.com/list/pgc-image/15269858649775498ea28fe" 57 }, { 58 "single_mode": true, 59 "abstract": "這個小品還有一個插曲就是原定的女主本來是閆妮,但是在春晚前3天臨時換成了金玉婷,登上春晚後,她還被稱為是“春晚第一美女”。", 60 "middle_mode": true, 61 "more_mode": true, 62 "tag": "news_entertainment", 63 "label": ["春晚", "金玉婷", "抑郁癥", "我是大偵探", "潘長江"], 64 "comments_count": 229, 65 "tag_url": "news_entertainment", 66 "title": "曾5次上春晚,患抑郁癥淡出,如今做主播沒人看,四處走穴很淒涼", 67 "chinese_tag": "娛樂", 68 "source": "貓眼娛樂", 69 "group_source": 2, 70 "has_gallery": false, 71 "media_url": "/c/user/64781639962/", 72 "media_avatar_url": "//p3.pstatp.com/large/2c6b001dd55cf954a3f6", 73 "image_list": [{ 74 "url": "//p3.pstatp.com/list/pgc-image/15270494556167c19bc847f" 75 }, { 76 "url": "//p3.pstatp.com/list/pgc-image/1527049455588fe78498ed1" 77 }, { 78 "url": "//p3.pstatp.com/list/pgc-image/152704945540787d17fdfb9" 79 }], 80 "source_url": "/group/6558629445270241805/", 81 "article_genre": "article", 82 "item_id": "6558629445270241805", 83 "is_feed_ad": false, 84 "behot_time": 1527057631, 85 "image_url": "//p3.pstatp.com/list/190x124/pgc-image/15270494556167c19bc847f", 86 "group_id": "6558629445270241805", 87 "middle_image": "http://p3.pstatp.com/list/pgc-image/15270494556167c19bc847f" 88 }], 89 "next": { 90 "max_behot_time": 1527057631 91 } 92 }

  我們看這個json的結構,data裏的就是我們想要的數據,好了我們只要仿造地址然後 curl模擬請求獲取數據就得了。

 二、爬蟲研發

  分析數據接口

    地址:‘https://www.toutiao.com/api/pc/feed/?max_behot_time=1527057712&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=A1656B107572ABC&cp=5B05529AAB2CEE1&_signature=PNjkzBAeZ-Tys2Ie-4uYMTzY5N‘

  

  對比之後發現,每次觸發的ajax地址只有max_behot_time、as、cp、_signature這4個參數有變化。max_behot_time有點像時間戳,格式化一下,果然是。然後就是as、cp、_signature三個變參,百度了下是js對時間戳的加密出來的。

  知乎上對as、cp有一些文章,_signature百度出來的結果很少。

  

  再次打開頁面調試

  技術分享圖片

找到參數生成的js,格式化js代碼找到如下代碼

  片段一:返回3個參數

  

{
            key: "_setParams",
            value: function(t) {
                var e = (0, h.
            default)(),
                i = 0;
                this.url = this._url,
                "refresh" === t ? (i = this.list.length > 0 ? this.list[0].behot_time: 0, this.url += "min_behot_time=" + i) : (i = this.list.length > 0 ? this.list[this.list.length - 1].behot_time: 0, this.url += "max_behot_time=" + i);
                var n = (0, _.sign)(i + ""); (0, a.default)(this.params, {
                    as: e.as,
                    cp: e.cp,
                    _signature: n
                })
            }
        }

  片段二:as、cp 時間戳加密邏輯,對照知乎上那片文章,這個可以用php的邏輯寫,easy

 

 1 function s() {
 2         var t = Math.floor((new Date).getTime() / 1e3),
 3         e = t.toString(16).toUpperCase(),
 4         i = (0, o.
 5     default)(t).toString().toUpperCase();
 6         if (8 != e.length) return {
 7             as: "479BB4B7254C150",
 8             cp: "7E0AC8874BB0985"
 9         };
10         for (var n = i.slice(0, 5), s = i.slice( - 5), a = "", r = 0; r < 5; r++) a += n[r] + e[r];
11         for (var l = "",
12         u = 0; u < 5; u++) l += e[u + 3] + s[u];
13         return {
14             as: "A1" + a + e.slice( - 3),
15             cp: e.slice(0, 3) + l + "E1"
16         }
17     }

  還差一個參數_signature,這個參數有點難找,結合百度找到了js文件

 技術分享圖片

  相關代碼片段:

  

 1 function(t, e) {
 2     Function(function(t) {
 3         return ‘e(e,a,r){(b[e]||(b[e]=t("x,y","x "+e+" y")(r,a)}a(e,a,r){(k[r]||(k[r]=t("x,y","new x[y]("+Array(r+1).join(",x[y]")(1)+")")(e,a)}r(e,a,r){n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t)s[n="$"+t]=r[n];for(t=0,b=s=a;t<b;t)s[t]=a[t];c(e,0,s)}c(t,b,k){u(e){v[x]=e}f{g=,ting(bg)}l{try{y=c(t,b,k)}catch(e){h=e,y=l}}for(h,y,d,g,v=[],x=0;;)switch(g=){case 1:u(!)4:f5:u((e){a=0,r=e;{c=a<r;c&&u(e[a]),c}}(6:y=,u((y8:if(g=,lg,g=,y===c)b+=g;else if(y!==l)y9:c10:u(s(11:y=,u(+y)12:for(y=f,d=[],g=0;g<y;g)d[g]=y.charCodeAt(g)^g+y;u(String.fromCharCode.apply(null,d13:y=,h=delete [y]14:59:u((g=)?(y=x,v.slice(x-=g,y:[])61:u([])62:g=,k[0]=65599*k[0]+k[1].charCodeAt(g)>>>065:h=,y=,[y]=h66:u(e(t[b],,67:y=,d=,u((g=).x===c?r(g.y,y,k):g.apply(d,y68:u(e((g=t[b])<"<"?(b--,f):g+g,,70:u(!1)71:n72:+f73:u(parseInt(f,3675:if(){bcase 74:g=<<16>>16g76:u(k[])77:y=,u([y])78:g=,u(a(v,x-=g+1,g79:g=,u(k["$"+g])81:h=,[f]=h82:u([f])83:h=,k[]=h84:!085:void 086:u(v[x-1])88:h=,y=,h,y89:u({e{r(e.y,arguments,k)}e.y=f,e.x=c,e})90:null91:h93:h=0:;default:u((g<<16>>16)-16)}}n=this,t=n.Function,s=Object.keys||(e){a={},r=0;for(c in e)a[r]=c;a=r,a},b={},k={};r‘.replace(/[-]/g,
 4         function(e) {
 5             return t[15 & e.charCodeAt(0)]
 6         })
 7     } ("v[x++]=v[--x]t.charCodeAt(b++)-32function return ))++.substrvar .length(),b+=;break;case ;break}".split("")))()(‘gr$Daten Иb/s!l y?y?g,(lfi~ah`{mv,-n|jqewVxp{rvmmx,&effkx[!cs"l".Pq%widthl"@q&heightl"vr*getContextx$"2d[!cs#l#,*;?|u.|uc{uq$fontl#vr(fillTextx$$龘???2<[#c}l#2q*shadowBlurl#1q-shadowOffsetXl#$$limeq+shadowColorl#vr#arcx88802[%c}l#vr&strokex[ c}l"v,)}eOmyoZB]mx[ cs!0s$l$Pb<k7l l!r&lengthb%^l$1+s$jl  s#i$1ek1s$gr#tack4)zgr#tac$! +0o![#cj?o ]!l$b%s"o ]!l"l$b*b^0d#>>>s!0s%yA0s"l"l!r&lengthb<k+l"^l"1+s"jl  s&l&z0l!$ +["cs\‘(0l#i\‘1ps9wxb&s() &{s)/s(gr&Stringr,fromCharCodes)0s*yWl ._b&s o!])l l Jb<k$.aj;l .Tb<k$.gj/l .^b<k&i"-4j!+& s+yPo!]+s!l!l Hd>&l!l Bd>&+l!l <d>&+l!l 6d>&+l!l &+ s,y=o!o!]/q"13o!l q"10o!],l 2d>& s.{s-yMo!o!]0q"13o!]*Ld<l 4d#>>>b|s!o!l q"10o!],l!& s/yIo!o!].q"13o!],o!]*Jd<l 6d#>>>b|&o!]+l &+ s0l-l!&l-l!i\‘1z141z4b/@d<l"b|&+l-l(l!b^&+l-l&zl\‘g,)gk}ejo{cm,)|yn~Lij~em["cl$b%@d<l&zl\‘l $ +["cl$b%b|&+l-l%8d<@b|l!b^&+ q$sign ‘, [Object.defineProperty(e, "__esModule", {
 8         value: !0
 9     })])
10 }

  

  我槽,第一眼看,這代碼好尼瑪長,php怎麽搞啊!

  然後有個想法,我直接用js跑出這個 參數來不就得了嗎,easy

  代碼實現:(註意sign這個算法的編碼,我被搞過) 從這個鏈接裏去復制:https://bbs.125.la/thread-14108290-1-1.html

 1 <!DOCTYPE html>
 2 <html>
 3     <head>
 4         <title>check</title>
 5     </head>
 6     <body>
 7         <script type="text/javascript" src="{{ URL::asset(‘/assets/js/md5.js‘) }}"></script>
 8         <script type="text/javascript">
 9             function xx(t){
10                 var e = t.toString(16).toUpperCase(),
11                 i = md5(t.toString()).toUpperCase(),
12                 str = ‘‘;
13                 if (8 != e.length){
14                     str = ‘http://www.toutiao.com/api/pc/feed/?max_behot_time=‘+t+‘&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=479BB4B7254C150&cp=7E0AC8874BB0985&_signature=‘+TAC.sign(t);
15                     return str;
16                 }
17                 for (var n = i.slice(0, 5), s = i.slice( - 5), a = "", r = 0; r < 5; r++) {
18                     a += n[r] + e[r];
19                 }
20                 for (var l = "",u = 0; u < 5; u++) {
21                     l += e[u + 3] + s[u];
22                 }
23                 str = ‘http://www.toutiao.com/api/pc/feed/?max_behot_time=‘+t+‘&category=__all__&utm_source=toutiao&widen=1&tadrequire=true&as=‘+"A1" + a + e.slice( - 3)+‘&cp=‘+e.slice(0, 3) + l + "E1"+‘&_signature=‘+TAC.sign(t);
24                 return str;
25             }
26             Function(function(t) {
return ‘e(e,a,r){(b[e]||(b[e]=t("x,y","x "+e+" y")(r,a)}a(e,a,r){(k[r]||(k[r]=t("x,y","new x[y]("+Array(r+1).join(",x[y]")(1)+")")(e,a)}r(e,a,r){n,t,s={},b=s.d=r?r.d+1:0;for(s["$"+b]=s,t=0;t<b;t)s[n="$"+t]=r[n];for(t=0,b=s=a;t<b;t)s[t]=a[t];c(e,0,s)}c(t,b,k){u(e){v[x]=e}f{g=,ting(bg)}l{try{y=c(t,b,k)}catch(e){h=e,y=l}}for(h,y,d,g,v=[],x=0;;)switch(g=){case 1:u(!)4:f5:u((e){a=0,r=e;{c=a<r;c&&u(e[a]),c}}(6:y=,u((y8:if(g=,lg,g=,y===c)b+=g;else if(y!==l)y9:c10:u(s(11:y=,u(+y)12:for(y=f,d=[],g=0;g<y;g)d[g]=y.charCodeAt(g)^g+y;u(String.fromCharCode.apply(null,d13:y=,h=delete [y]14:59:u((g=)?(y=x,v.slice(x-=g,y:[])61:u([])62:g=,k[0]=65599*k[0]+k[1].charCodeAt(g)>>>065:h=,y=,[y]=h66:u(e(t[b],,67:y=,d=,u((g=).x===c?r(g.y,y,k):g.apply(d,y68:u(e((g=t[b])<"<"?(b--,f):g+g,,70:u(!1)71:n72:+f73:u(parseInt(f,3675:if(){bcase 74:g=<<16>>16g76:u(k[])77:y=,u([y])78:g=,u(a(v,x-=g+1,g79:g=,u(k["$"+g])81:h=,[f]=h82:u([f])83:h=,k[]=h84:!085:void 086:u(v[x-1])88:h=,y=,h,y89:u({e{r(e.y,arguments,k)}e.y=f,e.x=c,e})90:null91:h93:h=0:;default:u((g<<16>>16)-16)}}n=this,t=n.Function,s=Object.keys||(e){a={},r=0;for(c in e)a[r]=c;a=r,a},b={},k={};r‘.replace(/[-]/g, function(i) {
return t[15 & i.charCodeAt(0)]
})
}("v[x++]=v[--x]t.charCodeAt(b++)-32function return ))++.substrvar .length(),b+=;break;case ;break}".split("")))()(‘gr$Daten Иb/s!l y?y?g,(lfi~ah`{mv,-n|jqewVxp{rvmmx,&effkx[!cs"l".Pq%widthl"@q&heightl"vr*getContextx$"2d[!cs#l#,*;?|u.|uc{uq$fontl#vr(fillTextx$$龘???2<[#c}l#2q*shadowBlurl#1q-shadowOffsetXl#$$limeq+shadowColorl#vr#arcx88802[%c}l#vr&strokex[ c}l"v,)}eOmyoZB]mx[ cs!0s$l$Pb<k7l l!r&lengthb%^l$1+s$jl s#i$1ek1s$gr#tack4)zgr#tac$! +0o![#cj?o ]!l$b%s"o ]!l"l$b*b^0d#>>>s!0s%yA0s"l"l!r&lengthb<k+l"^l"1+s"jl s&l&z0l!$ +["cs\‘(0l#i\‘1ps9wxb&s() &{s)/s(gr&Stringr,fromCharCodes)0s*yWl ._b&s o!])l l Jb<k$.aj;l .Tb<k$.gj/l .^b<k&i"-4j!+& s+yPo!]+s!l!l Hd>&l!l Bd>&+l!l <d>&+l!l 6d>&+l!l &+ s,y=o!o!]/q"13o!l q"10o!],l 2d>& s.{s-yMo!o!]0q"13o!]*Ld<l 4d#>>>b|s!o!l q"10o!],l!& s/yIo!o!].q"13o!],o!]*Jd<l 6d#>>>b|&o!]+l &+ s0l-l!&l-l!i\‘1z141z4b/@d<l"b|&+l-l(l!b^&+l-l&zl\‘g,)gk}ejo{cm,)|yn~Lij~em["cl$b%@d<l&zl\‘l $ +["cl$b%b|&+l-l%8d<@b|l!b^&+ q$sign ‘, [TAC = {}]);32 var t = Math.floor((new Date).getTime() / 1e3); 33 var tmpstr = xx(t); 34 $(‘body‘).append(‘<p>‘+tmpstr+‘</p>‘); 35 </script> 36 </body> 37 </html>

  效果參見:http://58.87.108.192/check (可加時間戳參數,例:http://58.87.108.192/check/1527057712)

  

  然後是抓取數據的事了,php沒辦法抓取動態數據!

  這塊我當時很頭疼,問了問同事才知道要用v8引擎的拓展去解析

  然後裝v8js,資料太少裝不上,gg。想別的方法,百度了一下發現抓取動態數據的時候很多人用 phantomjs,無頭瀏覽器,咦,我那哥們也是用的這個。

  安裝 phantomjs 博客網上很多,不贅述。放一個鏈接:https://blog.csdn.net/wanght89/article/details/78320375

  

  js代碼(獲取之前頁面生成的 url)

  check.js

 1 var page = require(‘webpage‘).create(),
 2     system = require(‘system‘);
 3 var url = ‘http://58.87.108.192/check‘;
 4 if (system.args.length === 2){
 5    url += ‘/‘+system.args[1];
 6 }
 7 page.open(url, function(status) {
 8   if (status !== ‘success‘) {
 9     console.log(‘Unable to access network‘);
10   } else {
11     var ua = page.evaluate(function() {
12         var res = [];
13         var domres = document.getElementsByTagName(‘p‘);
14         for(var i=0;i<domres.length;i++){            
15           res.push(domres[i].textContent);        
16         }        
17         return res;    
18       });    
19       console.log(ua);
20   }
21   phantom.exit();
22   unset(page);
23 });

  

  php 代碼(其實就是一個curl)

  spider.php

 1 <?php
 2 
 3 /**
 4 * 今日頭條首頁爬蟲
 5 */
 6 class Spider
 7 {
 8     private $searchTime = false;
 9     private $stopTime = false;
10 
11     public function __construct()
12     {
13         $this->stopTime = strtotime(date(‘Y-m-d‘));
14         $this->useData();
15     }
16 
17     //curl 獲取接口內容
18     public function curlRequest($url)
19     {
20         $ch = curl_init();
21         curl_setopt($ch,CURLOPT_URL,$url);
22         curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
23         curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,0);
24         curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,0);
25         curl_setopt($ch,CURLOPT_HEADER,0);
26         curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
27         return curl_exec($ch);
28     }
29 
30     //phantomjs 抓取地址
31     public function getUrl()
32     {
33         $commendStr = "phantomjs ****/js/check.js";//地址不讓看
34         if ($this->searchTime) {
35             $commendStr .= ‘ ‘.$this->searchTime;
36         }
37         return (exec($commendStr));
38     }
39 
40     //數據處理
41     public function useData()
42     {
43         $urlStr = $this->getUrl();
44         $urlArr = explode(‘,‘, $urlStr);
45         $res = [];
46         foreach ($urlArr as $curlUrl) {
47             sleep(1);
48             $curlJsonStr = $this->curlRequest($curlUrl);
49             $curlRes = json_decode($curlJsonStr, true);
50             var_dump($curlRes);exit;
51         }
52     }
53 }
54 
55 new Spider();

    爬取出來的數據示例

  技術分享圖片

關於cookie,這個數據接口沒有cookie的校驗,有的話,curl模擬下就行了

  

1 curl_setopt($ch,CURLOPT_COOKIE,$cookie_str);

數據重復問題,接口多次請求會給出很多重復數據,我的處理方法是給表裏面加了個唯一索引,md5str 這個是文章標題md5出來的

總結一下:

  這個爬蟲不是很成功啊,我現在弄一堆url然後去跑,發現第二次數據就沒法獲取到了,這事讓我很煩躁,沒找到原因

  整體的流程走了一邊,數據能拿到但是效率很低。

  感覺php不適合做爬蟲,靜態的數據一個curl就夠了,動態數據就比較棘手,phantomjs 感覺比較笨重,而且好像已經不再支持更新

多說一句感覺寫爬蟲可以有很多種方法,比如我那哥們他就是模擬一步一步點擊去做,我這個直接拼接口地址然後拿數據,有點不倫不類,初體驗,多多包涵吧。

  

php+phantomjs實現今日頭條的首頁推送抓取