
1. 项目概述当OpenClaw遇上微信公众号如果你正在寻找一种更优雅、更稳定、更“像人”的方式来批量获取微信公众号文章内容那么将OpenClaw与Playwright结合构建一个结构化的内容抓取技能绝对值得你投入时间研究。这不仅仅是写一个简单的爬虫脚本而是构建一个能够融入自动化工作流、具备强大容错能力和数据治理能力的智能采集节点。OpenClaw作为一个新兴的、面向AI智能体和工作流自动化的开源框架其核心魅力在于“技能”Skill的模块化封装。它允许你将复杂的操作比如登录、点击、数据提取封装成一个个可复用的、可编排的原子能力。而Playwright作为微软出品的现代浏览器自动化库以其对动态网页尤其是大量使用JavaScript渲染的现代Web应用近乎完美的支持而闻名。微信公众号的后台文章列表和文章详情页恰恰是这类动态渲染页面的典型代表传统的requestsBeautifulSoup组合在这里常常力不从心不是遇到登录态问题就是无法获取JavaScript执行后的最终DOM。这个技能要解决的痛点非常明确自动化、结构化地获取指定公众号的历史文章或单篇文章的完整内容包括标题、作者、发布时间、正文含图文、阅读数、点赞数等并将这些数据转换为干净的JSON或存入数据库为后续的内容分析、舆情监控、知识库构建或RSS生成提供原料。整个过程需要模拟真实用户行为绕过前端反爬机制并处理网络延迟、元素加载等不确定性因素。2. 核心思路与技术选型解析为什么是OpenClaw Playwright而不是其他更常见的组合这背后是一套针对微信公众号场景特性的深度考量。2.1 为何选择Playwright作为浏览器自动化核心首先我们必须正视微信公众号的页面特性。无论是公众号主页还是文章页其内容都严重依赖JavaScript动态加载和渲染。直接发送HTTP请求获取到的HTML源码往往只是一个包含JavaScript框架的“空壳”真正的文章内容需要通过执行JS才能生成。这就排除了单纯使用requests库的可能性。Selenium是浏览器自动化的老牌选手但Playwright在几个关键点上更具优势自动等待机制Playwright内置了智能等待可以等待元素可见、可点击、网络请求完成等大大减少了编写显式等待time.sleep或WebDriverWait的代码量让脚本更健壮。多浏览器支持与一致性它同时支持Chromium、Firefox和WebKitSafari引擎并且API高度统一。我们通常选择Chromium因为它性能好、兼容性强且Playwright能自动下载和管理浏览器二进制文件环境配置极其简单。强大的选择器和录制工具Playwright提供了非常丰富的选择器引擎CSS、Text、XPath等其codegen录制工具能快速生成操作脚本的骨架对于探索微信公众号页面结构帮助巨大。网络拦截与模拟可以轻松监听和修改网络请求这对于分析数据接口、模拟移动设备User-Agent微信公众号页面在PC和移动端展示不同至关重要。注意虽然微信公众号有提供官方API但API权限申请门槛高需认证服务号且接口有严格的频率限制主要用于管理后台不适合大规模的内容采集需求。因此基于浏览器模拟的“非官方”途径在特定合规前提下成为了许多个人开发者和研究者的技术选择。2.2 OpenClaw框架的定位与价值OpenClaw在这里扮演的是“调度中心”和“技能容器”的角色。你可以把它想象成一个机器人的大脑而Playwright抓取技能是它的一只手。OpenClaw的价值在于技能标准化将复杂的Playwright脚本封装成一个标准的OpenClaw Skill。这个技能有明确的输入如公众号ID、文章URL、采集页数和输出结构化的文章数据可以被其他技能或工作流如n8n、Airflow轻松调用。状态与上下文管理OpenClaw可以帮助管理登录状态Cookie、浏览器实例的生命周期。例如可以设计一个“微信登录”技能登录后返回浏览器上下文Context供后续的“内容抓取”技能复用避免每次抓取都重新登录。错误处理与重试逻辑框架层面可以提供统一的错误捕获、日志记录和重试机制让技能更加健壮。易于集成封装成Skill后可以通过OpenClaw的CLI、API或与其他AI智能体如基于Claude Code交互来触发任务极大地扩展了应用场景。技术栈全景图因此我们的核心架构是Python作为编程语言Playwright作为浏览器自动化驱动OpenClaw作为技能编排框架数据解析可能用到BeautifulSoup4或parsel最终数据存储到JSON文件、SQLite/MySQL数据库或发送到Webhook。3. 环境准备与核心依赖安装工欲善其事必先利其器。搭建一个稳定可复现的环境是第一步。3.1 Python环境与Playwright初始化建议使用Python 3.8及以上版本。使用虚拟环境venv或conda是最佳实践可以隔离项目依赖。# 1. 创建项目目录并进入 mkdir wechat-crawler-skill cd wechat-crawler-skill # 2. 创建虚拟环境以venv为例 python -m venv venv # 3. 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 4. 安装Playwright pip install playwright # 5. 安装Playwright所需的浏览器Chromium playwright install chromium这一步playwright install chromium会下载Chromium浏览器因为微信公众号页面在Chromium内核下兼容性最好。有时网络原因下载较慢可以尝试设置环境变量PLAYWRIGHT_DOWNLOAD_HOST为国内镜像。3.2 OpenClaw框架安装与技能项目创建OpenClaw的安装方式通常通过pip进行。由于它处于活跃开发中建议关注其官方GitHub仓库获取最新安装方式。# 安装OpenClaw核心库 pip install openclaw # 安装OpenClaw CLI工具如果提供 # pip install openclaw-cli安装完成后我们需要创建一个OpenClaw技能项目。OpenClaw通常有特定的项目结构。虽然其具体规范可能演变但一个典型的技能目录结构可能如下所示wechat_article_skill/ ├── skill.yaml # 技能元数据名称、描述、输入输出参数 ├── __init__.py ├── main.py # 技能主逻辑包含Playwright抓取代码 ├── requirements.txt # 技能专属依赖 └── README.md你可以使用OpenClaw CLI如果可用来脚手架生成这个结构或者手动创建。skill.yaml是这个技能的灵魂它定义了技能如何被调用。# skill.yaml 示例 name: wechat_article_crawler description: 使用Playwright自动化抓取微信公众号文章内容并结构化提取。 version: 1.0.0 author: Your Name inputs: - name: target type: string description: 公众号主页URL或文章具体URL required: true - name: max_posts type: integer description: 最大抓取文章数当target为主页时 required: false default: 10 - name: headless type: boolean description: 是否以无头模式运行浏览器 required: false default: true outputs: - name: articles type: array description: 提取到的文章结构化数据列表3.3 辅助工具库安装除了核心框架我们还需要一些辅助库来让开发更顺畅。pip install beautifulsoup4 # 备用HTML解析虽然Playwright自带解析能力很强 pip install pydantic # 用于定义和验证输出的结构化数据模型非常推荐 pip install python-dotenv # 管理环境变量如存储登录凭证使用pydantic来定义数据模型能让你的输出非常规范和自描述是构建高质量技能的关键。# models.py from pydantic import BaseModel, HttpUrl from datetime import datetime from typing import Optional, List class WechatArticle(BaseModel): title: str author: str publish_time: Optional[datetime] content_html: str # 原始HTML内容 content_text: str # 纯文本内容可后续清洗 source_url: HttpUrl read_count: Optional[int] None like_count: Optional[int] None cover_image_url: Optional[HttpUrl] None # 可以添加更多字段如摘要、音频链接等4. 技能核心实现Playwright自动化抓取这是整个技能最核心的部分我们将拆解为几个关键步骤。4.1 启动浏览器与页面导航首先在技能的主文件如main.py中我们需要编写Playwright的控制逻辑。import asyncio from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeoutError # 注意Playwright推荐使用异步API以获得最佳性能 async def crawl_wechat_article(target_url: str, headless: bool True) - list: 核心抓取函数 articles_data [] async with async_playwright() as p: # 启动浏览器使用Chromium可配置视口大小模拟手机 browser await p.chromium.launch(headlessheadless, args[--disable-blink-featuresAutomationControlled]) # 禁用自动化控制特征 context await browser.new_context( viewport{width: 375, height: 667}, # 模拟手机端iPhone 6/7/8尺寸 user_agentMozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1 ) page await context.new_page() try: # 导航到目标页面 await page.goto(target_url, wait_untilnetworkidle) # 等待网络空闲 await page.wait_for_load_state(domcontentloaded) # 这里需要判断target_url是公众号主页还是单篇文章页 # 通过页面URL或特定元素进行判断 if mp.weixin.qq.com/s? in target_url: # 单篇文章处理逻辑 article await _parse_single_article(page) if article: articles_data.append(article) else: # 公众号主页需要先获取文章列表 article_links await _fetch_article_list(page) # 然后遍历链接进入详情页抓取 for link in article_links[:max_posts]: # 控制数量 article await _crawl_single_from_link(context, link) if article: articles_data.append(article) await asyncio.sleep(1) # 礼貌性延迟避免请求过快 except PlaywrightTimeoutError as e: print(f页面加载超时: {e}) except Exception as e: print(f抓取过程发生错误: {e}) finally: await browser.close() return articles_data关键点解析args[--disable-blink-featuresAutomationControlled]这个启动参数非常重要它禁用了Chrome/Chromium中一些暴露自动化脚本的特征能在一定程度上避免被简单的反爬检测。viewport和user_agent模拟移动端访问。微信公众号页面在移动端和PC端的DOM结构可能不同且移动端通常反爬策略更宽松。模拟手机是提高成功率的常见技巧。wait_untilnetworkidle等待页面网络活动基本停止这对于动态加载内容的页面至关重要确保我们看到的是完整的页面。4.2 公众号主页文章列表抓取从公众号主页如https://mp.weixin.qq.com/mp/profile_ext?actionhome__biz...抓取文章列表是批量采集的关键。这个页面通常是一个无限滚动的列表。async def _fetch_article_list(page): 从公众号主页抓取文章链接列表 article_links [] print(正在获取文章列表...) # 首先可能需要处理潜在的“点击展开”或登录提示如果未登录 # 这里是一个示例实际选择器需要你通过浏览器开发者工具分析 # try: # load_more_btn page.locator(text加载更多) # if await load_more_btn.is_visible(): # await load_more_btn.click() # await page.wait_for_timeout(2000) # except: # pass # 滚动页面以加载更多内容模拟无限滚动 scroll_pause_time 2 last_height await page.evaluate(document.body.scrollHeight) max_scroll 5 # 控制最大滚动次数防止无限循环 for i in range(max_scroll): # 滚动到底部 await page.evaluate(window.scrollTo(0, document.body.scrollHeight)) await page.wait_for_timeout(scroll_pause_time * 1000) # 等待新内容加载 new_height await page.evaluate(document.body.scrollHeight) if new_height last_height: print(已滚动到底部或内容不再加载。) break last_height new_height print(f第{i1}次滚动页面高度: {new_height}) # 提取所有文章卡片中的链接 # 使用Playwright强大的选择器这里的选择器需要根据实际页面结构调整 # 通常链接在带有特定class的a标签内或者data-link属性里 link_elements await page.query_selector_all(div.weui-media-box__hd a) # 示例选择器 for elem in link_elements: href await elem.get_attribute(href) if href and mp.weixin.qq.com/s? in href: full_url href if href.startswith(http) else fhttps://mp.weixin.qq.com{href} article_links.append(full_url) print(f共获取到{len(article_links)}篇文章链接。) return list(set(article_links)) # 去重实操心得获取公众号主页列表链接是最大的难点之一因为微信前端结构会变且未登录时可能看不到历史文章。选择器的编写需要你亲自打开目标公众号主页使用浏览器的开发者工具F12仔细分析DOM结构。Playwright的codegen命令playwright codegen 目标URL是一个神器它可以录制你的操作并生成选择器代码是快速入门的捷径。4.3 单篇文章内容结构化提取进入单篇文章页面后我们需要精确地定位并提取各个字段。async def _parse_single_article(page): 解析单篇文章页面提取结构化信息 try: # 等待文章主体内容加载出来通常有一个特定的ID或Class # 例如文章正文可能在一个id为js_content的div里 await page.wait_for_selector(#js_content, stateattached, timeout10000) # 使用Page的evaluate方法在浏览器环境中执行JavaScript来提取数据 article_info await page.evaluate(() { const extract {}; // 1. 标题 const titleElem document.querySelector(#activity-name); extract.title titleElem ? titleElem.innerText.trim() : ; // 2. 作者/公众号名称 const authorElem document.querySelector(#js_name); extract.author authorElem ? authorElem.innerText.trim() : ; // 3. 发布时间 const timeElem document.querySelector(#publish_time); extract.publish_time timeElem ? timeElem.innerText.trim() : ; // 4. 阅读数和点赞数这些数据可能是动态加载的选择器可能不固定 const readElem document.querySelector(span.read_num); extract.read_count readElem ? parseInt(readElem.innerText) || 0 : 0; const likeElem document.querySelector(span.like_num); extract.like_count likeElem ? parseInt(likeElem.innerText) || 0 : 0; // 5. 封面图 const coverElem document.querySelector(img#js_cover); extract.cover_image_url coverElem ? coverElem.src : ; // 6. 正文HTML (保留格式但可以清理不需要的标签) const contentElem document.getElementById(js_content); if (contentElem) { // 深拷贝一份避免修改原DOM const contentClone contentElem.cloneNode(true); // 可选移除一些干扰元素如广告、引导关注二维码等 const elementsToRemove contentClone.querySelectorAll(.qr_code_pc, .code-snippet, .tips); elementsToRemove.forEach(el el.remove()); extract.content_html contentClone.innerHTML; extract.content_text contentClone.innerText; } else { extract.content_html ; extract.content_text ; } // 7. 原文链接 extract.source_url window.location.href; return extract; }) # 将提取的原始数据转换为我们的Pydantic模型 # 注意需要处理时间字符串的解析 from datetime import datetime import re pub_time_str article_info.get(publish_time, ) pub_time None if pub_time_str: # 尝试解析常见格式如“2023-10-27”或“昨天 20:00” try: # 这里需要根据实际显示的时间格式编写解析逻辑可能比较复杂 # 简化示例假设是“YYYY-MM-DD”格式 match re.search(r(\d{4}-\d{1,2}-\d{1,2}), pub_time_str) if match: pub_time datetime.strptime(match.group(1), %Y-%m-%d) except: pass article WechatArticle( titlearticle_info.get(title), authorarticle_info.get(author), publish_timepub_time, content_htmlarticle_info.get(content_html), content_textarticle_info.get(content_text), source_urlarticle_info.get(source_url), read_countarticle_info.get(read_count), like_countarticle_info.get(like_count), cover_image_urlarticle_info.get(cover_image_url) ) return article except Exception as e: print(f解析文章页面时出错: {e}) return None为什么要在page.evaluate中执行JS因为Playwright的Python API虽然能获取元素属性但进行复杂的DOM遍历和清洗时在浏览器上下文即页面内直接执行JavaScript效率更高、代码更简洁。特别是提取innerHTML和innerText在Python端做字符串处理不如在浏览器端直接操作DOM树方便。4.4 处理登录与Cookie持久化对于需要登录才能查看历史文章的公众号我们需要处理登录状态。重要提示自动化登录微信存在极高的账号安全风险可能导致账号被限制或封禁请务必谨慎仅用于学习研究并遵守平台规则。一种相对安全的思路是手动登录一次然后导出Cookie供脚本使用。手动获取Cookie编写一个简单的脚本用headlessFalse模式启动浏览器导航到公众号主页。手动扫码或账号密码登录。登录成功后使用Playwright导出当前上下文的Cookie。# 手动登录后保存Cookie async def save_cookies(context, file_pathcookies.json): cookies await context.cookies() import json with open(file_path, w) as f: json.dump(cookies, f) print(fCookies saved to {file_path})脚本加载Cookie在后续的自动化脚本中启动浏览器上下文时加载之前保存的Cookie。async def create_logged_in_context(browser, cookie_filecookies.json): context await browser.new_context(viewport{...}, user_agent...) if os.path.exists(cookie_file): with open(cookie_file, r) as f: cookies json.load(f) await context.add_cookies(cookies) print(Cookies loaded.) else: print(No cookie file found, will start fresh.) return context严重警告Cookie有有效期且与浏览器指纹、IP地址等绑定。频繁异地登录或异常访问仍可能触发风控。绝对不要将Cookie文件上传到公开仓库。此方法仅适用于低频、小规模的个人研究用途。5. 封装为OpenClaw技能并测试将上述Playwright代码封装到OpenClaw技能的标准结构中。5.1 编写技能主入口在main.py中我们需要创建一个符合OpenClaw调用约定的函数通常是run或execute。# main.py import asyncio from typing import Dict, Any from .models import WechatArticle # 假设抓取核心函数在一个叫crawler.py的文件里 from .crawler import crawl_wechat_article async def run(inputs: Dict[str, Any]) - Dict[str, Any]: OpenClaw技能标准入口函数 target inputs.get(target) max_posts inputs.get(max_posts, 10) headless inputs.get(headless, True) if not target: return {error: 输入参数target目标URL是必需的。} print(f开始抓取: {target}, 最大文章数: {max_posts}) # 调用异步抓取函数 articles_data await crawl_wechat_article(target, max_posts, headless) # 将Pydantic模型列表转换为字典列表以便JSON序列化输出 output_articles [article.dict() for article in articles_data if article] print(f抓取完成共获得{len(output_articles)}篇文章。) return { articles: output_articles } # 如果是直接运行脚本测试可以添加以下代码 if __name__ __main__: # 测试代码 test_inputs { target: https://mp.weixin.qq.com/s?__biz..., # 替换为单篇文章测试URL max_posts: 2, headless: False # 测试时打开浏览器可视化查看 } results asyncio.run(run(test_inputs)) import json print(json.dumps(results, ensure_asciiFalse, indent2))5.2 本地测试与调试在将技能部署到OpenClaw环境前务必进行充分的本地测试。测试单篇文章抓取使用一篇公开的文章URL设置headlessFalse观察浏览器行为是否正常数据提取是否准确。测试列表抓取使用一个你拥有访问权限的公众号主页URL可能需要已登录状态测试滚动和链接提取功能。控制max_posts为较小的值如3。验证输出结构检查返回的articles列表是否符合WechatArticle模型的定义时间格式是否正确内容是否完整。错误处理测试尝试输入一个无效的URL看错误是否被妥善捕获和记录而不是导致整个进程崩溃。调试技巧使用playwright codegen这是最强的调试工具直接生成操作代码。截图和录屏在代码关键点如登录后、列表加载后、解析前添加await page.screenshot(pathdebug.png)帮助定位页面状态问题。Console日志在page.evaluate中可以使用console.log()打印信息在Python端通过page.on(console, lambda msg: print(msg.text))监听。慢动作模式启动浏览器时使用slow_mo1000单位毫秒参数让所有操作慢速执行方便观察。6. 部署、优化与高级技巧技能开发完成后需要考虑如何让它稳定、高效、可持续地运行。6.1 部署与调度将技能部署到OpenClaw后你可以通过多种方式触发它CLI命令openclaw skill run wechat_article_crawler --target “URL”。API调用如果OpenClaw提供了HTTP Server可以通过POST请求调用技能。集成到自动化工作流这是OpenClaw最大的价值所在。你可以将这个技能作为n8n、Airflow或自定义调度系统中的一个节点。例如在n8n中可以定时触发该技能抓取指定公众号的新文章然后将结果数据保存到Google Sheets或发送到Notion数据库。部署环境建议Linux服务器推荐使用Ubuntu等系统资源占用更少。使用Docker将整个技能包括Python环境、Playwright浏览器打包成Docker镜像可以保证环境一致性方便迁移和扩展。处理无头模式依赖在无GUI的服务器上运行无头浏览器可能需要安装一些系统依赖。Playwright的安装脚本通常会处理但如果遇到问题可能需要手动安装sudo apt-get install -y libnss3 libatk-bridge2.0-0 libdrm-dev libxkbcommon-dev libgbm-dev libasound2。6.2 性能优化与稳定性提升并发控制如果需要抓取大量文章不要同时打开几十个页面。合理控制浏览器上下文Context和页面Page的数量。可以为每个任务使用独立的Browser Context但共享一个Browser实例。async def crawl_many_links(links): async with async_playwright() as p: browser await p.chromium.launch(headlessTrue) semaphore asyncio.Semaphore(3) # 控制最大并发数为3 tasks [] for link in links: task asyncio.create_task(_crawl_with_semaphore(semaphore, browser, link)) tasks.append(task) results await asyncio.gather(*tasks, return_exceptionsTrue) await browser.close() return results请求拦截与资源屏蔽为了提高加载速度和节省带宽可以拦截不必要的资源请求如图片、字体、样式表除非你需要截图。await page.route(**/*.{png,jpg,jpeg,gif,svg,woff,woff2,css}, lambda route: route.abort())代理IP池如果抓取频率很高考虑使用代理IP来分散请求避免IP被限制。Playwright启动浏览器时可以配置代理服务器。健壮的错误恢复网络波动、元素选择器失效是常态。代码中要有重试机制。对于列表抓取某篇文章失败不应导致整个任务终止应记录错误后继续下一篇。6.3 数据清洗与后续处理抓取到的content_html可能包含很多样式、无关的div和span标签。如果你只需要纯净的文本和图片链接可以进行二次清洗。使用readability或newspaper3k库这些库专门用于提取文章主体内容能有效去除导航、广告、页脚等噪音。自定义清洗规则基于对微信公众号文章HTML结构的了解编写正则表达式或使用BeautifulSoup移除特定class的div如引导关注、广告位。存储策略除了输出JSON可以考虑直接存入数据库如PostgreSQL的JSONB字段或MongoDB方便查询和分析。也可以将纯文本内容送入向量数据库构建本地知识库。7. 常见问题与避坑指南在实际操作中你几乎一定会遇到下面这些问题。问题现象可能原因排查与解决思路页面空白找不到任何内容1. 页面加载未完成。2. 被反爬机制检测到如WebDriver特征。3. 需要登录但未登录。1. 增加wait_until条件或显式等待关键元素。2. 使用args[--disable-blink-featuresAutomationControlled]并确保User-Agent是常见的移动端UA。3. 检查URL是否需要登录尝试加载Cookie或处理登录流程。元素选择器失效定位不到微信公众号前端代码更新DOM结构变化。1. 使用更稳定的选择器如通过>抓取速度很慢1. 页面资源图片、CSS加载拖慢速度。2. 网络延迟。3. 同步操作未充分利用异步。1. 拦截不必要资源见6.2。2. 考虑使用代理或优化网络环境。3. 确保所有Playwright API调用都使用await并利用asyncio.gather进行并发。阅读数/点赞数为0或抓不到这些数据通常是JS动态加载的初始HTML中没有。1. 等待更长时间或等待特定网络请求完成。2. 尝试通过page.evaluate()执行JS直接读取渲染后DOM中的文本。3. 如果数据来自接口尝试用page.on(response, ...)监听XHR/Fetch请求直接解析接口返回的JSON。账号被限制登录或操作自动化行为被微信安全策略识别。这是最严重的风险。1. 大幅降低抓取频率增加随机延迟。2. 避免在短时间内从同一IP发起大量请求。3.强烈建议仅用于个人、低频、非商业用途的研究和学习。OpenClaw调用技能报错技能输入输出格式不符合yaml定义或依赖未安装。1. 检查skill.yaml中inputs/outputs的type定义是否与Python代码中一致。2. 确保技能目录下的requirements.txt包含所有依赖并在OpenClaw环境中正确安装。最后的忠告技术是一把双刃剑。微信公众号内容抓取技能在技术实现上充满挑战和乐趣但务必牢记尊重版权抓取的内容仅用于个人学习、研究或符合“合理使用”原则的用途切勿用于商业牟利或侵犯原作者权益。遵守robots.txt虽然Playwright可以绕过但遵守目标网站的爬虫协议是基本的网络礼仪。控制影响将抓取频率控制在极低水平如每小时几次避免对目标服务器造成负担。风险自担任何自动化操作都可能带来账号安全、法律合规方面的风险请自行评估并承担后果。这个基于OpenClaw和Playwright的微信公众号抓取技能从技术层面为你提供了一个强大而灵活的解决方案框架。它不仅仅是代码的堆砌更是对动态Web自动化、数据管道构建和开源框架应用的一次深度实践。希望你在实现它的过程中既能解决实际问题也能享受到技术探索带来的乐趣。