Skip to content

feat: linkedin-session adapter (LinkedIn perf-data) - #51

Merged
Jooonnn merged 1 commit into
XBuilderLAB:mainfrom
h3qing:contrib/linkedin-session-adapter
Jun 18, 2026
Merged

Jooonnn merged 1 commit into
XBuilderLAB:mainfrom
h3qing:contrib/linkedin-session-adapter

Conversation

@h3qing

@h3qing h3qing commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

New linkedin-session perf-data adapter for /cheat-retro, mirroring the douyin-session architecture (Playwright persistent login + read rendered DOM — no signature reversing, no public API).

What it scrapes

LinkedIn's single-post analytics page (/analytics/post-summary/urn:li:activity:<id>/) is author-only and SSR/inline — there's no public API and no stable voyager XHR to intercept, so pure HTTP / requests (and WebFetch) can't reach it. The adapter logs in once (li_at cookie persisted to .auth-linkedin/), navigates to the post-summary page, and parses the rendered DOM text by known label anchors.

Captures 10 metrics for one post: impressions, members reached, reactions, comments, reposts, saves, sends, social engagements, profile-views-from-post, followers-from-post, plus the post body.

Matches the perf-data convention

  • bash run.sh <activity_id_or_url> <video_folder> [<script_path>] → writes <video_folder>/report.md, with the same exit codes as douyin-session (0 success · 1 login required · 2 missing playwright · 3 other) so /cheat-retro degrades gracefully to manual on any failure.
  • paths.py honors CHEAT_PROJECT_ROOT / CHEAT_VIDEOS_DIR like the other adapters; auth lives in its own .auth-linkedin/ to avoid cookie cross-talk.
  • report.md matches the existing adapters' shape: meta (author / age / link) → data snapshot (the 10 metrics + derived ratios: 反应率 / 评论率 / 转发率 / 社交互动率) → post body → original script → comments note.
  • review.py CLI matches the login / video <id> [script] contract.
  • Accepts a full post URL (…/feed/update/urn:li:activity:<id>/) or a bare activity_id and auto-extracts the id.
  • Adds .gitattributes (*.sh / *.py eol=lf) like bilibili-stat, to keep Windows-CRLF from breaking the scripts.

Bilingual JP/EN parser (honest maintenance note)

LinkedIn randomly flips the UI language between Japanese and English within a single session (a label shows up as Impressions one load and インプレッション数 the next). So extract.py's POST_METRICS stores both label aliases per metric and tries both. If you hit a third language, add the label to POST_METRICS — the aliases are a list. test_extract.py exercises both JP and EN with synthetic fixtures (no real metrics committed). The parser is DOM-text + language dependent, so it's marked stability ★★ and needs occasional upkeep; the debug dump (.cheat-cache/linkedin-session-debug/post_<id>.txt) is the first place to look when a field comes back None.

Install / usage

cd ~/my-channel
python3 -m venv .venv && source .venv/bin/activate
pip install -r adapters/perf-data/linkedin-session/requirements.txt   # playwright>=1.44
playwright install chromium
python adapters/perf-data/linkedin-session/crawler.py login           # log in once → .auth-linkedin/
# then /cheat-retro calls run.sh automatically; manual test:
python adapters/perf-data/linkedin-session/review.py video 7470493738918920193

How to get the activity_id: it's the number in a post URL https://www.linkedin.com/feed/update/urn:li:activity:<id>/.

Wiring

  • skills/cheat-retro/SKILL.md: adds the linkedin row to the adapter routing table + a linkedin-session 的特殊处理 block (same pattern as the douyin / xhs / bilibili PRs).
  • skills/cheat-init/SKILL.md: adds LinkedIn to the Q2.1 platform menu + the enabled_perf_adapters mapping.
  • .gitignore + templates/gitignore.template: add .auth-linkedin/ so the login cookie is never committed.

Tested

End-to-end on a real post (the example activity_id above): run.sh produces a sane report.md in the requested folder (auto-named-folder move works), and the unit tests pass for both JP and EN label sets. No auth, debug output, or __pycache__ is committed.

🤖 Generated with Claude Code

New linkedin-session adapter for /cheat-retro, mirroring the douyin-session
architecture (Playwright persistent login + read rendered DOM, no signature
reversing, no public API).

LinkedIn's single-post analytics page (/analytics/post-summary/) is
author-only and SSR/inline — no public API and no stable voyager XHR to
intercept, so pure HTTP can't reach it. This adapter logs in once (li_at
cookie persisted to .auth-linkedin/), navigates to the post-summary page,
and parses the rendered DOM text by known label anchors.

- Follows perf-data contract: bash run.sh <activity_id_or_url> <video_folder>
  [script] → writes <video_folder>/report.md; exit codes 0/1/2/3 match
  douyin-session
- paths.py honors CHEAT_PROJECT_ROOT / CHEAT_VIDEOS_DIR per the
  douyin-session / xhs-explore / bilibili-stat convention
- report.md format matches existing adapters: meta + data snapshot (10
  metrics: impressions / reach / reactions / comments / reposts / saves /
  sends / social engagements / profile-views-from-post / followers-from-post
  + derived ratios) + post body + comments note
- Bilingual JP/EN DOM parser: LinkedIn randomly flips the UI language
  between Japanese and English within a session, so extract.py's POST_METRICS
  stores both label sets per metric. Unit-tested both ways (test_extract.py,
  synthetic fixtures — no real metrics committed)
- accepts a full post URL (…/feed/update/urn:li:activity:<id>/) or a bare
  activity_id; auto-extracts the id
- .gitattributes (*.sh / *.py eol=lf) — prevents Windows-CRLF from breaking
  the scripts (matches bilibili-stat)
- cheat-retro adapter table + 特殊处理 block, cheat-init Q2.1 menu, and
  .gitignore / gitignore.template (.auth-linkedin/) all synced

Tested end-to-end on a real post (run.sh contract + report.md output).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Jooonnn
Jooonnn merged commit d07edb5 into XBuilderLAB:main Jun 18, 2026
@Jooonnn

Jooonnn commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

感谢这个 PR,整体质量很高 —— 架构忠实复刻了 douyin-session,JP/EN 双语 label parser 设计得当,cookie/auth 保密处理干净(.auth-linkedin/ 已进 .gitignore 和模板,无 secret/真实指标/__pycache__ 入库)。已合并 ✅

一个建议后续优化(非阻塞):

  • run.sh 的 PROJECT_ROOT 定位与其它适配器不一致:本 PR 用 dirname(dirname(realpath VIDEO_FOLDER)),硬假设视频目录正好在 <root>/videos/<folder> 两层之下。fix: 全仓代码审查 — 修复 Python / Shell / 协议 / 配置共 34 个 bug #49 之后 douyin/xhs/bilibili 都改成了向上查找 .cheat-state.json(不依赖嵌套深度 + 校验真项目根 + 找不到 exit 3)。当前写法若层级≠2 会算出错误 root,再因找不到 .auth-linkedin/ 报一个误导性的 exit 1 "Not logged in"。建议对齐到 .cheat-state.json 向上查找,保持优雅降级体验。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants