# -*- coding: utf-8 -*- """ 本地脚本:由 DrissionPage (DP) 控制浏览器(本地谷歌 Chrome 或比特浏览器)。 - 使用本地谷歌:USE_LOCAL_CHROME = True,会启动/连接本机 Chrome。 - 使用比特浏览器:USE_LOCAL_CHROME = False,需先启动比特浏览器客户端(API 端口 54345)。 """ from __future__ import annotations import random import sys import time from pathlib import Path # 保证从项目根目录运行时可导入 worker 包 _ROOT = Path(__file__).resolve().parent if str(_ROOT) not in sys.path: sys.path.insert(0, str(_ROOT)) # ---------- 选择控制对象 ---------- USE_LOCAL_CHROME = True # True=本地谷歌 Chrome,False=比特浏览器 # 本地谷歌 Chrome 配置(仅当 USE_LOCAL_CHROME=True 时生效) CHROME_DEBUG_PORT = 9222 # 调试端口;若为 None 则由脚本自动启动 Chrome CHROME_PATH = None # 例如 r"C:\Program Files\Google\Chrome\Application\chrome.exe",None 用系统默认 # 比特浏览器配置(仅当 USE_LOCAL_CHROME=False 时生效) BIT_API_BASE = "http://127.0.0.1:54345" BROWSER_NAME = "测试2" BROWSER_ID = None def _connect_local_chrome(): """连接或启动本地谷歌 Chrome,返回 ChromiumPage。""" from DrissionPage import ChromiumPage, ChromiumOptions co = ChromiumOptions() if CHROME_PATH: co.set_browser_path(CHROME_PATH) if CHROME_DEBUG_PORT is not None: # 连接已开启调试端口的 Chrome(需先手动启动:chrome --remote-debugging-port=9222) co.set_local_port(CHROME_DEBUG_PORT) print(f"正在连接本机 Chrome(调试端口 {CHROME_DEBUG_PORT})...") page = ChromiumPage(addr_or_opts=co) else: # 由 DrissionPage 自动启动 Chrome print("正在启动本地谷歌 Chrome...") page = ChromiumPage(addr_or_opts=co) print("已连接本地 Chrome。") return page def _connect_bit_browser(): """通过比特浏览器 API 打开并连接,返回 ChromiumPage。""" from worker.bit_browser import BitBrowserAPI from DrissionPage import ChromiumPage, ChromiumOptions 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}") co = ChromiumOptions().set_local_port(port=port) return ChromiumPage(addr_or_opts=co) def main(filters): from DrissionPage.errors import NoRectError if USE_LOCAL_CHROME: page = _connect_local_chrome() else: page = _connect_bit_browser() # 先启动监听再执行动作,否则拿不到数据包(见 DP 文档) page.listen.start('wapi/zpjob/rec/geek/list') if filters: main2(page, filters) # 有筛选时:page.get 会触发第 1 个 geek/list,点「确定」触发第 2 个;取最后一个才是筛选后的结果 packets = page.listen.wait(count=2, timeout=30) res = packets[-1] if packets else None else: page.get("https://www.zhipin.com/web/chat/recommend") res = page.listen.wait(timeout=30) if not res: raise RuntimeError("未捕获到目标请求 wapi/zpjob/rec/geek/list") for i in res.response.body["zpData"]["geekList"]: print(i) geekName = i["geekCard"]["geekName"] # 姓名 geekDegree = i["geekCard"]["geekDegree"] # 学历 expectPositionName = i["geekCard"]["expectPositionName"] # 期待职位 expectLocationName = i["geekCard"]["expectLocationName"] # 地区 applyStatusDesc = i["geekCard"]["applyStatusDesc"] # 当前状态离职0随时到岗之类的 ageDesc = i["geekCard"]["ageDesc"] # 年龄 lowSalary = i["geekCard"]["lowSalary"] # 最低要求工资 highSalary = i["geekCard"]["highSalary"] # 最高要求工资 """三个动作:1. 找到姓名 2. 滚动到那里 3. 点击打招呼""" try: container = page.get_frame("recommendFrame") except Exception: container = page # 1. 找到这个姓名 name_ele = container.ele(f'x://span[contains(text(),"{geekName}")]', timeout=5) if not name_ele: name_ele = container.ele(f'x://span[text()="{geekName}"]', timeout=2) if not name_ele: raise Exception(f"未找到姓名:{geekName}") # 2. 滚动到那里 name_ele.run_js("this.scrollIntoView({block: 'center', behavior: 'auto'})") time.sleep(0.8) # 3. 点击打招呼(先在该人所在卡片内找) parent = name_ele.parent() greet_btn = None for _ in range(8): if not parent: break greet_btn = parent.ele('x://span[text()="打招呼"]', timeout=0.5) or parent.ele( 'x://*[contains(text(),"打招呼")]', timeout=0.5) if greet_btn: break parent = parent.parent() if not greet_btn: greet_btn = container.ele('x://span[text()="打招呼"]', timeout=2) or container.ele( 'x://*[contains(text(),"打招呼")]', timeout=1) if not greet_btn: raise Exception("未找到「打招呼」按钮") greet_btn.click(by_js=True) def main1(): if USE_LOCAL_CHROME: page = _connect_local_chrome() else: page = _connect_bit_browser() page.listen.start('wapi/zpblock/recommend/filters') # 示例:打开一个页面(可选) page.get("https://www.zhipin.com/web/chat/recommend") res = page.listen.wait() filters = {} for i in res.response.body["zpData"]["vipFilter"]["filters"]: print(i) if i["name"] == "年龄": print(i["start"]) print(i["end"]) filters[i["name"]] = range(int(i["start"]), int(i["end"]) + 1) else: datas = [] for i1 in i["options"]: print(i1["name"]) datas.append(i1["name"]) filters[i["name"]] = datas print(filters) def main2(page, filters): """在同一 page 上打开推荐页、点筛选、选条件、点确定;点击确定后会触发 wapi/zpjob/rec/geek/list,由 main() 的 listen 捕获。""" page.get("https://www.zhipin.com/web/chat/recommend") page.ele("x://*[contains(text(),'筛选')]").click() time.sleep(3) for i in filters: page.ele(f"x://*[contains(text(),'{i}')]").click() time.sleep(random.random() * 2) page.ele("x://*[contains(text(),'确定')]").click() if __name__ == "__main__": main(filters=["初中及以下", '离职-随时到岗']) # main1() # main2()