├── .gitignore ├── README.md ├── app.js ├── common.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | /.vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wechat_crawler 2 | nodejs爬虫-通过抓取搜狗微信网站获取微信文章信息! 3 | 4 | ## [展示地址](http://nodejs999.com/wxlist) 5 | 6 | ## 说明 7 | ### 使用的模块 8 | * async -- 异步流程控制 [基本使用](http://blog.csdn.net/zzwwjjdj1/article/details/51857959) 9 | * request -- 抓取网站模块 [官网](https://www.npmjs.com/package/request) 10 | * cheerio -- 处理html模块 [官网](https://www.npmjs.com/package/cheerio) 11 | * [我的博客](http://blog.csdn.net/zzwwjjdj1/) 12 | 13 | ### 代码说明 14 | * app.js是主文件,`npm install` 后 `node app` 就可以启动爬虫任务,测试是支付宝公众号,爬取了最近5篇文章; 15 | * common.js是封装好的各种方法. 16 | * 爬取的思路 : 从搜索开始 -> 进入公众号文章列表页面 -> 再分别访问每篇文章 ->同时ajax获取点赞量,阅读量等信息. 因为公众号文章列表和文章内容页的url都是临时链接,大概是2个小时过期,所以每次都需要从搜索开始爬取. 17 | * 注意`验证码`问题,nodejs识别验证码暂时没找到好用的模块,我使用的是第三方接口实现的.授权码已屏蔽. 18 | * 整个爬取过程已完成.至于最后是写成接口还是自动运行,都可以自行修改. 19 | 20 | ## 有问题反馈 21 | 在使用中有任何问题,欢迎反馈给我. 22 | 23 | ## 感激 24 | 感谢以下的朋友,排名不分先后 25 | 26 | * [mnixu](https://github.com/mnixu/) 27 | * [liaizhen](https://github.com/liaizhen/) 28 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var ut = require('./common.js'); 2 | var async = require('async'); 3 | console.log('开始测试!!!') 4 | var public_num = '支付宝'; 5 | //任务数组 6 | var task = []; 7 | //根据public_num搜索公众号,最好是微信号或者微信全名. 8 | task.push(function (callback) { 9 | ut.search_wechat(public_num, callback) 10 | }); 11 | //根据url获取公众号获取最后10条图文列表 12 | task.push(function (url, callback) { 13 | ut.look_wechat_by_url(url, callback) 14 | }) 15 | //根据图文url获取详细信息,发布日期,作者,公众号,阅读量,点赞量等 16 | task.push(function (article_titles, article_urls, article_pub_times, callback) { 17 | ut.get_info_by_url(article_titles, article_urls, article_pub_times, callback) 18 | }) 19 | //执行任务 20 | async.waterfall(task, function (err, result) { 21 | if (err) return console.log(err); 22 | console.log(result); 23 | }) -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var async = require('async'); 3 | var cheerio = require('cheerio'); 4 | var Ut = {}; 5 | 6 | /** 7 | 根据微信号搜索公众号,并获取搜素到的第一个公众号链接 8 | @param {string} public_num 微信号 9 | @param {function} callback 回调函数,callback(null,url) 10 | */ 11 | Ut.search_wechat = function (public_num, callback) { 12 | var encode_public_num = encodeURIComponent(public_num); 13 | var url = `http://weixin.sogou.com/weixin?type=1&query=${encode_public_num}&ie=utf8&_sug_=y&_sug_type_=1`; 14 | request(url, function (err, response, html) { 15 | if (err) return callback(err, null); 16 | if (html.indexOf('302 Found') != -1) return callback(null, '302'); 17 | if (html.indexOf('您的访问过于频繁') != -1) return callback('-访问过于频繁') 18 | var $ = cheerio.load(html); 19 | //公众号页面的临时url 20 | var wechat_num = $($("#sogou_vr_11002301_box_0 a")[0]).attr('href') || ''; 21 | setTimeout(function () { 22 | callback(null, wechat_num.replace(/amp;/g, '')); 23 | }, 1000 + Math.ceil(Math.random() * 500)); 24 | }) 25 | }; 26 | 27 | /** 28 | 获取最近10条图文信息列表 29 | @param {string} url 根据search_wechat方法得到的公众号链接 30 | @param {function} callback 回调函数,callback(null, article_titles, article_urls, article_pub_times) 31 | */ 32 | Ut.look_wechat_by_url = function (url, callback) { 33 | var task3 = []; 34 | //发布时间数组 35 | var article_pub_times = []; 36 | //标题列表数组 37 | var article_titles = []; 38 | //文章临时url列表数组 39 | var article_urls = []; 40 | request(url, function (err, response, html) { 41 | if (err) return callback(err, null, null); 42 | var task4 = []; 43 | if (html.indexOf('为了保护你的网络安全,请输入验证码') != -1) { 44 | //验证验证码 45 | task4.push(function (callback) { 46 | Ut.solve_verifycode(html, url, function (err, result) { 47 | if (err) return callback(err, null); 48 | callback(null, result); 49 | }) 50 | }); 51 | } else { 52 | task4.push(function (callback) { 53 | callback(null, html); 54 | }); 55 | } 56 | task4.push(function (html, callback) { 57 | //文章数组,页面上是没有的,在js中,通过正则截取出来 58 | var msglist = html.match(/var msgList = ({.+}}]});?/); 59 | if (!msglist) return callback(`-没有搜索到 ${publicNum} 的文章,只支持订阅号,服务号不支持!`); 60 | msglist = msglist[1]; 61 | msglist = msglist.replace(/(")/g, '\\\"').replace(/( )/g, ''); 62 | msglist = JSON.parse(msglist); 63 | if (msglist.list.length == 0) return callback(`-没有搜索到 ${publicNum} 的文章,只支持订阅号,服务号不支持!`); 64 | 65 | //循环文章数组,重组数据 66 | msglist.list.forEach(function (msg, index) { 67 | //基本信息,主要是发布时间 68 | var article_info = msg.comm_msg_info; 69 | //发布时间 70 | var article_pub_time = Ut.fmtDate(new Date(article_info.datetime * 1000)).split(" ")[0]; 71 | //第一篇文章 72 | var article_first = msg.app_msg_ext_info; 73 | article_pub_times.push(article_pub_time); 74 | article_titles.push(article_first.title); 75 | article_urls.push('http://mp.weixin.qq.com' + article_first.content_url.replace(/(amp;)|(\\)/g, '')); 76 | if (article_first.multi_app_msg_item_list.length > 0) { 77 | //其他文章 78 | var article_others = article_first.multi_app_msg_item_list; 79 | article_others.forEach(function (article_other, index) { 80 | article_pub_times.push(article_pub_time); 81 | article_titles.push(article_other.title); 82 | article_urls.push('http://mp.weixin.qq.com' + article_other.content_url.replace(/(amp;)|(\\)/g, '')); 83 | }) 84 | } 85 | }) 86 | callback(null); 87 | }) 88 | async.waterfall(task4, function (err, result) { 89 | if (err) return callback(err); 90 | setTimeout(function () { 91 | callback(null, article_titles, article_urls, article_pub_times); 92 | }, 1000 + Math.ceil(Math.random() * 500)); 93 | }) 94 | }) 95 | }; 96 | 97 | /** 98 | 根据图文url获取详细信息,发布日期,作者,公众号,阅读量,点赞量等 99 | @param {array} article_titles 所有标题数组 100 | @param {array} article_urls 所有文章临时url数组 101 | @param {array} article_pub_times 所有发布时间数组 102 | @param {function} callback 回调函数,callback(null, articles); 103 | */ 104 | Ut.get_info_by_url = function (article_titles, article_urls, article_pub_times, callback) { 105 | var task1 = []; 106 | var articles = []; 107 | if (article_urls.length > 0) { 108 | article_urls.forEach(function (article_url, index) { 109 | //此if可以限制获取文章的数量.为了测试,限制抓取5篇 110 | if (index < 5) { 111 | task1.push(function (callback) { 112 | var article_object = { 113 | title: '', url: '', read_num: '', like_num: '', 114 | release_time: '', author: '', wechat_number: '' 115 | } 116 | var task2 = []; 117 | //发布日期,作者,公众号,url 118 | task2.push(function (callback) { 119 | request(article_url, function (err, response, html) { 120 | if (err) return callback(err, null); 121 | var $ = cheerio.load(html); 122 | //发布日期 123 | var release_time = $("#post-date").text(); 124 | //作者 125 | var author = $($(".rich_media_meta_list em")[1]).text(); 126 | //公众号 127 | var wechat_number = $("#post-user").text(); 128 | article_object.release_time = release_time; 129 | article_object.author = author; 130 | article_object.wechat_number = wechat_number; 131 | article_object.title = article_titles[index].replace(/amp;/g, '').replace(/"/g, '"'); 132 | article_object.url = article_urls[index]; 133 | callback(null, article_url); 134 | }) 135 | }) 136 | //阅读量和点赞量,ajax获取 137 | task2.push(function (article_url, callback) { 138 | var ajax_url = article_url.replace(/\/s\?/, '/mp/getcomment?'); 139 | var options = { 140 | url: ajax_url, 141 | json: true, 142 | method: 'GET' 143 | }; 144 | Ut.request_json(options, function (err, data) { 145 | if (err) { 146 | console.log(err) 147 | article_object.read_num = 0; 148 | article_object.like_num = 0; 149 | return callback(null, article_url); 150 | } 151 | article_object.read_num = data.read_num; 152 | article_object.like_num = data.like_num; 153 | callback(null, article_url); 154 | }) 155 | }) 156 | task2.push(function (article_url, callback) { 157 | var suffix_url = `&devicetype=Windows-QQBrowser&version=61030004&pass_ticket=qMx7ntinAtmqhVn+C23mCuwc9ZRyUp20kIusGgbFLi0=&uin=MTc1MDA1NjU1&ascene=1`; 158 | var get_forever_url = article_url + suffix_url; 159 | var options = { 160 | url: get_forever_url, 161 | headers: { 162 | 'User-Agent': 'request' 163 | } 164 | }; 165 | request(options, function (error, response, body) { 166 | if (!error && response.statusCode == 200) { 167 | article_object.url = response.request.href; 168 | } 169 | callback(null); 170 | }); 171 | }) 172 | async.waterfall(task2, function (err, result) { 173 | if (err) return callback(err, null); 174 | articles.push(article_object); 175 | setTimeout(function () { 176 | callback(null); 177 | }, 500 + Math.ceil(Math.random() * 500)); 178 | }) 179 | 180 | }) 181 | } 182 | }) 183 | } 184 | async.waterfall(task1, function (err, result) { 185 | if (err) return callback(err, null); 186 | callback(null, articles); 187 | }) 188 | }; 189 | 190 | /** 191 | 通过第三方接口,解决搜狗微信验证码 192 | @param {string} html ,从html获取验证码等信息 193 | @param {string} url ,验证成功后重新访问的url即公众号链接 194 | */ 195 | Ut.solve_verifycode = function (html, url, callback) { 196 | console.log('识别验证码'); 197 | var code_cookie = ''; 198 | var cert = ''; 199 | var task_code = []; 200 | //获取base64格式的验证码图片 201 | task_code.push(function (callback) { 202 | var $ = cheerio.load(html); 203 | var img_url = 'http://mp.weixin.qq.com' + $("#verify_img").attr('src'); 204 | img_url = `http://mp.weixin.qq.com/mp/verifycode?cert=${(new Date).getTime() + Math.random()}` 205 | cert = img_url.split('=')[1]; 206 | var j = request.jar(); 207 | request.get({ url: img_url, encoding: 'base64', jar: j }, function (err, response, body) { 208 | if (err) return callback(err); 209 | var cookie_string = j.getCookieString(img_url); 210 | code_cookie = cookie_string; 211 | callback(null, body); 212 | }) 213 | }) 214 | //通过第三方接口识别验证码,并返回 215 | task_code.push(function (base64, callback) { 216 | var form = { 217 | img_base64: base64, 218 | typeId: 2040 219 | } 220 | var opts = { 221 | url: 'http://ali-checkcode.showapi.com/checkcode', 222 | method: 'POST', 223 | formData: form, 224 | json: true, 225 | headers: { 226 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 227 | //授权码 228 | "Authorization": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 229 | } 230 | }; 231 | Ut.request_json(opts, function (err, data) { 232 | if (err) return callback(err); 233 | if (data.showapi_res_code == 0) { 234 | callback(null, data.showapi_res_body.Result); 235 | } 236 | }) 237 | }); 238 | //验证验证码是否正确 239 | task_code.push(function (verifycode, callback) { 240 | var verifycode_url = `http://mp.weixin.qq.com/mp/verifycode?cert=${encodeURIComponent(cert)}&input=${encodeURIComponent(verifycode)}`; 241 | var form = { 242 | input: encodeURIComponent(verifycode), 243 | cert: encodeURIComponent(cert) 244 | } 245 | var options = { 246 | url: verifycode_url, 247 | json: true, 248 | formData: form, 249 | method: 'post', 250 | headers: { "Cookie": code_cookie } 251 | }; 252 | Ut.request_json(options, function (err, data) { 253 | if (err) return callback(err); 254 | console.log('验证码识别成功') 255 | callback(null); 256 | }) 257 | }) 258 | //验证码正确重新访问 259 | task_code.push(function (callback) { 260 | request(url, function (err, response, html) { 261 | if (err) return callback(err, null); 262 | //搜狗微信的验证码即使输入成功,有的时候也需要输入几次验证码,所以重复调用solve_verifycode方法 263 | if (html.indexOf('为了保护你的网络安全,请输入验证码') != -1) { 264 | return Ut.solve_verifycode(html, url, callback); 265 | } 266 | callback(null, html); 267 | }) 268 | }) 269 | async.waterfall(task_code, function (err, result) { 270 | if (err) return callback(err, null); 271 | callback(null, result); 272 | }) 273 | }; 274 | 275 | 276 | /** 277 | request的ajax获取方法,某些网站反爬,可以自定义头部 278 | var options = { 279 | url: url, 280 | json:true, 281 | method : 'GET', 282 | headers: { 283 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 284 | (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36', 285 | 'X-Requested-With':'XMLHttpRequest' 286 | } 287 | }; 288 | */ 289 | Ut.request_json = function (options, callback) { 290 | request(options, function (error, response, body) { 291 | if (error) return callback(error, null); 292 | if (response.statusCode != 200) return callback("statusCode" + response.statusCode, null); 293 | callback(null, body); 294 | }); 295 | }; 296 | 297 | /** 298 | 格式化时间 299 | */ 300 | Ut.fmtDate = function (date) { 301 | // 将数字格式化为两位长度的字符串 302 | var fmtTwo = function (number) { 303 | return (number < 10 ? '0' : '') + number; 304 | }; 305 | var yyyy = date.getFullYear(); 306 | var MM = fmtTwo(date.getMonth() + 1); 307 | var dd = fmtTwo(date.getDate()); 308 | var HH = fmtTwo(date.getHours()); 309 | var mm = fmtTwo(date.getMinutes()); 310 | var ss = fmtTwo(date.getSeconds()); 311 | return '' + yyyy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss; 312 | } 313 | 314 | module.exports = Ut; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat_crawler", 3 | "version": "1.0.0", 4 | "description": "通过抓取搜狗微信网站获取微信文章信息!", 5 | "main": "app.js", 6 | "private": true, 7 | "author": "452076103@qq.com", 8 | "keywords": [ 9 | "async", 10 | "cheerio", 11 | "request", 12 | "nodejs爬虫", 13 | "微信文章", 14 | "搜狗微信" 15 | ], 16 | "dependencies": { 17 | "async": "^2.1.4", 18 | "cheerio": "^0.22.0", 19 | "request": "^2.79.0" 20 | }, 21 | "scripts": { 22 | "start": "node app" 23 | } 24 | } --------------------------------------------------------------------------------