2026-02-18 14:41:16 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
本地脚本:打开比特浏览器,并由 DrissionPage (DP) 接管控制。
|
|
|
|
|
|
运行前请确保:1) 比特浏览器客户端已启动(默认 API 端口 54345);2) 已安装 DrissionPage。
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import random
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import time
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
from worker.bit_browser import BitBrowserAPI
|
|
|
|
|
|
|
|
|
|
|
|
# 保证从项目根目录运行时可导入 worker 包
|
|
|
|
|
|
_ROOT = Path(__file__).resolve().parent
|
|
|
|
|
|
if str(_ROOT) not in sys.path:
|
|
|
|
|
|
sys.path.insert(0, str(_ROOT))
|
|
|
|
|
|
|
|
|
|
|
|
# 比特浏览器 API 地址(与 worker 默认一致)
|
|
|
|
|
|
BIT_API_BASE = "http://127.0.0.1:54345"
|
|
|
|
|
|
# 要打开的浏览器:None=第一个环境,或指定环境名称/备注
|
2026-03-05 00:16:10 +08:00
|
|
|
|
BROWSER_NAME = "测试2" # 例如 "第一个" 或 "主账号"
|
2026-02-18 14:41:16 +08:00
|
|
|
|
BROWSER_ID = None # 若已知窗口 ID,可直接填
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-06 01:33:11 +08:00
|
|
|
|
def do_recommend_filter(page):
|
|
|
|
|
|
"""
|
|
|
|
|
|
在推荐牛人页面:点开右上角筛选 -> 弹窗内完成筛选操作 -> 点击确认。
|
|
|
|
|
|
筛选 UI 在 iframe recommendFrame 内(推荐牛人 / recommend-v2)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
time.sleep(1.5) # 等待 iframe 加载
|
|
|
|
|
|
# 获取推荐牛人 iframe(name=recommendFrame)
|
|
|
|
|
|
frame = page.get_frame("@name=recommendFrame")
|
|
|
|
|
|
if not frame:
|
|
|
|
|
|
frame = page.get_frame(1) # fallback:第一个 iframe
|
|
|
|
|
|
if not frame:
|
|
|
|
|
|
print("未找到推荐牛人 iframe,跳过筛选")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 点击右上角「筛选」,打开弹窗
|
|
|
|
|
|
filter_btn = frame.ele("x://span[text()='筛选']", timeout=3) or frame.ele(
|
|
|
|
|
|
"x://*[contains(text(),'筛选')]", timeout=2
|
|
|
|
|
|
)
|
|
|
|
|
|
if not filter_btn:
|
|
|
|
|
|
print("未找到「筛选」按钮,跳过筛选")
|
|
|
|
|
|
return
|
|
|
|
|
|
filter_btn.click(by_js=True)
|
|
|
|
|
|
time.sleep(0.8)
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 弹窗内完成筛选(按需在此增加具体筛选项点击,例如学历、经验、期望职位等)
|
|
|
|
|
|
# 若无需改任何条件,可保持默认,直接点确认即可。
|
|
|
|
|
|
|
|
|
|
|
|
# 3. 点击弹窗中的「确认」
|
|
|
|
|
|
confirm = (
|
|
|
|
|
|
frame.ele("x://span[text()='确认']", timeout=3)
|
|
|
|
|
|
or frame.ele("x://span[text()='确定']", timeout=2)
|
|
|
|
|
|
or frame.ele("x://*[contains(text(),'确认')]", timeout=2)
|
|
|
|
|
|
)
|
|
|
|
|
|
if not confirm:
|
|
|
|
|
|
print("未找到「确认」按钮")
|
|
|
|
|
|
return
|
|
|
|
|
|
confirm.click(by_js=True)
|
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
|
print("推荐牛人筛选已确认")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-18 14:41:16 +08:00
|
|
|
|
def main():
|
|
|
|
|
|
from DrissionPage import ChromiumPage, ChromiumOptions
|
|
|
|
|
|
from DrissionPage.errors import NoRectError
|
|
|
|
|
|
|
|
|
|
|
|
print("正在连接比特浏览器 API...")
|
|
|
|
|
|
bit_api = BitBrowserAPI(BIT_API_BASE)
|
|
|
|
|
|
|
|
|
|
|
|
# 打开指定或第一个浏览器窗口
|
|
|
|
|
|
print("正在打开比特浏览器...")
|
|
|
|
|
|
cdp_addr, port, browser_id = bit_api.open_browser(
|
|
|
|
|
|
browser_id=BROWSER_ID,
|
|
|
|
|
|
name=BROWSER_NAME,
|
|
|
|
|
|
remark=None,
|
|
|
|
|
|
)
|
|
|
|
|
|
print(f"已打开浏览器 ID={browser_id}, CDP 端口={port}")
|
|
|
|
|
|
|
|
|
|
|
|
# DrissionPage 接管:先创建选项实例,再设置本地端口
|
|
|
|
|
|
co = ChromiumOptions().set_local_port(port=port)
|
|
|
|
|
|
page = ChromiumPage(addr_or_opts=co)
|
|
|
|
|
|
|
|
|
|
|
|
page.listen.start('wapi/zprelation/friend/getBossFriendListV2')
|
|
|
|
|
|
|
2026-03-06 01:33:11 +08:00
|
|
|
|
# 打开推荐牛人/聊天页
|
2026-02-18 14:41:16 +08:00
|
|
|
|
page.get("https://www.zhipin.com/web/chat/index")
|
|
|
|
|
|
res = page.listen.wait()
|
|
|
|
|
|
|
2026-03-06 01:33:11 +08:00
|
|
|
|
# 执行推荐牛人筛选:点开右上角筛选 -> 弹窗内完成筛选 -> 点击确认
|
|
|
|
|
|
do_recommend_filter(page)
|
|
|
|
|
|
|
2026-02-18 14:41:16 +08:00
|
|
|
|
for i in res.response.body["zpData"]["friendList"]:
|
|
|
|
|
|
print(i)
|
|
|
|
|
|
|
|
|
|
|
|
name = i["name"]
|
|
|
|
|
|
job_name = i["jobName"]
|
|
|
|
|
|
jobId = i["jobId"]
|
2026-03-05 00:16:10 +08:00
|
|
|
|
lastTime = i["lastTime"] # 最后一次上线
|
2026-02-18 14:41:16 +08:00
|
|
|
|
|
|
|
|
|
|
page.listen.start('wapi/zpchat/boss/historyMsg')
|
|
|
|
|
|
|
|
|
|
|
|
# 左侧联系人列表可能是虚拟滚动,直接用 actions.scroll 会 NoRectError;先定位再 JS scrollIntoView,再点击
|
|
|
|
|
|
name_sel = f'x://span[text()="{i["name"]}"]'
|
|
|
|
|
|
ele = page.ele(name_sel, timeout=2)
|
|
|
|
|
|
if not ele:
|
|
|
|
|
|
print(f" 跳过:未找到联系人 {i['name']}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
ele.run_js("this.scrollIntoView({block: 'center', behavior: 'auto'})")
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
time.sleep(0.8)
|
|
|
|
|
|
try:
|
|
|
|
|
|
page.ele(name_sel, timeout=2).click(by_js=True)
|
|
|
|
|
|
except (NoRectError, Exception) as e:
|
|
|
|
|
|
print(f" 跳过:点击 {i['name']} 失败: {e}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
res = page.listen.wait()
|
|
|
|
|
|
|
|
|
|
|
|
for i in res.response.body["zpData"]["messages"]:
|
|
|
|
|
|
print(i["body"])
|
|
|
|
|
|
body = i.get("body") or {}
|
2026-03-05 00:16:10 +08:00
|
|
|
|
|
2026-03-05 00:27:59 +08:00
|
|
|
|
try:
|
|
|
|
|
|
age = body["resume"]["age"] # 年龄
|
|
|
|
|
|
education = body["resume"]["education"] # 大专
|
|
|
|
|
|
position = body["resume"]["position"] # 期望职位
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
2026-03-05 00:16:10 +08:00
|
|
|
|
|
2026-02-18 14:41:16 +08:00
|
|
|
|
text = body.get("text") if body else None
|
|
|
|
|
|
if text and "手机号" in text:
|
|
|
|
|
|
break
|
|
|
|
|
|
if text and "微信号" in text:
|
|
|
|
|
|
break
|
|
|
|
|
|
else:
|
|
|
|
|
|
page.ele('x://*[@id="boss-chat-editor-input"]').input(
|
|
|
|
|
|
"后续沟通会更及时,您方便留一下您的微信号吗?我这边加您。")
|
|
|
|
|
|
time.sleep(random.randint(1, 3) + random.random())
|
|
|
|
|
|
page.ele('x://div[text()="发送"]').click()
|
|
|
|
|
|
time.sleep(random.randint(1, 5) + random.random())
|
|
|
|
|
|
page.ele('x://span[text()="换微信"]').click()
|
|
|
|
|
|
time.sleep(random.randint(1, 2) + random.random())
|
|
|
|
|
|
# 只点「交换微信」弹窗里的确定:先定位到“确定与对方交换微信吗?”所在 tooltip,再点其下的确定按钮,避免点到交换简历的确定
|
2026-02-18 17:21:23 +08:00
|
|
|
|
page.ele(
|
|
|
|
|
|
'x://span[contains(text(),"确定与对方交换微信吗?")]/../div[@class="btn-box"]/span[contains(@class,"boss-btn-primary")]').click(
|
|
|
|
|
|
by_js=True)
|
2026-02-18 14:41:16 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|