抓取微信公众号全部文章,采用AnyProxy+Javascript+Java实现

git 

https://gitee.com/saly/WechatSpider/blob/master/src/main/resources/spider.js#

/**
 * 微信公众号爬虫, 抓取流程参考`README.MD`文档
 *
 * @author 最爱吃小鱼
 */

// 规则配置
var config = {
    host: 'http://127.0.0.1:8080', // 服务器地址配置
    crawlHistory: true, // 是否采集列表历史数据
    crawlArticle: true, // 是否采集文章数据
    crawlComment: true, // 是否采集评论数据
    crawlLikeReadNum: true, // 是否采集文章的阅读量及点赞量
    autoNextScroll: true, // 是否自动下拉采取数据
    autoNextPage: true, // 是否自动文章翻页
    autoPostData: true, // 是否提交数据到服务器
    m: 3000, // 自动下拉的时间间隔 m ~ n 秒之间
    n: 5000,
    jumpInterval: 10, // 文章页跳转的时间间隔
    saveContentType: 'html',// 微信文章保存内容的形式: html/text
    localImg: true // 公众号的图片返回本地图片
}

var url = require('url');
var http = require('http');
var querystring = require('querystring');
var cheerio = require('cheerio');
var rp = require('request-promise');
var fs  = require("fs");
var img = fs.readFileSync(__dirname + "/black.png")
module.exports = {
    // 模块介绍
    summary: '微信公众号爬虫',
    // 发送请求拦截
    *beforeSendRequest(requestDetail) {
        if (!config.localImg) return null;
        // 将请求图片变成本地图片,加快文章显示
        if(/mmbiz\.qpic\.cn/i.test(requestDetail.url)){
            const localResponse = {
                statusCode: 200,
                header: { 'Content-Type': 'image/png' },
                body: img
            };
            return {
                response: localResponse
            };
        }
    },
    // 发送响应前处理
    *beforeSendResponse(requestDetail, responseDetail) {
        try {
            // 解析连接中的参数信息
            var link = requestDetail.url;

            // 历史页面第一页数据
            if(/mp\/profile_ext\?action=home/i.test(link)){
                if (!config.crawlHistory) {
                    return null;
                }
                // 取得响应内容
                var serverResData = responseDetail.response.body.toString();
                // 取得公众号唯一标识biz
                var biz = getBizByLink(link);

                // 取得微信公众号历史数据的第一页数据,包含公众号详情及最新的文章信息
                var account = getAccount(biz, serverResData);
                // 数据上传到服务器
                serverPost(biz, account, '/spider/firstpage')

                // 判断是否自动下拉请求数据
                if (!config.autoNextScroll) {
                    return null;
                }
                // 根据返回的数据状态组装相应的自动滚动加载JS
                var autoNextScrollJS = getAutoNextScrollJS();

                // 修改返回的body内容,插入JS
                var newResponse = Object.assign({}, responseDetail.response);
                newResponse.body += autoNextScrollJS;
                return {
                    response: newResponse
                };
            }

            // 向下翻页的数据的AJAX请求处理
            if(/mp\/profile_ext\?action=getmsg/i.test(link)){
                if (!config.crawlHistory) {
                    return null;
                }
                var biz = getBizByLink(link);
                var content = JSON.parse(responseDetail.response.body.toString());
                content = JSON.parse(content.general_msg_list);

                var articles = getArticles(biz, content.list);
                serverPost(biz, articles, '/spider/nextpage');
                return null;
            }

            // 文章页跳转
            if (/\/s\?__biz/.test(link) || /mp\/appmsg\/show/.test(link)) {
                if (!config.crawlArticle) {
                    return null;
                }
                var content = responseDetail.response.body.toString();
                var article = getArticle(link, content);
                let bb = async function() {
                    return await serverPost(article.biz, article, "/spider/updateArticleContent").then((nextLink) => {
                        if (nextLink == undefined || nextLink == null || nextLink == '') {
                            return null;
                        }
                        var autoNextPageMeta = getAutoNextPageMeta(nextLink);
                        console.log(nextLink);

                        // 修改返回的body内容,插入meta
                        var newResponse = Object.assign({}, responseDetail.response);
                        newResponse.body = content.replace('</title>', '</title>' + autoNextPageMeta);
                        return {
                            response: newResponse
                        };
                    });
                }
                return bb();
            }

            // 微信公众号的跟贴评论
            if (/\/mp\/appmsg_comment/.test(link)){
                if (!config.crawlComment) {
                    return null;
                }
                var comments = getComments(link, responseDetail);
                if (comments.length > 0) {
                    serverPost(comments[0].biz, comments, '/spider/comment');
                }
                return null;
            }

            // 获取点赞量和阅读量
            if (link.indexOf('getappmsgext') > -1) {
                if (!config.crawlLikeReadNum) {
                    return null;
                }
                var article = getArticleOfReadNumAndLikeNum(link, responseDetail);
                serverPost(article.biz, article, '/spider/updateArticleNum');
                return null;
            }

            return null;
        } catch (e) {
            console.log("程序运行异常");
            console.log(e);
            throw e;
        }

    }
};

// 转义符换成普通字符
function escape2Html(str){
    const arrEntities={'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'};
    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){return arrEntities[t];});
}

/**
 * 取得文章的详细信息, 通过biz,mid聚合主键找些此文章,更新文章内容
 *
 * @param link
 * @param responseDetail
 * @returns {{biz: *, mid: *, idx: *, content: (*|jQuery|string)}}
 */
function getArticle(link, content) {
    var $ = cheerio.load(content, { decodeEntities: false });
    var identifier = querystring.parse(url.parse(link).query);
    var articleContent = config.saveContentType=='html' ? $('#js_content').html() : $('#js_content').text();
    if (articleContent == '') {
        return null;
    }
    return {
        biz: identifier.__biz,
        mid: identifier.mid,
        idx: identifier.idx,
        content: articleContent
    }
}

/**
 * 取得文章的阅读量、点赞量等相关文章信息
 *
 * @param link
 * @param responseDetail
 * @returns {{biz: *, mid: *, idx: *, readNum: *, likeNum: *}}
 */
function getArticleOfReadNumAndLikeNum(link, responseDetail) {
    var identifier = querystring.parse(url.parse(link).query);
    var content = JSON.parse(responseDetail.response.body.toString());
    return {
        biz: identifier.__biz,
        mid: identifier.mid,
        idx: identifier.idx,
        readNum: content.appmsgstat.read_num,
        likeNum: content.appmsgstat.like_num
    }
}

/**
 * 从URL中解析出biz
 * @param link
 * @returns {biz}
 */
function getBizByLink(link) {
    var identifier = querystring.parse(url.parse(link).query);
    return identifier.__biz;
}

/**
 * 取得微信公众号及最新的文章信息
 * @param biz
 * @param serverResData
 * @returns {{}}
 */
function getAccount(biz, serverResData) {
    var account = {};
    // 解析公众号的数据
    account.nickname = /var nickname = "(.+?)"/.exec(serverResData)[1];
    account.headimg = /var headimg = "(.+?)"/.exec(serverResData)[1];
    account.biz = biz;
    account.crawlTime = new Date().getTime();

    // 解析文章列表数据
    var msgList = /var msgList = '(.+)';\n/.exec(serverResData)[1];
    msgList = JSON.parse(escape2Html(msgList).replace(/\\\//g,'/'));
    msgList = msgList.list;
    account.articles = getArticles(biz, msgList);
    return account;
}

/**
 * 解析封装取得自己想要文章信息
 * @param biz
 * @param content
 * @returns {Array}
 */
function getArticles(biz, content) {
    var articles = [];
    for (var i=0, len=content.length ; i < len ; i++) {
        var post = content[i];
        var cmi = post.comm_msg_info;
        // 只采取图文消息的数据,目前所知type=3就只有一张图片,其它类型未知
        if (cmi.type != 49) continue;
        var amei = post.app_msg_ext_info;
        var obj = getMidAndIdx(amei.content_url);

        articles.push({
            biz: biz,
            mid: obj.mid,
            title: amei.title,
            digest: amei.digest,
            contentUrl: amei.content_url,
            sourceUrl: amei.source_url,
            author: amei.author,
            cover: amei.cover,
            copyrightStat: amei.copyright_stat,
            datetime: cmi.datetime,
            idx: obj.idx
        });
    }
    return articles;
}

/**
 * 解析封装取得文章的评论信息
 * @param link
 * @param responseDetail
 * @returns {Array}
 */
function getComments(link, responseDetail){
    var identifier = querystring.parse(url.parse(link).query);
    var comments = [];
    var content = JSON.parse(responseDetail.response.body.toString());
    var electedComment = content.elected_comment;
    if (electedComment && electedComment.length) {
        for (var i=0, len=electedComment.length ; i < len ; i++) {
            comments.push({
                biz: identifier.__biz,
                mid: identifier.appmsgid,
                contentId: electedComment[i].content_id,
                nickName: electedComment[i].nick_name,
                logoUrl: electedComment[i].logo_url,
                content: electedComment[i].content,
                createTime: electedComment[i].create_time,
                likeNum: electedComment[i].like_num
            });
        }
    }
    return comments;
}

/**
 * 从连接取得mid及idx
 * @param link
 * @returns {{mid: *, idx: *}}
 */
function getMidAndIdx(link) {
    var identifier = querystring.parse(url.parse(link.replace(/amp;/g, '')).query);
    return {
        mid: identifier.mid,
        idx: identifier.idx
    }
}

/**
 * 向服务上传抓取到的数据
 * @param data 数据
 * @param path 请求路径
 */
function serverPost(biz, data, path) {
    if (!config.autoPostData) {
        return Promise.resolve().then(function(){
            return null;
        });
    }
    var options = {
        method: 'POST',
        uri: config.host + path,
        form: {
            biz: biz,
            content: JSON.stringify(data)
        },
        json: true
    };
    return rp(options).then(function (parsedBody) {
        if (parsedBody.code == 1) {
            console.log('--' + path + '------------------------');
            //console.log(data);
            return parsedBody.data;
        } else {
            console.log('请求失败, 失败信息=' + parsedBody.message);
            return null;
        }
    });
}

/**
 * 组装自动向下滚动翻页的JS
 *
 * @returns {string}
 */
function getAutoNextScrollJS() {
    var nextJS = '';
    nextJS += '<script type="text/javascript">';
    nextJS += '    var end = document.createElement("p");';
    nextJS += '    document.body.appendChild(end);';
    nextJS += '    (function scrollDown(){';
    nextJS += '        end.scrollIntoView();';
    nextJS += '        var loadMore = document.getElementsByClassName("loadmore with_line")[0];';
    nextJS += '        if (loadMore.style.display) {';
    nextJS += '            setTimeout(scrollDown,Math.floor(Math.random()*('+config.n+'-'+config.m+')+'+config.m+'));';
    nextJS += '        } ';
    nextJS += '    })();';
    nextJS += '<\/script>';
    return nextJS;
}

/**
 * 自动跳转到下一文章页的meta
 *
 * @param nextLink
 * @returns {string}
 */
function getAutoNextPageMeta(nextLink) {
    return '<meta http-equiv="refresh" content="' + config.jumpInterval + ';url=' + nextLink + '" />';
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
好的,以下是一个简单的用php实现抓取微信公众号文字和图片的代码示例: ```php <?php // 设置请求头 $headers = [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Referer: https://weixin.sogou.com/', ]; // 获取文章链接 $url = 'https://mp.weixin.qq.com/s/xxxxxxxxxxxxx'; // 发送请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $content = curl_exec($ch); curl_close($ch); // 解析HTML内容 $doc = new DOMDocument(); @$doc->loadHTML($content); // 获取文章标题 $title = $doc->getElementsByTagName('title')->item(0)->nodeValue; // 获取文章内容 $elements = $doc->getElementsByTagName('div'); $content = ''; for ($i = 0; $i < $elements->length; $i++) { $node = $elements->item($i); if ($node->getAttribute('class') == 'rich_media_content') { // 获取文字内容 $ps = $node->getElementsByTagName('p'); foreach ($ps as $p) { $content .= $p->nodeValue . "\n"; } // 获取图片链接 $imgs = $node->getElementsByTagName('img'); foreach ($imgs as $img) { $content .= $img->getAttribute('data-src') . "\n"; } break; } } // 输出结果 echo "文章标题: " . $title . "\n"; echo "文章内容: " . $content . "\n"; ?> ``` 需要注意的是,该代码仅供参考,具体实现还需要根据自己的需求进行调整。同时,需要遵循相关法律法规和微信公众平台的规定,避免违反用户隐私和侵犯知识产权等问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值