醋醋百科网

Good Luck To You!

引爆朋友圈!我用npx一行命令“偷”光了微信热门文章

嘿,各位朋友们,大家好!怎么优雅地获取微信公众号的热门文章?

你可能会说,这不就是爬虫吗?用 Python requests + BeautifulSoup,或者 Node.js axios + cheerio 不就分分钟搞定了?没错,但这样是不是有点太“普通”了?每次都得 git clone、npm install,或者配个 Python 虚拟环境,不觉得麻烦吗?

今天,我要介绍一个更酷的玩法——MCP(Module-driven Command Pipeline,模块驱动的命令管道),用 npx 这个神器,实现一行命令,就能把最新的微信热门文章给你安排得明明白白。

什么是 MCP?听起来这么玄乎?

MCP,全称 Module-driven Command Pipeline,中文叫“模块驱动的命令管道”。别被这个名字吓到,说白了,它就是一种软件设计思想,一种架构模式。

它的核心理念是:将一个复杂的任务,拆解成一连串独立的、可复用的“模块(Module)”,然后像搭积木一样,用一个“管道(Pipeline)”把这些模块串起来,让数据在管道里依次流过每个模块,最终完成整个任务。

这就像一个工厂的流水线:

  • 原料:就是你的初始输入(比如,一个指定的目标网址)。
  • 工人:就是我们一个个独立的“模块”,每个工人只干一件事(比如 A 工人负责抓取网页,B 工人负责解析 HTML,C 工人负责筛选数据,D 工人负责格式化输出)。
  • 流水线:就是“管道”,它规定了原料要经过哪些工人,以及先后顺序。
  • 成品:就是你最终想要的结果(比如,一个包含热门文章标题和链接的 JSON 文件)。

MCP 的核心优势

  • 高内聚,低耦合:每个模块只关心自己的任务,模块之间互不干扰,通过管道传递数据。修改一个模块,不会影响其他模块。
  • 超强复用性:写好的模块(比如“下载网页模块”)可以在任何其他 MCP 项目里直接使用。
  • 灵活可扩展:想增加新功能?简单,写个新模块,插到管道里就行了。想改变处理顺序?调整一下管道里的模块顺序即可。
  • 清晰易维护:整个工作流程一目了然,出了问题,可以快速定位是哪个模块的锅。

MCP 架构图解




从图可以看到,数据(Context)在一个上下文中流动,每个模块都可以读取和修改这个上下文,然后将其传递给下一个模块。

实战:三步打造微信热门文章获取器


  1. init 模块:初始化配置,比如要爬取的公众号名称。
  2. fetch_search_page 模块:根据公众号名称,请求搜狗微信搜索,获取搜索结果页的 HTML。
  3. parse_article_list 模块:解析 HTML,提取出文章的标题、链接、摘要和时间。
  4. format_output 模块:将提取到的数据格式化成美观的控制台输出。

架构图解


步骤一:创建项目并安装依赖

咱们用 Node.js 来实现。首先,你需要一个 package.json 文件来让你的脚本能被 npx 识别。

mkdir wechat-hot-articles
cd wechat-hot-articles
npm init -y

接下来,我们需要两个核心依赖:

  • axios: 用于发送 HTTP 请求,获取网页内容。
  • cheerio: 服务端版的 jQuery,用于解析 HTML,提取数据。
npm install axios cheerio

步骤二:编写 MCP 核心代码

现在,激动人心的编码时刻到了!我们将创建一个 index.js 文件,把所有的逻辑都放在里面。

#!/usr/bin/env node

// ↑↑↑ 这行叫做 "Shebang",是告诉系统这个文件要用 node 来执行,是 npx 的关键!

const axios = require('axios');
const cheerio = require('cheerio');

/**
 * 管道运行器
 * @param {Array<Function>} modules - 按顺序执行的模块数组
 * @param {object} initialContext - 初始上下文
 */
async function runPipeline(modules, initialContext = {}) {
    let context = { ...initialContext };
    for (const module of modules) {
        try {
            // 每个模块都接收上下文,并可以修改它
            // 使用 await 确保异步模块正确执行
            context = await module(context);
        } catch (error) {
            console.error(`\n Error in module: ${module.name}`);
            console.error(error.message);
            // 管道中任何一个模块出错,就中断执行
            return;
        }
    }
    return context;
}

// ------------------- MCP 模块定义 -------------------

/**
 * 模块1: 初始化
 * 从命令行参数获取要搜索的公众号名称
 */
async function init(context) {
    console.log(' [Module: init] - Initializing...');
    const query = process.argv[2]; // process.argv[2] 是 npx 命令后的第一个参数
    if (!query) {
        // 如果用户没提供公众号,就抛出错误
        throw new Error('Please provide a WeChat Official Account name. Usage: npx wechat-hot-articles "公众号名称"');
    }
    console.log(` Target Account: ${query}`);
    // 将查询参数添加到上下文中
    return { ...context, query };
}

/**
 * 模块2: 获取搜狗搜索结果页面
 * 使用 axios 请求搜狗微信搜索
 */
async function fetchSearchPage(context) {
    console.log(' [Module: fetchSearchPage] - Fetching search results...');
    const { query } = context;
    // 搜狗微信搜索的 URL
    const url = `https://weixin.sogou.com/weixin?type=1&query=${encodeURIComponent(query)}`;
    
    const response = await axios.get(url, {
        // 必须设置 User-Agent,否则可能被识别为爬虫
        headers: {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
        },
    });

    console.log(' Page fetched successfully.');
    // 将获取到的 HTML 文本添加到上下文中
    return { ...context, html: response.data };
}

/**
 * 模块3: 解析文章列表
 * 使用 cheerio 解析 HTML,提取文章信息
 */
async function parseArticleList(context) {
    console.log(' [Module: parseArticleList] - Parsing article list...');
    const { html } = context;
    const $ = cheerio.load(html);
    const articles = [];

    // 搜狗微信的文章列表在一个叫 'news-list' 的 ul 中
    $('.news-list li').each((index, element) => {
        const titleElement = $(element).find('.txt-box h3 a');
        const title = titleElement.text().trim();
        // 搜狗的链接是临时的,需要处理一下
        const url = 'https://weixin.sogou.com' + titleElement.attr('href');
        const summary = $(element).find('.txt-box .txt-info').text().trim();
        const time = $(element).find('.txt-box .s-p').attr('t');
        
        // 将时间戳转换为可读日期
        const date = new Date(parseInt(time) * 1000).toLocaleString();

        if (title && url) {
            articles.push({ title, url, summary, date });
        }
    });

    if (articles.length === 0) {
        throw new Error('No articles found. The account may not exist or has no recent posts.');
    }
    
    console.log(` Found ${articles.length} articles.`);
    // 将解析出的文章数组添加到上下文中
    return { ...context, articles };
}

/**
 * 模块4: 格式化并输出结果
 * 在控制台打印出漂亮的文章列表
 */
async function formatOutput(context) {
    console.log('\n [Module: formatOutput] - Formatting results...\n');
    const { articles, query } = context;
    
    console.log('=================================================');
    console.log(` Latest Articles for "${query}" `);
    console.log('=================================================\n');

    articles.forEach((article, index) => {
        console.log(`[${index + 1}] ${article.title}`);
        console.log(`     Date: ${article.date}`);
        console.log(`     Summary: ${article.summary}`);
        console.log(`     Link: ${article.url}\n`);
    });

    console.log('=================================================');
    console.log(' All tasks completed!');
    
    // 这个模块是管道的终点,可以不返回 context
    return context;
}


// ------------------- 主函数:启动管道 -------------------

function main() {
    // 定义我们的管道,将所有模块按顺序放入数组
    const pipeline = [
        init,
        fetchSearchPage,
        parseArticleList,
        formatOutput,
    ];

    // 运行管道!
    runPipeline(pipeline);
}

// 执行主函数
main();

步骤三:配置 package.json 并发布

为了让 npx 能找到并执行我们的 index.js,我们需要在 package.json 中添加一个 bin 字段。

打开 package.json,修改成类似下面这样:

JSON

{
  "name": "wechat-hot-articles-mcp-demo", // 包名,需要是唯一的
  "version": "1.0.0",
  "description": "A MCP demo to fetch latest articles from a WeChat Official Account via npx.",
  "main": "index.js",
  "bin": {
    "wechat-hot-articles": "./index.js" // 关键!定义命令名和对应的执行文件
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["wechat", "scraper", "mcp", "npx"],
  "author": "Your Name",
  "license": "MIT",
  "dependencies": {
    "axios": "^0.21.1", // 版本号可能不同
    "cheerio": "^1.0.0-rc.10"
  }
}

最重要的就是 bin 字段。它告诉 npm,当用户安装这个包时,要创建一个名为 wechat-hot-articles 的命令行工具,它实际执行的是 ./index.js 文件。

本地测试

在发布到 npm 之前,我们可以在本地模拟 npx 的行为。在项目根目录下运行:

Bash

npm link

这个命令会在你的全局 node_modules 中创建一个指向你当前项目的符号链接。然后你就可以在任何地方使用你定义的命令了:

Bash

wechat-hot-articles "前端大全"

如果一切顺利,你将看到控制台输出“前端大全”公众号的最新文章列表!

发布到 NPM

测试无误后,就可以发布到 npm 让全世界的开发者使用了!

  1. 去 npmjs.com 注册一个账号。
  2. 在你的终端登录 npm:npm login。
  3. 发布!npm publish。(注意:package.json 里的 name 必须是 npm 上没有被占用的)

发布成功后,任何人(包括你自己)就可以在任何有 Node.js 环境的电脑上,通过 npx 一行命令运行你的杰作了:

Bash

npx wechat-hot-articles-mcp-demo "前端大全"

(这里的包名
wechat-hot-articles-mcp-demo 换成你自己的)

总结

看,我们通过 MCP 架构,非常优雅地实现了一个功能强大且易于维护的命令行工具。回顾一下:

  • MCP 思想:把复杂任务拆解为 模块(Module),用 管道(Pipeline) 串联,实现了高内聚、低耦合和高复用。
  • 代码实现:我们用 JavaScript 写了四个独立的异步模块,和一个管道运行器 runPipeline 来驱动它们。
  • npx 魔法:通过配置 package.json 的 bin 字段和发布到 npm,我们创造了一个可以随处运行的、免安装的命令行工具。

这种模式不仅可以用来做爬虫,任何有固定流程的命令行任务,比如代码生成器、自动化部署脚本、文件处理器等等,都可以用 MCP 的思想来构建。它能让你的代码结构更清晰,逻辑更优雅。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言