嘿,各位朋友们,大家好!怎么优雅地获取微信公众号的热门文章?
你可能会说,这不就是爬虫吗?用 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)在一个上下文中流动,每个模块都可以读取和修改这个上下文,然后将其传递给下一个模块。
实战:三步打造微信热门文章获取器
- init 模块:初始化配置,比如要爬取的公众号名称。
- fetch_search_page 模块:根据公众号名称,请求搜狗微信搜索,获取搜索结果页的 HTML。
- parse_article_list 模块:解析 HTML,提取出文章的标题、链接、摘要和时间。
- 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 让全世界的开发者使用了!
- 去 npmjs.com 注册一个账号。
- 在你的终端登录 npm:npm login。
- 发布!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 的思想来构建。它能让你的代码结构更清晰,逻辑更优雅。