微信公衆號批量爬取java版

最近須要爬取微信公衆號的文章信息。在網上找了找發現微信公衆號爬取的難點在於公衆號文章連接在pc端是打不開的,要用微信的自帶瀏覽器(拿到微信客戶端補充的參數,才能夠在其它平臺打開),這就給爬蟲程序形成很大困擾。後來在知乎上看到了一位大牛用php寫的微信公衆號爬取程序,就直接按大佬的思路整了整搞成java的了。改造途中遇到蠻多細節問題,拿出來分享一下。php

附上大牛文章連接:https://zhuanlan.zhihu.com/c_65943221  寫php的或者只須要爬取思路的能夠直接看這個,思路寫的很是詳細。html

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------java

系統的基本思路是在安卓模擬器上運行微信,模擬器設置代理,經過代理服務器攔截微信數據,將獲得的數據發送給本身的程序進行處理。node

須要準備的環境:nodejs,anyproxy代理,安卓模擬器mysql

nodejs下載地址:http://nodejs.cn/download/,我下載的是windows版的,下好直接安裝就行。安裝好後,直接運行C:\Program Files\nodejs\npm.cmd 會自動配置好環境。react

anyproxy安裝:按上一步安裝好nodejs以後,直接在cmd運行 npm install -g anyproxy 就會安裝了web

安卓模擬器隨便在網上下一個就行了,一大堆。sql

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------mongodb

首先爲代理服務器安裝證書,anyproxy默認不解析https連接,安裝證書後就能夠解析了,在cmd執行anyproxy --root 就會安裝證書,以後還得在模擬器也下載這個證書。數據庫

而後輸入anyproxy -i 命令 打開代理服務。(記得加上參數!)

 

 

記住這個ip和端口,以後安卓模擬器的代理就用這個。如今用瀏覽器打開網頁:http://localhost:8002/  這是anyproxy的網頁界面,用於顯示http傳輸數據。

點擊上面紅框框裏面的菜單,會出一個二維碼,用安卓模擬器掃碼識別,模擬器(手機)就會下載證書了,安裝上就行了。

如今準備爲模擬器設置代理,代理方式設置爲手動,代理ip爲運行anyproxy機器的ip,端口是8001

 

到這裏準備工做基本完成,在模擬器上打開微信隨便打開一個公衆號的文章,就能從你剛打開的web界面中看到anyproxy抓取到的數據:

上面紅框內就是微信文章的連接,點擊進去能夠看到具體的數據。若是response body裏面什麼都沒有可能證書安裝有問題。

若是上面都走通了,就能夠接着往下走了。

這裏咱們靠代理服務抓微信數據,但總不能抓取一條數據就本身操做一下微信,那樣還不如直接人工複製。因此咱們須要微信客戶端本身跳轉頁面。這時就可使用anyproxy攔截微信服務器返回的數據,往裏面注入頁面跳轉代碼,再把加工的數據返回給模擬器實現微信客戶端自動跳轉。

打開anyproxy中的一個叫rule_default.js的js文件,windows下該文件在:C:\Users\Administrator\AppData\Roaming\npm\node_modules\anyproxy\lib

在文件裏面有個叫replaceServerResDataAsync: function(req,res,serverResData,callback)的方法,這個方法就是負責對anyproxy拿到的數據進行各類操做。一開始應該只有callback(serverResData);這條語句的意思是直接返回服務器響應數據給客戶端。直接刪掉這條語句,替換成大牛寫的以下代碼。這裏的代碼我並無作什麼改動,裏面的註釋也解釋的給很是清楚,直接按邏輯看懂就行,問題不大。

 1 replaceServerResDataAsync: function(req,res,serverResData,callback){
 2         if(/mp\/getmasssendmsg/i.test(req.url)){//當連接地址爲公衆號歷史消息頁面時(第一種頁面形式)
 3             //console.log("開始第一種頁面爬取");
 4             if(serverResData.toString() !== ""){
try {//防止報錯退出程序 7 var reg = /msgList = (.*?);/;//定義歷史消息正則匹配規則 8 var ret = reg.exec(serverResData.toString());//轉換變量爲string 9 HttpPost(ret[1],req.url,"/InternetSpider/getData/showBiz");//這個函數是後文定義的,將匹配到的歷史消息json發送到本身的服務器 10 var http = require('http'); 11 http.get('http://xxx/getWxHis', function(res) {//這個地址是本身服務器上的一個程序,目的是爲了獲取到下一個連接地址,將地址放在一個js腳本中,將頁面自動跳轉到下一頁。後文將介紹getWxHis.php的原理。 12 res.on('data', function(chunk){ 13 callback(chunk+serverResData);//將返回的代碼插入到歷史消息頁面中,並返回顯示出來 14 }) 15 }); 16 }catch(e){//若是上面的正則沒有匹配到,那麼這個頁面內容多是公衆號歷史消息頁面向下翻動的第二頁,由於歷史消息第一頁是html格式的,第二頁就是json格式的。 17 //console.log("開始第一種頁面爬取向下翻形式"); 18 try { 19 var json = JSON.parse(serverResData.toString()); 20 if (json.general_msg_list != []) { 21 HttpPost(json.general_msg_list,req.url,"/xxx/showBiz");//這個函數和上面的同樣是後文定義的,將第二頁歷史消息的json發送到本身的服務器 22 } 23 }catch(e){ 24 console.log(e);//錯誤捕捉 25 } 26 callback(serverResData);//直接返回第二頁json內容 27 } 28 } 29 //console.log("開始第一種頁面爬取 結束"); 30 }else if(/mp\/profile_ext\?action=home/i.test(req.url)){//當連接地址爲公衆號歷史消息頁面時(第二種頁面形式) 31 try { 32 var reg = /var msgList = \'(.*?)\';/;//定義歷史消息正則匹配規則(和第一種頁面形式的正則不一樣) 33 var ret = reg.exec(serverResData.toString());//轉換變量爲string 34 HttpPost(ret[1],req.url,"/xxx/showBiz");//這個函數是後文定義的,將匹配到的歷史消息json發送到本身的服務器 35 var http = require('http'); 36 http.get('xxx/getWxHis', function(res) {//這個地址是本身服務器上的一個程序,目的是爲了獲取到下一個連接地址,將地址放在一個js腳本中,將頁面自動跳轉到下一頁。後文將介紹getWxHis.php的原理。 37 res.on('data', function(chunk){ 38 callback(chunk+serverResData);//將返回的代碼插入到歷史消息頁面中,並返回顯示出來 39 }) 40 }); 41 }catch(e){ 42 //console.log(e); 43 callback(serverResData); 44 } 45 }else if(/mp\/profile_ext\?action=getmsg/i.test(req.url)){//第二種頁面表現形式的向下翻頁後的json 46 try { 47 var json = JSON.parse(serverResData.toString()); 48 if (json.general_msg_list != []) { 49 HttpPost(json.general_msg_list,req.url,"/xxx/showBiz");//這個函數和上面的同樣是後文定義的,將第二頁歷史消息的json發送到本身的服務器 50 } 51 }catch(e){ 52 console.log(e); 53 } 54 callback(serverResData); 55 }else if(/mp\/getappmsgext/i.test(req.url)){//當連接地址爲公衆號文章閱讀量和點贊量時 56 try { 57 HttpPost(serverResData,req.url,"/xxx/getMsgExt");//函數是後文定義的,功能是將文章閱讀量點贊量的json發送到服務器 58 }catch(e){ 59 60 } 61 callback(serverResData); 62 }else if(/s\?__biz/i.test(req.url) || /mp\/rumor/i.test(req.url)){//當連接地址爲公衆號文章時(rumor這個地址是公衆號文章被闢謠了) 63 try { 64 var http = require('http'); 65 http.get('http://xxx/getWxPost', function(res) {//這個地址是本身服務器上的另外一個程序,目的是爲了獲取到下一個連接地址,將地址放在一個js腳本中,將頁面自動跳轉到下一頁。後文將介紹getWxPost.php的原理。 66 res.on('data', function(chunk){ 67 callback(chunk+serverResData); 68 }) 69 }); 70 }catch(e){ 71 callback(serverResData); 72 } 73 }else{ 74 callback(serverResData); 75 } 76 //callback(serverResData); 77 },

這裏簡單解釋一下,微信公衆號的歷史消息頁連接有兩種形式:一種以 mp.weixin.qq.com/mp/getmasssendmsg 開頭,另外一種是 mp.weixin.qq.com/mp/profile_ext 開頭。歷史頁是能夠向下翻的,若是向下翻將觸發js事件發送請求獲得json數據(下一頁內容)。還有公衆號文章連接,以及文章的閱讀量和點贊量的連接(返回的是json數據),這幾種連接的形式是固定的能夠經過邏輯判斷來區分。這裏有個問題就是歷史頁若是須要所有爬取到該怎麼作到。個人思路是經過js去模擬鼠標向下滑動,從而觸發提交加載下一部分列表的請求。或者直接利用anyproxy分析下滑加載的請求,直接向微信服務器發生這個請求。但都有一個問題就是如何判斷已經沒有餘下數據了。我是爬取最新數據,暫時沒這個需求,可能之後要。若是有需求的能夠嘗試一下。

下圖是上文中的HttpPost方法內容。

 1 function HttpPost(str,url,path) {//將json發送到服務器,str爲json內容,url爲歷史消息頁面地址,path是接收程序的路徑和文件名
 2     console.log("開始執行轉發操做");
 3     try{
 4     var http = require('http');
 5     var data = {
 6         str: encodeURIComponent(str),
 7         url: encodeURIComponent(url)
 8     };
 9     data = require('querystring').stringify(data);
10     var options = {
11         method: "POST",
12         host: "xxx",//注意沒有http://,這是服務器的域名。
13         port: xxx,
14         path: path,//接收程序的路徑和文件名
15         headers: {
16             'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
17             "Content-Length": data.length
18         }
19     };
20     var req = http.request(options, function (res) {
21         res.setEncoding('utf8');
22         res.on('data', function (chunk) {
23             console.log('BODY: ' + chunk);
24         });
25     });
26     req.on('error', function (e) {
27         console.log('problem with request: ' + e.message);
28     });
29     
30     req.write(data);
31     req.end();
32     }catch(e){
33         console.log("錯誤信息:"+e);
34     }
35     console.log("轉發操做結束");
36 }

作完以上工做,接下來就是按本身業務來完成服務端代碼了,咱們的服務用於接收代理服務器發過來的數據進行處理,進行持久化操做,同時向代理服務器發送須要注入到微信的js代碼。針對代理服務器攔截到的幾種不一樣連接發來的數據,咱們就須要設計相應的方法來處理這些數據。從anyproxy處理微信數據的js方法replaceServerResDataAsync: function(req,res,serverResData,callback)中,咱們能夠知道至少須要對公衆號歷史頁數據、公衆號文章頁數據、公衆號文章點贊量和閱讀量數據設計三種方法來處理。同時咱們還須要設計一個方法來生成爬取任務,完成公衆號的輪尋爬取。若是須要爬取更多數據,能夠從anyproxy抓取到的連接中分析出更多須要的數據,而後往replaceServerResDataAsync: function(req,res,serverResData,callback)中添加斷定,攔截到須要的數據發送到本身的服務器,相應的在服務端添加方法處理該類數據就好了。

我是用java寫的服務端代碼。

處理公衆號歷史頁數據方法:

public void getMsgJson(String str ,String url) throws UnsupportedEncodingException {
        // TODO Auto-generated method stub
        String biz = "";
        Map<String,String> queryStrs = HttpUrlParser.parseUrl(url);
        if(queryStrs != null){
            biz = queryStrs.get("__biz");
            biz = biz + "==";
        }
        /**
         * 從數據庫中查詢biz是否已經存在,若是不存在則插入,
         * 這表明着咱們新添加了一個採集目標公衆號。
         */
        List<WeiXin> results = weiXinMapper.selectByBiz(biz);
        if(results == null || results.size() == 0){
            WeiXin weiXin = new WeiXin();
            weiXin.setBiz(biz);
            weiXin.setCollect(System.currentTimeMillis());
            weiXinMapper.insert(weiXin);
        }
        //System.out.println(str);
        //解析str變量
        List<Object> lists = JsonPath.read(str, "['list']");
        for(Object list : lists){
            Object json = list;
            int type = JsonPath.read(json, "['comm_msg_info']['type']");
            if(type == 49){//type=49表示是圖文消息
                String content_url = JsonPath.read(json, "$.app_msg_ext_info.content_url");
                content_url = content_url.replace("\\", "").replaceAll("amp;", "");//得到圖文消息的連接地址
                int is_multi = JsonPath.read(json, "$.app_msg_ext_info.is_multi");//是不是多圖文消息
                Integer datetime = JsonPath.read(json, "$.comm_msg_info.datetime");//圖文消息發送時間
                /**
                 * 在這裏將圖文消息連接地址插入到採集隊列庫tmplist中
                 * (隊列庫將在後文介紹,主要目的是創建一個批量採集隊列,
                 * 另外一個程序將根據隊列安排下一個採集的公衆號或者文章內容)
                 */
                try{
                    if(content_url != null && !"".equals(content_url)){
                        TmpList tmpList = new TmpList();
                        tmpList.setContentUrl(content_url);
                        tmpListMapper.insertSelective(tmpList);
                    }
                }catch(Exception e){
                    System.out.println("隊列已存在,不插入!");
                }
                
                /**
                 * 在這裏根據$content_url從數據庫post中判斷一下是否重複
                 */
                List<Post> postList = postMapper.selectByContentUrl(content_url);
                boolean contentUrlExist = false;
                if(postList != null && postList.size() != 0){
                    contentUrlExist = true;
                }
            
                
                if(!contentUrlExist){//'數據庫post中不存在相同的$content_url'
                    Integer fileid = JsonPath.read(json, "$.app_msg_ext_info.fileid");//一個微信給的id
                    String title = JsonPath.read(json, "$.app_msg_ext_info.title");//文章標題
                    String title_encode = URLEncoder.encode(title, "utf-8");
                    String digest = JsonPath.read(json, "$.app_msg_ext_info.digest");//文章摘要
                    String source_url = JsonPath.read(json, "$.app_msg_ext_info.source_url");//閱讀原文的連接
                    source_url = source_url.replace("\\", "");
                    String cover = JsonPath.read(json, "$.app_msg_ext_info.cover");//封面圖片
                    cover = cover.replace("\\", "");
                    /**
                     * 存入數據庫
                     */
//                    System.out.println("頭條標題:"+title);
//                    System.out.println("微信ID:"+fileid);
//                    System.out.println("文章摘要:"+digest);
//                    System.out.println("閱讀原文連接:"+source_url);
//                    System.out.println("封面圖片地址:"+cover);                    
                    
                    Post post = new Post();
                    post.setBiz(biz);
                    post.setTitle(title);
                    post.setTitleEncode(title_encode);
                    post.setFieldId(fileid);
                    post.setDigest(digest);
                    post.setSourceUrl(source_url);
                    post.setCover(cover);
                    post.setIsTop(1);//標記一下是頭條內容
                    post.setIsMulti(is_multi);
                    post.setDatetime(datetime);
                    post.setContentUrl(content_url);
                    
                    postMapper.insert(post);
                }
            
                if(is_multi == 1){//若是是多圖文消息
                    List<Object> multiLists = JsonPath.read(json, "['app_msg_ext_info']['multi_app_msg_item_list']");
                    for(Object multiList : multiLists){
                        Object multiJson = multiList;                    
                        content_url = JsonPath.read(multiJson, "['content_url']").toString().replace("\\", "").replaceAll("amp;", "");//圖文消息連接地址
                        /**
                         * 這裏再次根據$content_url判斷一下數據庫中是否重複以避免出錯
                         */
                        contentUrlExist = false;
                        List<Post> posts = postMapper.selectByContentUrl(content_url);
                        if(posts != null && posts.size() != 0){
                            contentUrlExist = true;
                        }
                        if(!contentUrlExist){//'數據庫中不存在相同的$content_url'
                            /**
                             * 在這裏將圖文消息連接地址插入到採集隊列庫中
                             * (隊列庫將在後文介紹,主要目的是創建一個批量採集隊列,
                             * 另外一個程序將根據隊列安排下一個採集的公衆號或者文章內容)
                             */
                            if(content_url != null && !"".equals(content_url)){
                                TmpList tmpListT = new TmpList();
                                tmpListT.setContentUrl(content_url);
                                tmpListMapper.insertSelective(tmpListT);
                            }
                            
                            String title = JsonPath.read(multiJson, "$.title");
                            String title_encode = URLEncoder.encode(title, "utf-8");
                            Integer fileid = JsonPath.read(multiJson, "$.fileid");
                            String digest = JsonPath.read(multiJson, "$.digest");
                            String source_url = JsonPath.read(multiJson, "$.source_url");
                            source_url = source_url.replace("\\", "");
                            String cover = JsonPath.read(multiJson, "$.cover");
                            cover = cover.replace("\\", "");                        
//                            System.out.println("標題:"+title);
//                            System.out.println("微信ID:"+fileid);
//                            System.out.println("文章摘要:"+digest);
//                            System.out.println("閱讀原文連接:"+source_url);
//                            System.out.println("封面圖片地址:"+cover);                            
                            Post post = new Post();
                            post.setBiz(biz);
                            post.setTitle(title);
                            post.setTitleEncode(title_encode);
                            post.setFieldId(fileid);
                            post.setDigest(digest);
                            post.setSourceUrl(source_url);
                            post.setCover(cover);
                            post.setIsTop(0);//標記一下不是頭條內容
                            post.setIsMulti(is_multi);
                            post.setDatetime(datetime);
                            post.setContentUrl(content_url);
                            
                            postMapper.insert(post);
                            
                        }
                    }
                }            
            }        
        }
    }

 

處理公衆號文章頁的方法:

public String getWxPost() {
        // TODO Auto-generated method stub
        /**
         * 當前頁面爲公衆號文章頁面時,讀取這個程序
         * 首先刪除採集隊列表中load=1的行
         * 而後從隊列表中按照「order by id asc」選擇多行(注意這一行和上面的程序不同)
         */
        tmpListMapper.deleteByLoad(1);
        List<TmpList> queues = tmpListMapper.selectMany(5);
        String url = "";
        if(queues != null && queues.size() != 0 && queues.size() > 1){
            TmpList queue = queues.get(0);
            url = queue.getContentUrl();
            queue.setIsload(1);
            int result = tmpListMapper.updateByPrimaryKey(queue);
            System.out.println("update result:"+result);
        }else{
            System.out.println("getpost queues is null?"+queues==null?null:queues.size());
            WeiXin weiXin = weiXinMapper.selectOne();
            String biz = weiXin.getBiz();
            if((Math.random()>0.5?1:0) == 1){
                url = "http://mp.weixin.qq.com/mp/getmasssendmsg?__biz=" + biz + 
                        "#wechat_webview_type=1&wechat_redirect";//拼接公衆號歷史消息url地址(第一種頁面形式)
            }else{
                url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz + 
                        "#wechat_redirect";//拼接公衆號歷史消息url地址(第二種頁面形式)
            }
            url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz + 
                    "#wechat_redirect";//拼接公衆號歷史消息url地址(第二種頁面形式)
            //更新剛纔提到的公衆號表中的採集時間time字段爲當前時間戳。
            weiXin.setCollect(System.currentTimeMillis());
            int result = weiXinMapper.updateByPrimaryKey(weiXin);
            System.out.println("getPost weiXin updateResult:"+result);
        }
        int randomTime = new Random().nextInt(3) + 3;
        String jsCode = "<script>setTimeout(function(){window.location.href='"+url+"';},"+randomTime*1000+");</script>";
        return jsCode;
        
    }

 

處理公衆號點贊量和閱讀量的方法:

public void getMsgExt(String str,String url) {
        // TODO Auto-generated method stub
        String biz = "";
        String sn = "";
        Map<String,String> queryStrs = HttpUrlParser.parseUrl(url);
        if(queryStrs != null){
            biz = queryStrs.get("__biz");
            biz = biz + "==";
            sn = queryStrs.get("sn");
            sn = "%" + sn + "%";
        }
        /**
         * $sql = "select * from `文章表` where `biz`='".$biz."'
         * and `content_url` like '%".$sn."%'" limit 0,1;
         * 根據biz和sn找到對應的文章
         */
        Post post = postMapper.selectByBizAndSn(biz, sn);
        
        if(post == null){
            System.out.println("biz:"+biz);
            System.out.println("sn:"+sn);
            tmpListMapper.deleteByLoad(1);
            return;
        }
        
//        System.out.println("json數據:"+str);
        Integer read_num;
        Integer like_num;
        try{
            read_num = JsonPath.read(str, "['appmsgstat']['read_num']");//閱讀量
            like_num  = JsonPath.read(str, "['appmsgstat']['like_num']");//點贊量
        }catch(Exception e){
            read_num = 123;//閱讀量
            like_num  = 321;//點贊量
            System.out.println("read_num:"+read_num);
            System.out.println("like_num:"+like_num);
            System.out.println(e.getMessage());
        }        
        
        /**
         * 在這裏一樣根據sn在採集隊列表中刪除對應的文章,表明這篇文章能夠移出採集隊列了
         * $sql = "delete from `隊列表` where `content_url` like '%".$sn."%'" 
         */
        tmpListMapper.deleteBySn(sn);
        
        //而後將閱讀量和點贊量更新到文章表中。
        post.setReadnum(read_num);
        post.setLikenum(like_num);
        postMapper.updateByPrimaryKey(post);
        
    }

 

處理跳轉向微信注入js的方法:

public String getWxHis() {
        String url = "";
        // TODO Auto-generated method stub
        /**
         * 當前頁面爲公衆號歷史消息時,讀取這個程序
         * 在採集隊列表中有一個load字段,當值等於1時表明正在被讀取
         * 首先刪除採集隊列表中load=1的行
         * 而後從隊列表中任意select一行
         */
        tmpListMapper.deleteByLoad(1);
        TmpList queue = tmpListMapper.selectRandomOne();
        System.out.println("queue is null?"+queue);
        if(queue == null){//隊列表爲空
            /**
             * 隊列表若是空了,就從存儲公衆號biz的表中取得一個biz,
             * 這裏我在公衆號表中設置了一個採集時間的time字段,按照正序排列以後,
             * 就獲得時間戳最小的一個公衆號記錄,並取得它的biz
             */
            WeiXin weiXin = weiXinMapper.selectOne();
            
            String biz = weiXin.getBiz();
            url = "https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=" + biz + 
                    "#wechat_redirect";//拼接公衆號歷史消息url地址(第二種頁面形式)
            //更新剛纔提到的公衆號表中的採集時間time字段爲當前時間戳。
            weiXin.setCollect(System.currentTimeMillis());
            int result = weiXinMapper.updateByPrimaryKey(weiXin);
            System.out.println("getHis weiXin updateResult:"+result);
        }else{
            //取得當前這一行的content_url字段
            url = queue.getContentUrl();
            //將load字段update爲1
            tmpListMapper.updateByContentUrl(url);
        }
        //將下一個將要跳轉的$url變成js腳本,由anyproxy注入到微信頁面中。
        //echo "<script>setTimeout(function(){window.location.href='".$url."';},2000);</script>";
        int randomTime = new Random().nextInt(3) + 3;
        String jsCode = "<script>setTimeout(function(){window.location.href='"+url+"';},"+randomTime*1000+");</script>";
        return jsCode;
    }

以上就是對處理代理服務器攔截到的數據進行處理的程序。這裏有一個須要注意的問題,程序會對數據庫中的每一個收錄的公衆號進行輪循訪問,甚至是已經存儲的文章也會再次訪問,目的是爲了一直更新文章的閱讀數和點贊數。若是須要抓取大量的公衆號建議對添加任務隊列的代碼進行修改,添加條件限制,不然公衆號一多 輪循抓取重複數據將十分影響效率。

至此就將微信公衆號的文章連接所有爬取到,並且這個連接是永久有效並且能夠在瀏覽器打開的連接,接下來就是寫爬蟲程序從數據庫中拿連接爬取文章內容等信息了。

我是用webmagic寫的爬蟲,輕量好用。

public class SpiderModel implements PageProcessor{
    
    private static PostMapper postMapper;
    
    private static List<Post> posts;
    
    // 抓取網站的相關配置,包括編碼、抓取間隔、重試次數等
    private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
    
    public Site getSite() {
        // TODO Auto-generated method stub
        return this.site;
    }
    
    public void process(Page page) {
        // TODO Auto-generated method stub
        Post post = posts.remove(0);
        String content = page.getHtml().xpath("//div[@id='js_content']").get();
        //存在和諧文章 此處作斷定若是有直接刪除記錄或設置表示位表示文章被和諧
        if(content == null){
            System.out.println("文章已和諧!");
            //postMapper.deleteByPrimaryKey(post.getId());
            return;
        }
        String contentSnap = content.replaceAll("data-src", "src").replaceAll("preview.html", "player.html");//快照
        String contentTxt = HtmlToWord.stripHtml(content);//純文本內容
        
        Selectable metaContent = page.getHtml().xpath("//div[@id='meta_content']");
        String pubTime = null;
        String wxname = null;
        String author = null;
        if(metaContent != null){
            pubTime = metaContent.xpath("//em[@id='post-date']").get();
            if(pubTime != null){
                pubTime = HtmlToWord.stripHtml(pubTime);//文章發佈時間
            }
            wxname = metaContent.xpath("//a[@id='post-user']").get();
            if(wxname != null){
                wxname = HtmlToWord.stripHtml(wxname);//公衆號名稱
            }
            author = metaContent.xpath("//em[@class='rich_media_meta rich_media_meta_text' and @id!='post-date']").get();
            if(author != null){
                author = HtmlToWord.stripHtml(author);//文章做者
            }
        }
        
//        System.out.println("發佈時間:"+pubTime);
//        System.out.println("公衆號名稱:"+wxname);
//        System.out.println("文章做者:"+author);
        
        String title = post.getTitle().replaceAll("&nbsp;", "");//文章標題
        String digest = post.getDigest();//文章摘要
        int likeNum = post.getLikenum();//文章點贊數
        int readNum = post.getReadnum();//文章閱讀數
        String contentUrl = post.getContentUrl();//文章連接
        
        WechatInfoBean wechatBean = new WechatInfoBean();
        wechatBean.setTitle(title);
        wechatBean.setContent(contentTxt);//純文本內容
        wechatBean.setSourceCode(contentSnap);//快照
        wechatBean.setLikeCount(likeNum);
        wechatBean.setViewCount(readNum);
        wechatBean.setAbstractText(digest);//摘要
        wechatBean.setUrl(contentUrl);
        wechatBean.setPublishTime(pubTime);
        wechatBean.setSiteName(wxname);//站點名稱 公衆號名稱
        wechatBean.setAuthor(author);
        wechatBean.setMediaType("微信公衆號");//來源媒體類型
        
        WechatStorage.saveWechatInfo(wechatBean);
        
        //標示文章已經被爬取
        post.setIsSpider(1);
        postMapper.updateByPrimaryKey(post);
        
    }    
    
    public static void startSpider(List<Post> inposts,PostMapper myPostMapper,String... urls){
        
        long startTime, endTime;
        startTime = System.currentTimeMillis();
        postMapper = myPostMapper;
        posts = inposts;
        
        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();        
        SpiderModel spiderModel = new SpiderModel();
        Spider mySpider = Spider.create(spiderModel).addUrl(urls);
        mySpider.setDownloader(httpClientDownloader);
        try {
            SpiderMonitor.instance().register(mySpider);
            mySpider.thread(1).run();
        } catch (JMException e) {
            e.printStackTrace();
        }
        
        endTime = System.currentTimeMillis();
        System.out.println("爬取時間" + ((endTime - startTime) / 1000) + "秒--");
        
    }
    
}

其它的一些無關邏輯的存儲數據代碼就不貼了,這裏我把代理服務器抓取到的數據存在了mysql,把本身的爬蟲程序爬到的數據存儲在了mongodb。

下面是本身爬取到的公衆號號的信息:

 

 ___________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________18.4.23更新

微信已經修改點贊量和閱讀量請求連接,連接中已不包含參數biz和sn,故而咱們拿到閱讀量和點贊量後沒法找到屬於哪篇文章了。

可是能夠從請求頭中的referer 獲取到,看下圖

而後咱們小小修改一下rule_default.js文件,把請求頭數據獲取到,丟給服務器,後臺再解析數據拿到referer 提取出裏面的sn和biz就ok了

nodejs獲取請求頭的方法:req.headers  拿到後記得作下轉換JSON.stringify(xxx) 而後丟給後臺就好了  修改十行不到的代碼就不貼出來了

最後提醒下要控制採集頻率,別放在那兒一直採,會被警告惡意刷公衆號文章流量,我已經被封了一個小號!!!共勉

愛好技術的能夠一塊兒交流交流 我微信號:zazymA

相關文章
相關標籤/搜索