├── .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 | }
--------------------------------------------------------------------------------