WorkBuddy挑战“四大天王”!
一、拾枝杂谈
我用 WorkBuddy 配合 Playwright 成功搭建了多平台数据矩阵,使用的核心技术栈就是 Python + Playwright + lark-cli(即飞书官方开源的命令行工具)。
一共七个平台,每个平台都有个性的问题,我都分别写了文章进行讲解。
只是我没想到,在最后一个平台——搜狐号的数据采集过程中,居然让我有点招架不住。
因为搜狐号派出了自己的“四大天王”——分别是“导航层级天王”,“日期选择天王”,“数据表格天王”,和 “日历弹窗天王”。

今天这篇文章,我就带大家一起攻下这四大天王!
二、“导航层级天王”
搜狐号的导航非常复杂,要想查看自己发布的文章数据,是这样走的:“数据分析” -> “内容分析” -> “单篇” -> “图文”,还要选定日期来确定统计的文章。
但是最开始的 config.py 配置中,WorkBuddy 把搜狐号的入口URL选错了,当时的 config.py 里搜狐号的 home_url 和 creator_url 配的是"home_url": "https://mp.sohu.com/mpfe/v4/contentManagement/first/page"。
所以脚本已启动就直接跳到了内容管理页,而不是数据分析页码。虽然我告诉了 WorkBuddy 正确的路径,但是脚本默认打开的起点就已经跑偏了,也就不可能读取正确的文章数据。
而且早期的脚本在“一通乱点”之后,没有进行检查,比如URL是否变化,页面上是否出现了“数据列表”或者类似的模块,playwright是否成功找到了 el-table 或 日期选择器。
相反,它直接就把当前页面的 HTML 抓下来解析了,然而既然当前页是内容管理页,那自然只能看到 “阅读 + 评论” 两项基础数据,与我们预期的完整数据大相径庭。
为了应对“导航层级天王”锐不可当的气势,我命令 WorkBuddy 做了两件事:
一是“单刀直入”,在 souhu.py 中代码写死,直接导航到数据分析页,不再依赖侧边栏点击 await self.page.goto("https://mp.sohu.com/mpfe/v4/data/analysis", ...)。相关代码片如下:
# Direct navigate to 数据分析 page (URL found from user manual nav)
print(f"[{self.name}] Direct navigate to data analysis page...")
try:
await self.page.goto("https://mp.sohu.com/mpfe/v4/data/analysis",
wait_until="domcontentloaded", timeout=60000)
except Exception as e:
print(f"[{self.name}] Direct nav error: {e}")
二是“步步为营”,先 goto 数据分析页,再点 tab,不再从内容管理页开始慢慢菜单导航。

三、“日期选择天王”
我先给大家还原一下“日期选择天王”是如何仗势欺人的。
我当时和 WorkBuddy 提出的需求是这样的:“进入搜狐号数据分析页后,把日期范围改成从 2026-07-15 到今天,这样能看到所有 GEO 系列的文章。”
但是脚本执行完,表格里只出来 1 条数据,而且日期范围看起来还是默认的最近几天,我让 WorkBuddy 排查后才发现,页面上有俩日期选择器!如下图所示:

可以看到,上面的选择器控制的是 图表数据,下面的选择器控制的是 表格数据。
playwright 脚本最开始的写法是这样的:
date_selectors = [
'input[placeholder*="日期"]',
'input[placeholder*="开始"]',
'.el-date-editor input',
...
]
for sel in date_selectors:
el = self.page.locator(sel).first
if await el.is_visible():
await el.click()
break
这么写问题出在哪儿?first 只拿到第一个可见的日期输入框, 也就是顶部图表区的那个; 填完顶部后,脚本没有意识到还有第二个选择器; 底部表格区的日期范围没被修改,仍然只显示默认 7 天内的文章, 所以只抓到 1 条数据。
为了应对“日期选择天王”的分身术,我让 WorkBuddy 使出了 “一石二鸟” 之法。不再只找“第一个”日期输入框,要找到全部 4 个输入框(顶部开始/结束 + 底部开始/结束),然后一口气全部填完。
验证思路如下:
# 找到全部 .el-range-input(两个日期范围选择器,共 4 个 input)
inputs = document.querySelectorAll('.el-range-input')
# 顶部图表区
inputs[0].value = "2026-07-15"
inputs[1].value = "2026-08-10"
# 底部数据列表区
inputs[2].value = "2026-07-15"
inputs[3].value = "2026-08-10"
# 触发 Vue/Element UI 的响应
inputs[0].dispatchEvent(new Event('input', {bubbles: true}));
inputs[0].dispatchEvent(new Event('change', {bubbles: true}));
# ... 对 4 个 input 都触发
然后填完后,按 Escape 键关闭日历弹窗,否则弹窗会挡住表格,影响读取数据;接着,再按 Enter 触发刷新,让表格真正按新日期范围重新请求数据。
由于搜狐号的 Element UI 日期选择器,输入框直接接受 2026-07-15 这种格式,所以 debug_sohu_date.py 里直接用了了项目约定的 ISO 格式。
四、“数据表格天王”
前两个天王被打倒后,页面终于能正确加载出数据列表了,但脚本一解析,返回的结果却是一塌糊涂!
document.querySelectorAll(‘table’) 找到了一些 <table> 标签,但是提取出来的 rows 要么是空的,要么只有表头,要么行内容对不上号。给人的感觉就像是页面上明明有表格,Playwright 却读不到数据。
WorkBuddy 的干活能力确实可以,一顿排查后它告诉我,根本原因是搜狐号后台用的是 Vue + Element UI。它的 组件渲染到 DOM 里之后,并不是我们平时写的那种简单结构:
<table>
<thead><tr><th>标题</th><th>阅读</th>...</tr></thead>
<tbody><tr><td>...</td></tr></tbody>
</table>
而是为了支持固定列、表头、虚拟滚动等效果,拆成了多张表和大量 div 嵌套,大概长下面这样:
<div class="el-table">
<!-- 表头区域(单独一张 table) -->
<div class="el-table__header-wrapper">
<table class="el-table__header">...</table>
</div>
<!-- 表体区域(单独一张 table) -->
<div class="el-table__body-wrapper">
<table class="el-table__body">
<tbody>
<tr>
<td>
<div class="cell">
<div class="label">
文章标题
<div>2026-07-20</div>
</div>
</div>
</td>
<td><div class="cell">123</div></td>
<td><div class="cell">45</div></td>
...
</tr>
</tbody>
</table>
</div>
</div>
虽然能抓到 el-table__header 和 el-table__body 这两张表,但它按“第一张表的表头 + 第二张表的行”这种默认规则去拼,结果就乱了。
为了对付“数据表格天王”的障眼法,我和WorkBuddy商讨后决定采用“量体裁衣”政策,专门写了一个只针对 el-table 的解析器,核心逻辑在 sohu.py 的 _extract_articles_from_page() 里:
async def _extract_articles_from_page(self) -> list:
return await self.page.evaluate(r"""
() => {
const out = [];
const tbody = document.querySelector('.el-table__body tbody');
if (!tbody) return out;
const trs = tbody.querySelectorAll('tr');
for (const tr of trs) {
const labelEl = tr.querySelector('.label');
if (!labelEl) continue; // 过滤掉没有标题的行
const fullText = (labelEl.innerText || '').trim();
if (!fullText) continue;
const lines = fullText.split('\n').map(s => s.trim()).filter(Boolean);
const title = lines[0] || '';
let dateStr = '';
const dateMatch = fullText.match(/(\d{4}-\d{2}-\d{2})/);
if (dateMatch) dateStr = dateMatch[1];
const cells = tr.querySelectorAll('div.cell');
const nums = [];
for (let i = 1; i < cells.length - 1 && i < 7; i++) {
nums.push((cells[i].innerText || '').trim());
}
if (title && nums.length >= 6) {
out.push({
title: title,
publish_date: dateStr,
reads: nums[0],
visits: nums[1],
likes: nums[2],
comments: nums[3],
shares: nums[4],
votes: nums[5],
});
}
}
return out;
}
""")
这个个性化方法实现了下面这些要点:
- 直接定位
.el-table__body tbody,而不是遍历所有 <table>; - 用
.label判断有效数据行,没有.label的行直接跳过; - 从
.label的文本里拆分标题和日期; - 按
div.cell的顺序取 6 个数字字段,对应到 阅读数、访问数、点赞数、评论数、分享数、投票数。
三个问题全解决之后,才终于能稳定拿到搜狐号的数据。
五、“日历弹窗天王”
我没想到 “日期选择天王” 居然和 “日历弹窗天王” 狼狈为奸!
之前在解决“日期选择天王” 的时候,我们最终选择的修正方案是直接往日期输入框填数值。填好之后,脚本就直接去截图了,但是这时候日历弹窗还开着呢!
奇怪了,为啥弹窗还在遮挡呢?
这是因为 Element UI 的 el-date-picker 组件是这样的交互:
- 用户点击或聚焦日期输入框;
- 组件弹出一个日历面板,让用户点选日期;
- 如果用户点选或按 Enter,面板关闭;
- 如果用户按 Escape,面板也会关闭。
脚本用代码往输入框里填值,并没有真正完成“用户点选/关闭面板”这个动作, 所以日历面板一直悬在页面上,挡住了一大半内容。直观效果就是截图里一半画面被日历遮住了。
虽然 querySelector 读 DOM 不依赖视觉可见性,但如果弹窗层有 z-index 覆盖或焦点陷阱,某些操作会受影响。
修复方式也很简单,就是填好日期后,Playwright 模拟按一下 Escape 键关闭日历弹窗即可。如下所示:
# 1.填完 4 个日期输入框
inputs[0].value = "2026-07-15"
inputs[1].value = "2026-08-10"
inputs[2].value = "2026-07-15"
inputs[3].value = "2026-08-10"
# 2.触发 Element UI / Vue 的响应
for (const input of inputs) {
input.dispatchEvent(new Event('input', {bubbles: true}));
input.dispatchEvent(new Event('change', {bubbles: true}));
}
# 3.关键:按 Escape 关闭日历弹窗
await page.keyboard.press("Escape")
# 4.再按 Enter 触发表格刷新
await page.keyboard.press("Enter")
Δ总结
- OK,以上就是本篇文章的全部内容了,感谢阅读!
- 这篇文章总结了up 在处理搜狐号数据统计时遇到的四个主要问题,包括导航复杂的问题,日期选择器不唯一的问题,数据表格隐式呈现的问题,以及日历弹窗遮挡的问题。我把它们比喻成了四大天王哈哈。
- 关注迅高AI实验室,学习AI不迷路!
更多推荐
所有评论(0)