这几天折腾 LinkedIn 的自动化,头皮都要挠破了。需求其实很简单:写个脚本定时去更新自己的 LinkedIn 个人简介(Headline 和 About),顺便抓一下私信里的猎头消息。

一开始我觉得这事儿有手就行,掏出祖传的 Playwright 一把梭,结果啪啪打脸。LinkedIn 的反爬和风控策略简直变态:

  1. WebDriver 标记检测:原生的无头浏览器一上去,甚至还没开始动作,直接给你弹个验证码或者直接封 Session。
  2. React 合成事件拦截:你以为用 page.evaluate() 写点 JS 去改 DOM 的 value 就能填表单?天真!前端的 React 根本不认,你哪怕把输入框的值改了,一提交还是空的。

折腾了两天,最终摸索出了一套本地接管浏览器 + Playwright Stealth + 纯原生模拟按键的终极组合拳,完美绕过检测。这里给大家详细复盘一下,少走弯路。

核心思路:放弃无头,接管真实浏览器

绝大多数反爬检测都在查你的“指纹”。如果你每次都新起一个干净的无头浏览器,还要去模拟登录,被干掉的几率极高。

老司机的做法是:在本机以“开启调试端口”的方式,启动一个真实的 Chrome,然后再用 Playwright 连上去。这样你现成的 Cookie、登录态全都在,且不会带有明显的自动化指纹。再加上 playwright-stealth 的掩护,基本上稳如老狗。

第一步:环境与插件安装

你需要装一下 playwright 和 stealth 插件:

pip install playwright
pip install playwright-stealth

第二步:带调试端口启动 Chrome

这一步千万别漏。你需要关掉现有的所有 Chrome 窗口,然后从命令行启动它,并指定调试端口。

Mac/Linux 环境:

# 指定一个用户数据目录,避免和日常使用的冲突导致锁死
google-chrome-stable --remote-debugging-port=9222 --user-data-dir=/tmp/chrome_debug_profile

Windows 环境:

chrome.exe --remote-debugging-port=9222 --user-data-dir="C:\chrome_debug"

启动之后,你可以手动在这个弹出的浏览器里打开 LinkedIn,把账号密码登录好。

第三步:Python 脚本实战,修改 LinkedIn Headline

准备工作做完,直接上代码。下面这段代码会连接到刚刚开启的 Chrome,自动访问 LinkedIn 个人资料页,并把 Headline 修改为我们指定的文字。

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
import time

def update_linkedin_headline(new_headline):
    with sync_playwright() as p:
        # 1. 核心!连接到已经启动并登录好 LinkedIn 的本地 Chrome
        browser = p.chromium.connect_over_cdp('http://127.0.0.1:9222')
        
        # 2. 获取当前的上下文并新建页面
        context = browser.contexts[0]
        page = context.new_page()
        
        # 3. 注入 Stealth 伪护脚本,抹除 WebDriver 指纹!
        stealth_sync(page)

        print("正在访问 LinkedIn 个人主页...")
        # 注意把这里的 url 换成你自己的 profile 链接
        page.goto('https://www.linkedin.com/in/me/')
        page.wait_for_load_state('domcontentloaded')
        
        # 停顿一下,模拟人类的加载等待
        time.sleep(3)

        # 4. 点击编辑按钮 (这里的选择器可能因为 LinkedIn 的改版而变化,请根据实际情况微调)
        # LinkedIn 的前端非常复杂,建议使用 aria-label 或 has-text 来稳定定位
        edit_btn = page.locator('button[aria-label="Edit intro"]')
        if edit_btn.is_visible():
            edit_btn.click()
            time.sleep(2)
        else:
            print("找不到编辑按钮!")
            return

        # 5. 修改 Headline
        # 【坑点警告】绝对不要用 page.evaluate() 去改 value!
        # 必须用原生的 .fill() 或者模拟键盘按键,否则 React 不会触发 onChange 事件!
        headline_input = page.locator('input[name="headline"]')
        headline_input.click() # 先聚焦
        
        # 清空原内容并填入新内容
        headline_input.fill(new_headline)
        print(f"已填入新 Headline: {new_headline}")
        
        time.sleep(1)

        # 6. 点击保存
        save_btn = page.locator('button').filter(has_text='Save')
        if save_btn.count() > 0:
            save_btn.last.click()
            print("保存完毕!")
            time.sleep(3)
        else:
            print("没找到保存按钮")
        
        # 收工,断开连接(不会关闭浏览器)
        page.close()
        browser.disconnect()

if __name__ == '__main__':
    update_linkedin_headline("AI Agent Developer | System Architecture | MLOps")

踩坑总结

代码看起来不长,但全是血泪教训:

  1. 不要用 DOM 注入去修改输入框:LinkedIn 这种 React 重度依赖的网站,监听的是底层事件。纯原生操作 .fill() 会模拟一连串的输入事件,这样“保存”按钮才能正常亮起。
  2. Stealth 插件是保命符:加了 stealth_sync(page),后台查你的 navigator.webdriver 永远是 false,查插件列表也能模拟出正常的浏览器环境。对于日常抓抓猎头私信,甚至自动回复,这招百试百灵。

总之,以后凡是遇到封控严格的站,别头铁用纯脚本去硬刚。本机接管 + Stealth 伪装 + 原生键盘模拟,才是现代爬虫和自动化的终极出路!

遇到问题欢迎在评论区交流,如果有更好的路子也请不吝赐教。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐