news 2026/5/2 12:51:28

基于ChatGPT与Playwright的智能简历投递自动化系统构建指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于ChatGPT与Playwright的智能简历投递自动化系统构建指南

1. 项目概述:当简历投递遇上AI自动化

如果你也经历过海投简历的疲惫,每天对着几十个招聘网站重复填写个人信息、上传PDF、复制粘贴个人介绍,那么“koushik4/Resume-Automation-using-ChatGPT”这个项目标题,很可能瞬间就抓住了你的眼球。这不仅仅是一个简单的脚本工具,它背后代表了一种全新的求职思路:将繁琐、重复、低效的简历投递过程,交给AI和自动化流程来处理,从而解放求职者的时间和精力,让他们能更专注于准备面试和提升技能本身。

这个项目的核心,是利用ChatGPT的智能内容生成与优化能力,结合自动化脚本(如Python的Selenium或Playwright),实现简历信息的智能填充、岗位描述的针对性匹配,以及一键式多平台投递。简单来说,它试图解决求职者面临的两个核心痛点:一是海投的效率问题,二是简历与岗位的匹配度问题。想象一下,你只需要维护一份“主简历”和几个关键变量(如目标职位、技能侧重),AI就能根据不同的岗位描述,自动生成数十份侧重点各不相同的定制化简历和求职信,并自动完成投递表单的填写。这听起来像是未来,但通过这个开源项目,我们已经可以亲手搭建这样一个“求职助手”。

它适合谁呢?首先是正在积极求职的软件工程师、数据分析师、产品经理等科技行业从业者,他们面对的招聘平台(如LinkedIn, Indeed, Glassdoor)表单结构相对标准,自动化可行性高。其次,它也适合任何希望系统性提升求职效率的人,哪怕你只是用它来批量生成不同版本的求职信,其价值也已经远超手动操作。当然,你需要具备基础的Python环境搭建和命令行操作知识,但这正是本篇文章要带你一步步攻克的目标。接下来,我将为你彻底拆解这个项目的实现逻辑、技术细节,并分享从零搭建到实际投递的全过程,以及我踩过的那些坑和最终验证有效的技巧。

2. 项目整体设计与核心思路拆解

2.1 核心需求与解决方案映射

这个项目的目标非常明确:自动化、个性化地投递简历。但“自动化投递”本身可以拆解为几个更具体的子任务,每个子任务都需要选择合适的技术方案。

子任务一:信息提取与解析

  • 需求:从招聘网站获取岗位描述(Job Description, JD)、公司信息、申请表单字段。
  • 传统方案:手动阅读、复制粘贴。效率极低,且无法结构化。
  • 本项目方案:使用Web自动化工具(如Selenium)模拟浏览器访问,定位并抓取页面上的特定文本和HTML元素。这里的关键在于如何稳定地定位元素,因为不同网站的页面结构差异巨大。通常需要结合CSS选择器、XPath,并处理动态加载的内容(可能需要等待特定元素出现)。

子任务二:内容生成与适配

  • 需求:根据抓取到的JD,动态调整简历中的“个人总结”、“项目经验描述”、“技能列表”等部分,使其更贴合岗位要求。
  • 传统方案:求职者手动为每个岗位重写相关内容。耗时耗力,且难以保证质量。
  • 本项目方案:调用ChatGPT(或类似大语言模型API)。将你的“主简历”和抓取到的JD一起作为提示词(Prompt)输入,要求模型生成一个针对该JD优化的简历版本或求职信。这是项目的“智能”核心。例如,一个提示词可能是:“以下是我的简历背景:[你的简历文本]。请根据以下职位描述:[JD文本],重写我的‘工作经验’部分,突出与‘云计算’和‘微服务’相关的技能。”

子任务三:表单自动填充与提交

  • 需求:将生成的个性化内容,以及你的固定信息(姓名、邮箱、电话等),自动填写到招聘网站的申请表单中,并提交。
  • 传统方案:手动填写数十个字段。
  • 本项目方案:再次利用Web自动化工具。脚本需要能识别表单中的各个输入框(input)、下拉菜单(select)、文件上传按钮(input[type=“file”]),并模拟键盘输入或直接设置元素值。最大的挑战在于网站的反爬虫和反自动化机制,如验证码、非常规的输入组件等。

子任务四:流程管理与状态跟踪

  • 需求:管理要投递的岗位列表,记录投递状态(已申请、失败、需人工干预),避免重复投递。
  • 本项目方案:通常使用一个简单的数据库(如SQLite)或CSV文件来维护一个“任务队列”。每条记录包含岗位URL、公司、职位、抓取状态、申请状态、生成的内容快照等。

2.2 技术栈选型背后的逻辑

原项目使用了Python,这是一个非常合理的选择。我们来分析一下每个组件选型的理由:

  1. Python:生态丰富,在自动化、网络爬虫、AI集成方面有海量成熟的库(Selenium, Playwright, Requests, BeautifulSoup, OpenAI SDK),语法简洁,开发效率高。
  2. Selenium / Playwright:两者都是浏览器自动化利器。
    • Selenium:老牌、稳定、社区庞大,兼容性最好。但需要额外下载浏览器驱动(如ChromeDriver),且对现代Web应用(大量异步加载)的支持有时需要更复杂的等待逻辑。
    • Playwright:后起之秀,由微软开发。它内置了Chromium、Firefox和WebKit的驱动,开箱即用。其API设计更现代,自动等待机制更智能,录制生成代码的功能对新手极其友好。在今天的实践中,我个人更倾向于推荐Playwright,因为它能显著减少在“等待页面加载”这类问题上的调试时间。
  3. OpenAI API (ChatGPT):提供最直接、强大的文本生成与理解能力。你需要一个OpenAI的API密钥。替代方案可以是开源的本地大模型(如通过Ollama部署Llama 3),但生成质量和可控性通常需要更多调优,且对本地算力有要求。对于简历优化这种对质量要求较高的任务,初期使用ChatGPT API是性价比最高的选择。
  4. SQLite:轻量级、文件式数据库,无需安装独立服务。非常适合这种单机、个人使用的场景,用来存储岗位信息和申请记录绰绰有余。
  5. 配置文件(如config.yaml/json):用来管理你的个人信息、API密钥、目标网站列表、提示词模板等。绝对不要将这些敏感信息硬编码在脚本里。

注意:使用自动化脚本投递简历必须遵守招聘网站的服务条款(Terms of Service)。许多网站明确禁止自动化提交。因此,本项目更适用于学习和研究目的,或在确认不违反规定、且控制请求频率(如模拟真人操作速度,加入随机延迟)的情况下谨慎使用。在实际求职中,它最适合用于那些允许批量申请或申请流程极其标准的平台,或者作为你的“智能简历生成器”,生成内容后由你本人去提交。

3. 核心模块解析与实操要点

3.1 环境搭建与依赖管理

工欲善其事,必先利其器。一个清晰、可复现的环境是项目成功的第一步。

步骤1:创建独立的Python虚拟环境这是为了避免项目依赖污染你的系统Python环境,也便于后续部署。

# 在项目目录下 python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate

步骤2:管理项目依赖创建一个requirements.txt文件,列出所有需要的包。这是比原项目可能更规范的做法。

playwright>=1.40.0 openai>=1.3.0 python-dotenv>=1.0.0 pandas>=2.0.0 sqlalchemy>=2.0.0 pyyaml>=6.0

然后安装:

pip install -r requirements.txt

对于Playwright,还需要安装浏览器内核:

playwright install chromium

步骤3:配置文件与密钥管理创建.env文件(并加入.gitignore!)来存储敏感信息:

OPENAI_API_KEY=sk-your-actual-api-key-here LINKEDIN_EMAIL=your.email@example.com LINKEDIN_PASSWORD=your_password # 注意:明文存储密码风险极高,建议使用会话缓存或更安全的方式,此处仅为示例。

创建config.yaml存储非敏感配置:

resume: master_file: "./data/master_resume.md" output_dir: "./data/generated_resumes/" openai: model: "gpt-4-turbo-preview" # 或 "gpt-3.5-turbo" 以节省成本 temperature: 0.7 # 控制创造性,简历生成建议0.5-0.8 websites: linkedin: login_url: "https://www.linkedin.com/login" job_search_url: "https://www.linkedin.com/jobs/search/" indeed: base_url: "https://www.indeed.com" automation: headless: false # 开发时设为false便于调试 slow_mo: 100 # 每个操作延迟100毫秒,模拟真人速度 timeout: 30000 # 页面加载超时时间

实操心得

  • 虚拟环境是必须的:尤其是当你同时进行多个Python项目时。
  • 永远不要提交密钥.env文件必须被.gitignore忽略。可以在项目中提供一个.env.example文件,列出需要的环境变量名,供他人参考。
  • Playwright over Selenium:在新项目中,除非有特殊兼容性要求,否则直接选择Playwright。它的page.wait_for_selectorpage.wait_for_load_state比Selenium的显式等待(WebDriverWait)更简洁可靠。

3.2 智能内容生成模块深度剖析

这是项目的“大脑”。其核心是一个精心设计的提示词工程(Prompt Engineering)流程。

基本流程

  1. 输入:你的主简历(Markdown或纯文本格式)、目标岗位描述(JD)。
  2. 处理:将两者拼接,加上指令,形成完整的提示词,发送给ChatGPT API。
  3. 输出:模型返回优化后的简历文本或求职信。

一个进阶的提示词模板示例

你是一位专业的简历优化专家。请根据以下求职者的原始简历和招聘职位描述,完成以下任务: 【任务一:提取关键需求】 从提供的职位描述中,提取出3-5个最核心的技能要求和岗位职责关键词。 【任务二:针对性优化】 基于提取出的关键词,优化求职者的“工作经验”部分。针对每一条工作经历: 1. 使用STAR原则(情境、任务、行动、结果)进行重述。 2. 在描述中自然地融入关键词。 3. 量化成果,尽可能使用数字(如“提升效率30%”、“管理10人团队”)。 【任务三:生成求职信草稿】 撰写一段简短的求职信开头段落(约150字),表达对该特定职位的兴趣,并概括性地说明你的哪方面经验与职位要求最为匹配。 【原始简历】 {master_resume_text} 【招聘职位描述】 {job_description_text} 请以JSON格式回复,包含以下字段: - “keywords”: [提取出的关键词列表], - “optimized_experience”: “优化后的工作经验部分文本”, - “cover_letter_intro”: “求职信开头段落”

为什么这样设计?

  • 角色设定:让AI进入“专家”角色,提高输出质量。
  • 分步任务:引导模型进行结构化思考,比直接说“优化一下简历”效果更好。
  • STAR原则和量化:这是简历撰写的黄金法则,在提示词中明确提出要求,能显著提升生成内容的专业性。
  • JSON格式输出:便于后续代码解析和处理,可以直接存入数据库或文件。

代码实现要点

import openai from dotenv import load_dotenv import yaml import json import os load_dotenv() class ResumeOptimizer: def __init__(self, config_path='./config.yaml'): with open(config_path, 'r') as f: self.config = yaml.safe_load(f) self.client = openai.OpenAI(api_key=os.getenv('OPENAI_API_KEY')) self.model = self.config['openai']['model'] def load_master_resume(self): path = self.config['resume']['master_file'] with open(path, 'r', encoding='utf-8') as f: return f.read() def optimize_for_job(self, job_description): master_resume = self.load_master_resume() prompt = self._build_prompt(master_resume, job_description) try: response = self.client.chat.completions.create( model=self.model, messages=[{"role": "user", "content": prompt}], temperature=self.config['openai']['temperature'], response_format={ "type": "json_object" } # 强制JSON输出 ) content = response.choices[0].message.content return json.loads(content) # 解析为字典 except openai.APIError as e: print(f"OpenAI API调用失败: {e}") return None except json.JSONDecodeError as e: print(f"解析AI返回的JSON失败: {e}") return None def _build_prompt(self, resume, jd): # 这里填入上面设计的提示词模板,使用f-string填充{master_resume_text}和{job_description_text} prompt_template = """ ... (提示词内容如上) ... """ return prompt_template.format(master_resume_text=resume, job_description_text=jd) def save_optimized_resume(self, job_id, optimized_data): output_dir = self.config['resume']['output_dir'] os.makedirs(output_dir, exist_ok=True) filename = f"{output_dir}/resume_{job_id}.md" # 将优化后的内容,结合主简历的其他部分,生成完整的Markdown文件 full_content = f"# 优化简历 (Job ID: {job_id})\n\n**匹配关键词:** {', '.join(optimized_data['keywords'])}\n\n## 工作经验\n{optimized_data['optimized_experience']}\n\n## 求职信开头\n{optimized_data['cover_letter_intro']}" with open(filename, 'w', encoding='utf-8') as f: f.write(full_content) print(f"已保存优化简历至: {filename}")

注意事项

  • API成本:GPT-4比GPT-3.5-Turbo贵很多。在大量测试和生成时,可以先使用GPT-3.5,定稿前再用GPT-4进行最终润色。
  • 内容审核:生成的简历内容必须由你本人仔细审核。AI可能会“幻觉”出你并不具备的技能或经历。它只是辅助工具,你才是最终的责任人。
  • 提示词迭代:不要指望一次就写出完美的提示词。根据生成结果反复调整你的提示词,是获得高质量输出的关键。可以保存不同的提示词模板,用于不同风格的岗位(如技术型、管理型、创意型)。

3.3 自动化投递引擎构建细节

这是项目的“双手”,负责与招聘网站交互。我们以Playwright为例,讲解一个简化的LinkedIn申请流程。

核心类结构

from playwright.sync_api import sync_playwright import time import random class JobAutoApplicator: def __init__(self, config): self.config = config self.browser = None self.context = None self.page = None def start(self): """启动浏览器和上下文""" p = sync_playwright().start() self.browser = p.chromium.launch( headless=self.config['automation']['headless'], slow_mo=self.config['automation']['slow_mo'] ) # 建议使用持久化上下文来保存登录状态,避免每次登录 self.context = self.browser.new_context( viewport={'width': 1920, 'height': 1080}, user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...' ) self.page = self.context.new_page() self.page.set_default_timeout(self.config['automation']['timeout']) def login_to_linkedin(self, email, password): """登录LinkedIn(示例,实际网站可能经常改版)""" self.page.goto(self.config['websites']['linkedin']['login_url']) # 等待页面加载 self.page.wait_for_load_state('networkidle') # 定位并填写邮箱 email_input = self.page.locator('input#username') # ID选择器 email_input.fill(email) # 定位并填写密码 password_input = self.page.locator('input#password') password_input.fill(password) # 点击登录按钮 login_button = self.page.locator('button[type="submit"]') login_button.click() # 等待登录成功,通常跳转到首页或验证 self.page.wait_for_url('**/feed/**', timeout=60000) # 等待跳转到Feed页 print("登录成功") time.sleep(random.uniform(2, 5)) # 随机延迟 def search_and_apply(self, job_title, location, optimized_resume_text): """搜索职位并尝试申请(高度简化版)""" search_url = f"{self.config['websites']['linkedin']['job_search_url']}?keywords={job_title}&location={location}" self.page.goto(search_url) # 等待职位列表加载 self.page.wait_for_selector('div.job-card-container', timeout=15000) # 获取第一页的职位链接(实际应分页或滚动加载) job_cards = self.page.locator('div.job-card-container a.job-card-container__link') job_links = [] for i in range(job_cards.count()): href = job_cards.nth(i).get_attribute('href') if href and '/jobs/view/' in href: full_url = f"https://www.linkedin.com{href}" if href.startswith('/') else href job_links.append(full_url) for job_url in job_links[:3]: # 仅测试前3个 self._apply_to_single_job(job_url, optimized_resume_text) time.sleep(random.uniform(10, 20)) # 每个申请间隔较长,模拟真人 def _apply_to_single_job(self, job_url, resume_text): """申请单个职位""" print(f"正在处理职位: {job_url}") self.page.goto(job_url) try: # 1. 点击“快速申请”或“申请”按钮 easy_apply_button = self.page.locator('button:has-text("Easy Apply"), button:has-text("申请")').first easy_apply_button.wait_for(state='visible', timeout=10000) easy_apply_button.click() time.sleep(random.uniform(1, 3)) # 2. 处理可能出现的多页表单 # 这里是一个极其简化的示例,真实表单非常复杂 modal = self.page.locator('div.easy-apply-modal') # 模态框选择器 # 假设第一个页面是联系信息,已保存 # 点击“下一步” next_button = modal.locator('button:has-text("Next"), button:has-text("下一步")') if next_button.is_visible(): next_button.click() time.sleep(1) # 3. 在简历相关问题页面,填入AI生成的简历文本 # 寻找文本框或可编辑区域 resume_field = modal.locator('textarea, div[contenteditable="true"]').first if resume_field.is_visible(): resume_field.fill(resume_text) # 填入优化后的简历摘要 print(" 已填入优化简历内容") time.sleep(random.uniform(1, 2)) # 4. 继续下一步,直到提交 submit_button = modal.locator('button:has-text("Submit application"), button:has-text("提交申请")') if submit_button.is_visible(): # 在实际使用中,这里应该有一个确认步骤,而不是直接提交 # submit_button.click() print(" 模拟:找到提交按钮(为防止误操作,已注释点击代码)") # 关闭模态框 self.page.keyboard.press('Escape') else: print(" 未找到提交按钮,可能表单复杂或需要上传文件") except Exception as e: print(f" 申请过程中出现错误: {e}") # 通常应该截图保存错误现场 self.page.screenshot(path=f'./error_screenshots/error_{int(time.time())}.png') finally: # 确保回到职位页面或列表 time.sleep(2) def close(self): """关闭浏览器""" if self.context: self.context.close() if self.browser: self.browser.close()

关键难点与应对策略

  1. 元素定位不稳定:网站经常改版,CSS选择器或XPath可能失效。
    • 策略:使用更稳定的定位策略,如通过>-- jobs.db CREATE TABLE IF NOT EXISTS job_listings ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, company TEXT, location TEXT, description TEXT, source TEXT, -- e.g., 'linkedin', 'indeed' found_date DATETIME DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT 'pending', -- 'pending', 'processing', 'applied', 'failed', 'skipped' applied_date DATETIME, optimized_resume_path TEXT, cover_letter_path TEXT, notes TEXT -- 记录错误信息或特殊情况 );

      核心管理类

      import sqlite3 from contextlib import contextmanager from datetime import datetime class JobManager: def __init__(self, db_path='./data/jobs.db'): self.db_path = db_path self._init_db() def _init_db(self): with self._get_connection() as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS job_listings ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, company TEXT, location TEXT, description TEXT, source TEXT, found_date DATETIME DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT 'pending', applied_date DATETIME, optimized_resume_path TEXT, cover_letter_path TEXT, notes TEXT ) """) @contextmanager def _get_connection(self): conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row # 返回字典样式的行 try: yield conn conn.commit() except Exception as e: conn.rollback() raise e finally: conn.close() def add_job(self, url, title, company, location, description, source): """添加一个新职位到待处理列表""" with self._get_connection() as conn: try: conn.execute(""" INSERT OR IGNORE INTO job_listings (url, title, company, location, description, source) VALUES (?, ?, ?, ?, ?, ?) """, (url, title, company, location, description, source)) return conn.lastrowid except sqlite3.IntegrityError: print(f"职位已存在: {url}") return None def get_pending_jobs(self, limit=10): """获取一批待处理的职位""" with self._get_connection() as conn: cursor = conn.execute(""" SELECT * FROM job_listings WHERE status = 'pending' ORDER BY found_date LIMIT ? """, (limit,)) return [dict(row) for row in cursor.fetchall()] def update_job_status(self, job_id, status, optimized_path=None, notes=None): """更新职位状态""" with self._get_connection() as conn: update_data = {'status': status, 'notes': notes} if status == 'applied': update_data['applied_date'] = datetime.now() if optimized_path: update_data['optimized_resume_path'] = optimized_path set_clause = ', '.join([f"{k}=?" for k in update_data.keys()]) values = list(update_data.values()) + [job_id] conn.execute(f""" UPDATE job_listings SET {set_clause} WHERE id=? """, values) def get_statistics(self): """获取申请统计""" with self._get_connection() as conn: cursor = conn.execute(""" SELECT COUNT(*) as total, SUM(CASE WHEN status='applied' THEN 1 ELSE 0 END) as applied, SUM(CASE WHEN status='failed' THEN 1 ELSE 0 END) as failed, SUM(CASE WHEN status='pending' THEN 1 ELSE 0 END) as pending FROM job_listings """) return dict(cursor.fetchone())

      主程序流程

      def main(): # 1. 初始化管理器 config = load_config() job_manager = JobManager() optimizer = ResumeOptimizer(config) applicator = JobAutoApplicator(config) # 2. 启动浏览器并登录(仅需一次) applicator.start() applicator.login_to_linkedin(os.getenv('LINKEDIN_EMAIL'), os.getenv('LINKEDIN_PASSWORD')) # 3. 获取一批待处理职位(这里假设职位已通过其他方式,如爬虫,添加到数据库) pending_jobs = job_manager.get_pending_jobs(limit=5) for job in pending_jobs: print(f"处理职位: {job['title']} at {job['company']}") job_manager.update_job_status(job['id'], 'processing') try: # 4. 使用AI优化简历 optimized_data = optimizer.optimize_for_job(job['description']) if not optimized_data: job_manager.update_job_status(job['id'], 'failed', notes='AI优化失败') continue # 5. 保存优化后的简历 resume_path = optimizer.save_optimized_resume(job['id'], optimized_data) # 6. 执行自动化申请(使用优化后的简历文本摘要) applicator.search_and_apply( job_title=job['title'], location=job['location'], optimized_resume_text=optimized_data['optimized_experience'][:500] # 取前500字符填入 ) # 7. 更新状态为成功(这里简化了,实际应根据applicator的返回结果) job_manager.update_job_status(job['id'], 'applied', optimized_path=resume_path) print(f" 成功申请: {job['title']}") except Exception as e: print(f" 处理失败: {e}") job_manager.update_job_status(job['id'], 'failed', notes=str(e)) # 8. 处理间隔 time.sleep(random.uniform(30, 60)) # 长时间间隔 # 9. 打印统计信息 stats = job_manager.get_statistics() print(f"\n申请统计: 总计 {stats['total']}, 成功 {stats['applied']}, 失败 {stats['failed']}, 待处理 {stats['pending']}") # 10. 关闭 applicator.close() if __name__ == '__main__': main()

      4. 常见问题与排查技巧实录

      在实际搭建和运行过程中,你会遇到各种各样的问题。下面是我在多次实践中总结出的典型问题及其解决方案。

      4.1 AI内容生成相关

      问题1:AI生成的简历内容空洞、重复或偏离重点。

      • 排查:检查你的提示词。是否过于笼统?是否提供了足够具体和高质量的“主简历”?
      • 解决
        1. 强化提示词:在提示词中明确要求“使用具体数据量化成果”、“遵循STAR原则”、“避免使用‘负责’、‘参与’等模糊词汇”。
        2. 提供范例:在提示词中给出一两个你满意的、修改前和修改后的例子,让AI学习你的偏好。
        3. 分步生成:不要一次性要求AI重写整个简历。可以先让它提取JD关键词,然后针对“项目经验”部分优化,再优化“技能总结”部分。
        4. 调整温度(Temperature):降低temperature(如从0.8调到0.4)会让输出更稳定、更可预测,但可能缺乏变化;调高则会更有创造性,但可能跑偏。需要根据输出效果调整。

      问题2:API调用超时或返回非JSON格式。

      • 排查:网络问题、API密钥额度不足、或提示词导致模型输出格式不符合response_format={ "type": "json_object" }的要求。
      • 解决
        1. 增加重试机制:对API调用封装一个带指数退避的重试函数。
        import tenacity @tenacity.retry(stop=tenacity.stop_after_attempt(3), wait=tenacity.wait_exponential(multiplier=1, min=4, max=10)) def call_openai_with_retry(prompt): # ... 调用代码 ...
        1. 验证提示词:确保你的提示词明确要求了JSON格式输出,并且在消息中包含了json_object的指示。
        2. 设置超时:在OpenAI客户端设置较长的超时时间。client = openai.OpenAI(api_key=api_key, timeout=30.0)

      4.2 浏览器自动化相关

      问题3:元素找不到(TimeoutError)。

      • 排查:这是最常见的问题。页面结构变了、元素加载慢了、或者脚本运行太快了。
      • 解决
        1. 使用更健壮的定位器:优先使用>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/2 12:51:24

WindowResizer:3分钟解决Windows窗口尺寸困扰,重新掌控桌面布局

WindowResizer:3分钟解决Windows窗口尺寸困扰,重新掌控桌面布局 【免费下载链接】WindowResizer 一个可以强制调整应用程序窗口大小的工具 项目地址: https://gitcode.com/gh_mirrors/wi/WindowResizer 你是否遇到过这样的场景?一个老…

作者头像 李华
网站建设 2026/5/2 12:51:24

C语言AES-128-GCM vs ChaCha20-Poly1305性能对决:实测STM32F4/F7/H7三大平台,谁才是IoT设备的终极加密选择?

更多请点击: https://intelliparadigm.com 第一章:C语言轻量级加密性能概览 在资源受限的嵌入式系统、IoT设备及固件安全场景中,C语言实现的轻量级加密算法因其零运行时依赖、可预测执行时间与高度可控内存占用而备受青睐。相较于OpenSSL等…

作者头像 李华
网站建设 2026/5/2 12:51:16

终极指南:如何成为Audiocraft音频AI项目的核心贡献者

终极指南:如何成为Audiocraft音频AI项目的核心贡献者 【免费下载链接】audiocraft Audiocraft is a library for audio processing and generation with deep learning. It features the state-of-the-art EnCodec audio compressor / tokenizer, along with MusicG…

作者头像 李华