Appearance
Python 爬虫实战:构建社媒热点笔记与评论采集系统
前言
在社交媒体数据分析中,如何稳定、高效地获取热点内容及其用户反馈一直是个技术难点。某书(XHS)作为典型的 SPA(单页应用),其前端更新频繁,反爬机制复杂。
本文将面向工程师,详细介绍一套 社媒数据采集系统 的技术架构与核心实现。该系统采用 Playwright 浏览器自动化 + 网络请求拦截 的混合策略,实现了对热点笔记与评论的稳定抓取、智能过滤及自动化推送。
总体架构
为了兼顾灵活性与产品化体验,系统采用了分层架构设计:
- 抓取层:基于 Playwright,负责浏览器交互、页面渲染与网络事件监听。
- 登录与会话层:管理 Cookies 轮换、登录态保持与上下文复用。
- 数据层:基于 SQLite 的轻量级存储,负责数据去重、持久化与索引优化。
- 推送层:将清洗后的客源数据批量推送到下游业务系统。
- UI 层:基于 PyQt6 构建桌面端界面,通过多线程实现非阻塞交互。
核心技术选型
为什么选择 Playwright?
相较于传统的 Requests/BeautifulSoup 纯协议抓取,我们优先选择了 Playwright,主要基于以下考量:
- 真实渲染:能够完美处理动态加载的 JS 内容,对抗前端混淆。
- 网络拦截:可以直接监听并解析浏览器发出的 API 请求(Response Interception),获取结构化的 JSON 数据,避免了复杂的 HTML 解析。
- 上下文复用:支持持久化 BrowserContext,大幅减少冷启动与重复登录的开销。
核心功能实现
1. 热点笔记抓取:混合模式
系统支持“关键字搜索”与“发现页推荐”两种模式。核心逻辑在于利用 Playwright 加载页面,同时监听后台 API 数据的返回。
我们通过监听 window.__INITIAL_STATE__ 或特定的 API 路由来获取数据,而不是单纯依赖 DOM 解析。
python
async def start_crawling(self, keywords):
# 启动浏览器上下文
context = await self.browser.new_context(storage_state="auth.json")
page = await context.new_page()
# 监听网络响应,直接截获 JSON 数据
page.on("response", self.handle_api_response)
# 访问搜索页
await page.goto(f"https://www.example.com/search_result?keyword={keywords}")
# 模拟人类滚动行为,触发懒加载
await self.auto_scroll(page)
def handle_api_response(self, response):
"""拦截搜索接口与详情页接口"""
if "/api/sns/web/v1/search/notes" in response.url:
data = response.json()
self.save_notes(data['data']['items'])这种方式既利用了浏览器的渲染能力通过反爬检测,又直接获取了干净的 JSON 数据。
2. 评论深度抓取:攻克“展开”难题
评论抓取是本项目的难点,尤其是多级子评论的展开。在 评论抓取模块 中,我们实现了一套智能的展开逻辑:
- 监听 API:监听
/api/sns/web/v1/comment/相关接口。 - 自动展开:识别页面中的“展开”、“查看更多回复”按钮,并模拟点击。
- 父子关联:在内存中构建评论树,将子评论挂载到对应的父评论下。
python
async def expand_replies(self, page):
"""智能识别并点击展开按钮"""
# 定位所有可见的展开按钮
# 包含:展开 x 条回复、查看更多等文案
expand_buttons = page.locator("text=/展开|查看更多|更多回复/")
count = await expand_buttons.count()
for i in range(count):
btn = expand_buttons.nth(i)
if await btn.is_visible():
# 滚动到元素位置,模拟真实点击
await btn.scroll_into_view_if_needed()
await btn.click()
# 随机延时,避免触发频控
await page.wait_for_timeout(random.randint(500, 1500))3. 数据存储与去重
使用 SQLite 作为本地数据库,设计了高效的表结构以支持高频写入与去重。
在 数据存储模块 中,我们使用了 INSERT OR REPLACE 语法来确保数据的幂等性(即重复抓取同一篇笔记时,自动更新而非报错)。
python
CREATE TABLE IF NOT EXISTS notes (
note_id TEXT PRIMARY KEY,
title TEXT,
user_id TEXT,
likes INTEGER,
comments INTEGER,
collected INTEGER,
share INTEGER,
hot_score REAL, -- 自定义热度评分
crawl_time INTEGER
);
-- 针对查询频繁的字段建立索引
CREATE INDEX IF NOT EXISTS idx_notes_time ON notes(crawl_time);
CREATE INDEX IF NOT EXISTS idx_notes_score ON notes(hot_score DESC);4. 反爬与稳定性策略
为了保证工具的长期稳定运行,我们在 爬虫主逻辑 中集成了多重防护机制:
- 登录态检测:在操作前检查登录元素,若失效则暂停任务并弹窗提示。
- 人类行为模拟:
- 随机延时:点击与滚动之间插入高斯分布的随机等待时间。
- 鼠标轨迹:避免机械式的瞬间跳转。
- 僵尸进程清理:在 初始化阶段,会自动扫描并清理上次异常退出遗留的浏览器进程,防止内存泄漏。
UI 与工程化
为了让非技术人员(如运营团队)也能使用,我们基于 PyQt6 开发了桌面客户端,并实现了任务的线程化管理。同时,通过 PyInstaller 与 Inno Setup,我们将复杂的 Python 环境打包为标准的 Windows 安装程序 (.exe),实现了“开箱即用”。
在 UI 线程管理 模块中,抓取任务运行在独立的 QThread 中,通过信号(Signal)与槽(Slot)机制将日志和进度实时更新到 UI,确保界面不会因阻塞操作而卡死。
python
class CrawlThread(QThread):
log_signal = pyqtSignal(str)
progress_signal = pyqtSignal(int)
def run(self):
# 实例化爬虫核心
crawler = XHSCrawler(config=self.config)
# 注入回调函数,实现实时日志
crawler.set_logger(self.log_signal.emit)
crawler.start()总结
本系统通过 浏览器真实渲染 + API 网络拦截 + HTML 兜底 的三重策略,有效地解决了 SPA 网页数据抓取的稳定性问题。
- 对于数据:通过 SQLite 本地化存储与标准化清洗,建立了可靠的数据资产。
- 对于效率:自动化的客源过滤与推送,替代了人工手动搜寻,大幅提升了运营效率。
- 对于扩展:模块化的设计使得后续接入新的分析规则或推送渠道变得十分简单。
