Work apps

feishu.cn

飞书文档(Feishu Docs / docx)抓取

Notes an AI agent reads before it works on feishu.cn: what to open, what to call, what breaks. Written by browser-harness contributors after real runs, and kept on GitHub under the MIT license.

Give it to your agent

Any coding agent

Start the task with this line. The Markdown version is the same notes without the page around them.

Read https://browser-use.com/skills/feishu.md, then <your task>

Browser Harness

Save the files into your workspace and turn domain skills on. The code below calls its helpers, such as http_get and js. What Browser Harness is.

d="${BH_AGENT_WORKSPACE:-$HOME/.config/browser-harness/agent-workspace}/domain-skills/feishu"
mkdir -p "$d" && cd "$d"
curl -sLO https://raw.githubusercontent.com/browser-use/browser-harness/main/agent-workspace/domain-skills/feishu/docx-scraping.md
export BH_DOMAIN_SKILLS=1

抓 *.feishu.cn/docx/<token> 的正文与配图。连接用户已登录的 Chrome 即可,无需处理登录。

Current discovery uses the first hostname label, which can be the tenant name. Read this file directly for Feishu document tasks when automatic discovery does not list it.

URL 形状

https://<tenant>.feishu.cn/docx/<25位token>

同一 wiki 空间内的兄弟文档只在正文里以富文本链接出现,document.body.innerText 拿不到 href--要拿链接得查 DOM:

js("Array.from(document.querySelectorAll('a[href*=\"/docx/\"]')).map(a=>({t:a.innerText.trim(),h:a.href}))")

陷阱 1:滚动容器不是 window

正文渲染在一个内部 div 里,window.scrollY 恒为 0,window.scrollBy() 无效。先探测真正的滚动容器:

FIND = """
(() => {
  let best=null,bh=0;
  document.querySelectorAll('div').forEach(el=>{
    const s=getComputedStyle(el);
    if((s.overflowY==='auto'||s.overflowY==='scroll') && el.scrollHeight>el.clientHeight+200){
      if(el.scrollHeight>bh){bh=el.scrollHeight;best=el;}
    }});
  window.__sc=best || document.scrollingElement;
  return best?{m:'el',sh:best.scrollHeight,ch:best.clientHeight}
            :{m:'win',sh:document.documentElement.scrollHeight,ch:window.innerHeight};
})()
"""
js(FIND)

实测返回形如 {'m':'el','sh':8054,'ch':769}。之后统一用 window.__sc.scrollTop += N 滚动。

陷阱 2:内容是虚拟化的

滚出视口的块会被卸载,innerText 只返回当前渲染的部分。一次性读拿不到全文。做法:小步滚动 + 逐行去重累积。

import time
js("window.__sc.scrollTop = 0")
time.sleep(9)
seen, lines = set(), []
idle = 0
for i in range(500):
    for ln in js("document.body.innerText").split("\n"):
        s = ln.strip()
        if s and s not in seen:
            seen.add(s)
            lines.append(s)
    state = js("({top:window.__sc.scrollTop,sh:window.__sc.scrollHeight,ch:window.__sc.clientHeight})")
    if state["top"] + state["ch"] >= state["sh"] - 5:
        break
    step = max(1, int(state["ch"] * 0.8))
    js(f"window.__sc.scrollTop += {step}")
    time.sleep(0.75)
    current = js("window.__sc.scrollTop")
    idle = idle + 1 if current == state["top"] else 0
    if idle >= 4:
        raise RuntimeError("Document scrolling stopped before the end")
else:
    raise RuntimeError("Document capture reached its iteration limit")
 
  • 步长取 clientHeight * 0.8 左右,留重叠,避免漏块。
  • 每步之间 sleep ≥ 0.7s,否则新块还没渲染就读了。
  • 加 stall 计数(连续 3–4 次 scrollTop 不变就 break),否则末尾会空转。
  • 首屏要先 sleep(8~10),飞书首次渲染慢。

行级去重的代价:正文里重复出现的短句(表头、「注意:」)只会保留第一次。对提取事实够用,对还原原文顺序不够。

陷阱 3:表格结构会丢

飞书表格在 innerText 里退化成一串无分隔的单元格文本,列关系全丢。文档里的关键规格常常就在表格中。结论:表格靠文本抓不回来,只能靠截图看。

陷阱 4:配图 URL 需要鉴权,别直接下

正文配图指向 internal-api-drive-stream.feishu.cn/space/api/box/stream/download/v2/cover/...,脱离浏览器会话直接 http_get 拿不到。

可行做法:滚动截屏,把整页按屏切成 PNG:

capture_screenshot(f"{out}/{slug}_{i:03d}.png", max_dim=1800)
  • max_dim=1800 必须加:2× 屏下原图超过 2000px,部分模型会拒收。
  • 一份 8000px 高的文档约 12–21 张。
  • 截屏同时保留了表格结构和配图,是补齐陷阱 3 的唯一手段。

噪声清单

正文抽取会混入这些固定 UI 文本,写清洗规则时直接丢弃:

External / Add Shortcut / Last modified: ... / Share / AI QuickView / Free trial
Comments (0) / Upload Log / Customer Service / What's New / Help Center
Keyboard Shortcuts / Backlinks (0) / Mentioned Docs (N) / Graph view
Suggestions are generated by AI. / This document hasn't been mentioned by others yet.
Be the first to like this

另外正文里普遍插入零宽字符,先清掉再比对:

t = t.replace("​","").replace("","").replace("⁠","")

文档标题尤其明显--extract-meta.json 里抓到的 title 前缀是几十个零宽字符。

推荐流程

1. new_tab(url) → wait_for_load() → sleep(9)
2. 探测滚动容器 → 回到顶部
3. 第一遍:滚动 + 累积 innerText  → full-text.txt
4. 第二遍:回到顶部,滚动 + capture_screenshot → shots/
5. 用截图补表格与配图,用文本做全文检索

两遍分开跑,不要在一个循环里同时做--截屏耗时会拖慢文本累积的节奏,容易漏块。

docx-scraping.md · updated Oct 9, 2026 · 364 words

Sites change. These notes describe what worked when they were last updated, not a guarantee they still do. If something broke, the fix is a pull request to browser-harness.