1. 为什么企业尽调场景需要复用 Chrome 登录态
做股权穿透、关联企业筛查、投融资背景核查时,绕不开一个动作:把目标公司的自然人股东全部拉出来,再逐个进入股东个人页,翻完他名下所有任职企业。一个中等规模的主体,股东二三十人,每人任职企业十几家,手动点下来就是几百次页面跳转,眼睛和手都受不了。
真正麻烦的不是翻页本身,而是登录。企业信息平台对未登录用户的可见字段做了大量裁剪,股东持股比例、任职明细这些关键数据往往要登录后才完整展示。如果脚本里用账号密码登录,会频繁触发滑块验证、短信校验,跑不了几十条就被风控拦下。所以更稳的思路是:复用你本机 Chrome 里已经登录好的会话,让 Selenium 挂到这个已经带 Cookie 的浏览器实例上,脚本只负责点页面、抓数据,不碰登录流程。
这套方案的核心技术点是 Chrome 的 CDP 远程调试端口。Chrome 启动时加一个--remote-debugging-port参数,就会在本地开一个调试服务,Selenium 通过debuggerAddress连上去,等于直接接管你眼前这个已经登录的浏览器。本文就把这套流程拆成可复制的配置和代码,从启动参数到批量提取的验证动作一次讲清。适合做尽调、投研、企业背景分析,且有一定 Python 基础的同学跟做。
2. TaoToken 在批量提取链路里的前置准备
脚本要跑起来,除了 Selenium 和 Chrome,还需要一个稳定的模型调用入口来处理提取过程中的字段清洗、企业名称标准化、异常判断这类需要语义理解的动作。比如从一堆任职企业里区分「存续」和「注销」,或者把「XX科技(上海)有限公司」和「XX科技上海分公司」归并成同一主体,纯正则很难覆盖全,交给模型判断更省事。
我这边用的是 TaoToken 的 API 来做这层处理。它的接入地址是https://taotoken.net/api,兼容常见的 OpenAI 风格调用格式,Python 里用requests或openaiSDK 都能直接打。你需要先去控制台拿一个 API Key,然后在脚本里配好 base_url 和 key 即可。
具体操作路径:打开官网https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,进入控制台创建 API Key,地址是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。拿到 key 后,模型对话调试可以用https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite先验证连通性。如果你后面要把这套采集做成长期跑的 Agent 任务,可以看下 Coding Plan 页面https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。
注意:TaoToken 在这里的角色是「数据后处理与语义判断的模型入口」,不是浏览器代理,也不参与页面请求。页面抓取仍然走你本机 Chrome 的真实会话,两者职责分开,链路才清晰。
3. 可复制的 config.toml 与 settings.json 配置骨架
为了让端口、路径、等待时长这些参数不散落在代码里,建议抽成配置文件。下面这份config.toml覆盖了浏览器连接、采集行为、模型后处理三块,直接改值就能用。
# config.toml [browser] # Chrome 远程调试端口,默认 9222 debug_port = 9222 # 本机 Chrome 用户数据目录,Windows 示例,macOS/Linux 换成对应路径 user_data_dir = "C:/Users/yourname/AppData/Local/Google/Chrome/User Data" # 页面加载超时(秒) page_load_timeout = 30 # 元素显式等待超时(秒) element_wait_timeout = 15 [collect] # 目标企业清单文件 company_list_file = "companies.txt" # 输出目录 output_dir = "./output" # 翻页随机延时区间(秒) delay_min = 1.5 delay_max = 3.5 # 单股东任职企业最大翻页数,防止死循环 max_pages_per_shareholder = 50 [llm] # TaoToken API 入口 base_url = "https://taotoken.net/api" api_key = "sk-你的key" model = "gpt-4o-mini" # 是否启用模型做企业名称标准化 enable_name_normalize = true对应的settings.json用来存一些运行时状态和字段映射,方便脚本读取:
{ "field_map": { "shareholder_name": "股东姓名", "share_ratio": "持股比例", "profile_link": "个人主页", "company_name": "企业名称", "position": "职位" }, "output": { "json_file": "result.json", "txt_file": "readable_result.txt", "excel_file": "result.xlsx" }, "dedup": { "by_company_id": true, "normalize_name": true } }读取配置的代码很短,用tomllib(Python 3.11+)或toml库都行:
import tomllib import json with open("config.toml", "rb") as f: config = tomllib.load(f) with open("settings.json", "r", encoding="utf-8") as f: settings = json.load(f) DEBUG_PORT = config["browser"]["debug_port"] USER_DATA_DIR = config["browser"]["user_data_dir"]这样改端口、改路径、改延时都不用动主逻辑,换台电脑只改config.toml一处。
4. 启动 Chrome 并挂载 Selenium 的完整步骤
4.1 手动启动带调试端口的 Chrome
关键点:不要用 Selenium 自己新建一个 Chrome 实例,那样会开一个全新的用户数据目录,登录态是空的。正确做法是先手动启动 Chrome,指定调试端口和已有的用户数据目录。
Windows 下命令:
chrome.exe --remote-debugging-port=9222 --user-data-dir="C:/Users/yourname/AppData/Local/Google/Chrome/User Data"macOS 下:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir="/Users/yourname/Library/Application Support/Google/Chrome"启动后,浏览器里应该已经是你平时登录好的状态。可以先访问一下目标平台,确认登录还在。然后打开http://127.0.0.1:9222/json/version,能看到返回的 JSON 就说明调试端口通了。
4.2 Selenium 挂载现有实例
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_experimental_option("debuggerAddress", f"127.0.0.1:{DEBUG_PORT}") driver = webdriver.Chrome(options=options) # 验证挂载成功:打印当前页面标题 print(driver.title)如果这一步报Connection refused,说明 Chrome 没起来或者端口不对;如果报user data directory is already in use,说明你重复启动了 Chrome,把多余的进程关掉再试。
4.3 搜索企业并进入主页
直接拼搜索 URL 比模拟输入框点击稳定得多,中文和特殊符号用quote编码:
import urllib.parse import time def search_company(driver, company_name): search_url = f"https://www.tianyancha.com/search?key={urllib.parse.quote(company_name)}" driver.get(search_url) time.sleep(2) # 抓取第一条 /company/ 链接 links = driver.find_elements("css selector", "a[href*='/company/']") if not links: return None return links[0].get_attribute("href")4.4 解析股东表格
不要用固定 XPath,页面一改版就废。更稳的做法是全局找包含「股东信息」文本的节点,向上回溯到最近的<table>,再遍历行:
def parse_shareholders(driver): shareholders = [] # 找到包含「股东信息」的容器 nodes = driver.find_elements("xpath", "//*[contains(text(),'股东信息')]") if not nodes: return shareholders table = nodes[0].find_element("xpath", "./ancestor::table[1]") rows = table.find_elements("tag name", "tr") for row in rows[1:]: # 跳过表头 cells = row.find_elements("tag name", "td") if len(cells) < 3: continue name_el = cells[1].find_elements("tag name", "a") name = name_el[0].text if name_el else cells[1].text ratio = cells[2].text link = name_el[0].get_attribute("href") if name_el else "" shareholders.append({"name": name, "ratio": ratio, "link": link}) return shareholders4.5 抓取股东任职企业并翻页
进入股东个人页后,切到「所有任职企业」标签,定位表头同时含「职位」和「企业名称」的表格,逐行取企业名,然后循环点下一页:
def parse_companies(driver, max_pages=50): companies = [] seen = set() for _ in range(max_pages): tables = driver.find_elements("css selector", "table") target = None for t in tables: header = t.text if "职位" in header and "企业名称" in header: target = t if not target: break rows = target.find_elements("tag name", "tr") for row in rows[1:]: cells = row.find_elements("tag name", "td") if len(cells) < 2: continue name = cells[1].text.strip() if name and name not in seen: seen.add(name) companies.append(name) # 找下一页按钮 next_btns = driver.find_elements("xpath", "//a[contains(text(),'下一页')]") if not next_btns or "disabled" in next_btns[0].get_attribute("class"): break next_btns[0].click() time.sleep(2) return companies4.6 用 TaoToken 做企业名称标准化
抓下来的企业名可能有「XX科技(上海)有限公司」和「XX科技上海分公司」这种变体,去重前先过一遍模型:
import requests def normalize_names(names, config): prompt = "请把以下企业名称做标准化归并,输出JSON数组,相同主体的名称合并为一项:\n" + "\n".join(names) resp = requests.post( f"{config['llm']['base_url']}/v1/chat/completions", headers={"Authorization": f"Bearer {config['llm']['api_key']}"}, json={ "model": config["llm"]["model"], "messages": [{"role": "user", "content": prompt}] }, timeout=60 ) return resp.json()["choices"][0]["message"]["content"]5. 验证请求与成功结果
跑通的标准动作分三步。第一步,确认挂载成功:脚本启动后打印driver.title,应该是你当前 Chrome 打开的页面标题,而不是空白页。第二步,确认登录态复用:访问目标企业主页,检查股东表格是否有数据,如果返回的是登录引导页,说明 Cookie 没带上,回到 4.1 检查user-data-dir是否指向了你平时登录的那个目录。第三步,确认批量提取完整:以一家股东数 20+ 的企业为例,脚本跑完后result.json里应该有 20 条以上股东记录,每条带companies数组,数组长度和页面上「所有任职企业」显示的总数对得上。
一个正常的输出片段长这样:
{ "company": "目标企业名称", "shareholders": [ { "name": "张三", "ratio": "35%", "companies": ["A科技有限公司", "B信息技术有限公司", "C投资合伙企业"] } ] }如果companies数组为空但页面上明明有数据,多半是表格定位选错了,参考下一节的排查。
6. 本篇常见错误排查
报错一:Connection refused连不上 9222。原因通常是 Chrome 没带调试参数启动,或者端口被占用。先确认http://127.0.0.1:9222/json/version能打开,打不开就换端口,比如 9223,同时改config.toml里的debug_port。
报错二:user data directory is already in use。你重复启动了 Chrome。任务管理器里把所有 chrome.exe 进程结束,只保留一个带调试参数的实例。
报错三:翻页后数据不变,重复抓同一页。页面里可能有多套分页控件,误点了无关的那个。解决办法是通过 DOM 父子关系,把分页容器绑定到当前数据表的父节点上,而不是全局找「下一页」。
报错四:部分股东没有个人主页链接。有些股东是法人主体或未实名展示,没有/human/页面。脚本里要单独捕获,标记为「无链接」跳过,不要让它中断整个任务。
报错五:任职企业不足 10 条时表格匹配失效。如果之前用「行数≥10」来判断目标表格,小数据量就会漏。改成「表头关键词 + 父容器 class + 分页绑定」三重特征定位,不依赖行数。
报错六:切换标签后抓到空白数据。标签点击后接口是异步返回的,直接抓会拿到旧 DOM。点击后加显式等待,等表格行数大于 0 或 Loading 遮罩消失再解析。
报错七:脚本异常退出后残留大量 Chrome 进程。在finally块里统一执行driver.quit(),但注意复用模式下quit()会关掉你手动启动的浏览器,如果不想关,改成只close()当前标签页。
7. 接入与后续动作
这套方案跑通后,日常尽调的单企业采集基本够用了。如果你要把它做成批量任务,比如一次导入几十家企业清单循环跑,建议把浏览器连接层、搜索模块、股东解析、任职抓取、数据输出拆成独立模块,参数全部走config.toml,这样网站改版时只改常量,不用动主逻辑。
模型后处理这块,API Key 在控制台创建:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite,接入细节看文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。如果你用的是 Claude Code 这类编码工具来维护这套脚本,Anthropic 兼容入口在https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=ClaudeCodeAnthropic&utm_campaign=rewrite,长期跑 Agent 任务可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite。
最后提醒一句:采集频率一定要加随机延时,单账号高频请求会触发滑块甚至封号。这套代码的定位是合法尽调场景下的效率工具,别拿去做恶意爬取。页面 DOM 和接口会不定期改版,解析失效时按「常量抽离」的思路快速适配就行。