最近須要爬取微信公衆號的文章信息。在網上找了找發現微信公衆號爬取的難點在於公衆號文章連接在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(" ", "");//文章標題 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