每天上班第一件事就是打开十几个网页:查招标公告、盯政策补贴、刷一下企业有没有新增诉讼,再搜一圈行业竞品动态。这些动作重复、耗时,而且特别适合交给程序——但传统爬虫脚本只能抓“定死的页面”,页面结构一变就失效,更不用说从杂乱网页里提炼出“这条信息和我的业务有没有关系”。
这次我们要解决的,就是让 AI 自己完成这个流程:定时上网、搜索目标关键词、阅读网页内容、用大模型做结构化提取,最后把结果汇总成一份可读的报告。它不是某个现成的商业软件,而是一套可以自己搭建的 AI Agent 自动化方案。整套系统以 Python 为主,依赖浏览器自动化和 LLM API,可以跑在本地电脑上,也可以放到服务器里长期运行。
本文会拆解这套“AI 自动上网找项目、找钱、查风险”系统的完整技术方案:先看核心能力与适用边界,再给出环境准备、代码实现、定时任务配置、API 封装和批量任务扩展。所有代码都是可直接改用的通用模板,你需要替换成自己的目标站点、搜索关键词和 LLM API Key。如果你的目标不只是信息搜集,还想研究多智能体协作和 Agent 长期记忆,文末也会给出一个开源项目作为扩展参考。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | AI Agent 自动化信息搜集与结构化分析 |
| 核心功能 | 定时上网搜索、网页内容抓取、LLM 信息提取、结果汇总与通知 |
| 技术栈 | Python + Playwright/Selenium + LLM API + 定时任务调度 |
| 硬件要求 | 调用云端 LLM 时本地不需要独立显卡;本地跑模型时需按模型规格确认显存 |
| 启动方式 | 命令行脚本运行,配合 cron / APScheduler / Windows 任务计划定时触发 |
| 接口能力 | 可以封装为 FastAPI 服务,对外提供 HTTP 接口 |
| 批量任务 | 支持多关键词、多站点、多轮调度和结果去重合并 |
| 适用场景 | 项目线索搜集、招投标信息监控、政策补贴跟进、企业风险舆情 |
| 合规边界 | 仅用于合法信息搜集,数据来源需遵守目标网站协议,结果需人工复核 |
这套方案的最大特点是不需要从零训练模型:网页抓取由 Playwright 这类浏览器自动化工具完成,信息理解交给 LLM API,你的核心工作只是把“任务定义”写清楚——搜索什么、关注什么、输出成什么格式。因此部署门槛比大多数人想象的低。
2. 适用场景与使用边界
2.1 适合解决什么问题
对商务、创业者、投融资人员和风险相关岗位来说,每天的信息搜集工作可以拆成三个固定动作:找项目线索、找资金和补贴信息、查目标对象的经营风险。这三个动作都很适合做成 AI Agent 的日常任务。
- 找项目:每天自动搜索招标信息、政府采购公告、产业园区招商通知等。
- 找钱:定时跟政策申报网站、基金公告、补贴公示等页面。
- 查风险:监控企业新增诉讼、行政处罚、经营异常、股权变更等信息。
如果把这些任务做成脚本,每天早上自动跑一轮,输出一份 Markdown 或 JSON 格式的晨报,能省下至少一小时的重复劳动。
2.2 不适合什么场景
这套方案不擅长获取需要账号权限才能访问的数据,也不适合对抓取结果做一锤定音的判断。LLM 会存在幻觉,网页解析可能漏字段,所以它定位是“信息搜集助手”,不是“自动决策系统”。如果要用在正式风控报告、投资决策或招投标制作环节,最终判断必须由人来复核。
2.3 合规与安全边界
自动上网抓取数据时必须注意以下几点:
- 遵守目标网站的 robots 协议和用户条款,不抓取登录墙后的受保护内容。
- 控制抓取频率,不对目标站点造成访问压力。
- 不破解验证码、不绕过访问控制。遇到验证码时应该暂停任务,人工介入或换用合规数据源。
- 涉及企业工商信息、个人数据时,确认数据来源是否允许再加工和存储。
- 抓取的内容只用于合法商业决策,不对外二次贩卖。
3. 技术方案与系统设计
整套系统的设计可以拆成四层,每一层职责单一,方便单独替换和调试。
3.1 任务调度层
调度层负责决定“什么时候跑、跑哪些任务”。最简单的是直接用操作系统的 cron,或者用 Python 的 APScheduler。任务定义通常用配置文件管理,包含:任务名、搜索关键词、目标 URL、执行频率、通知渠道。
3.2 数据采集层
采集层负责“上网拿内容”。这里优先推荐 Playwright,因为它基于真实 Chromium 内核,不容易被基础的反爬规则拦截,也能处理 JavaScript 动态渲染的页面。如果是纯静态页面,用 requests + BeautifulSoup 更轻量,能减少内存占用。
3.3 信息提取层
采集到的 HTML 不能直接丢给大模型,因为 token 消耗会很大。建议先用可读性分析把网页正文提取出来,做敏感信息截断,再交给 LLM 做结构化提取。让模型输出固定的 JSON 格式,例如:{"title": "...", "date": "...", "risk_type": "...", "summary": "..."}。
3.4 结果存储与通知层
每轮任务的结果先写入本地文件或 SQLite,避免程序中断丢数据。然后生成汇总摘要,推送到钉钉机器人、飞书机器人、企业微信群机器人或邮件。推送内容不宜过长,给关键字段和原文链接即可。
下面按这个分层结构,给出完整的部署与实现步骤。
4. 本地部署环境准备
4.1 系统与语言环境
Windows、macOS、Linux 都可以运行。需要提前装好 Python 3.10 或更高版本,并保证终端里可以执行python命令。
python --version4.2 安装依赖
建议创建独立虚拟环境,避免依赖冲突。
python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate然后安装核心依赖:
pip install playwright openai apscheduler beautifulsoup4 httpx playwright install chromium说明:
playwright:负责浏览器自动化和网页抓取。openai:虽然包名是 openai,但现在大多数国内 LLM 服务都兼容 OpenAI 格式,换 Base URL 就能用。apscheduler:负责定时任务调度。beautifulsoup4:负责解析 HTML、提取网页正文。httpx:用于请求接口和发送通知。
如果你的目标站点全部是静态页面,不装 Playwright 也可以,直接用requests就行。但从通用性考虑,Playwright 更能应对动态页面。
4.3 配置 LLM API Key
去你选择的模型服务商控制台申请 API Key,记录 Base URL 和模型名称。常见的推理模型、对话模型都能完成结构化提取任务,关键在于提示词要写清楚输出格式。
export LLM_API_KEY="your-api-key" export LLM_BASE_URL="https://api.example.com/v1" export LLM_MODEL="your-model-name"也可以把这些变量写入.env文件,用python-dotenv加载。
4.4 目录结构规划
建议用下面的结构组织工程,避免运行几天后文件到处乱放:
ai_agent_scout/ ├── config.yaml ├── requirements.txt ├── agent.py ├── scheduler.py ├── api_server.py ├── data/ │ ├── results/ │ └── logs/ └── tasks/ ├── project_search.py └── risk_monitor.py无头模式下抓取的数据都落在data/results,日志统一写到data/logs。这样排查问题时会省很多时间。
5. 核心代码实现
5.1 任务配置
用 YAML 管理多任务是最方便的方式。新增一个监控方向时,不需要改代码,直接加配置即可。
# config.yaml tasks: - name: project_tender keywords: - "智慧园区 招标" - "数字化 政府采购" target_urls: - "https://example.com/tender" schedule: "0 8 * * *" output_format: "json" - name: risk_monitor keywords: - "目标公司名 经营异常" - "目标公司名 行政处罚" target_urls: - "https://example.com/company" schedule: "0 9 * * *" output_format: "json" llm: api_key_env: "LLM_API_KEY" base_url_env: "LLM_BASE_URL" model_env: "LLM_MODEL" temperature: 0.1 storage: result_dir: "data/results" log_dir: "data/logs"注意:temperature要设低一点,结构化提取任务需要确定性输出,温度越高越容易编造内容。
5.2 搜索与网页抓取模块
这里用 Playwright 打开搜索引擎或目标站点,取回页面内容。核心是两件事:定位搜索框、输入关键词、回车、等待结果。
# agent.py import asyncio from playwright.async_api import async_playwright async def search_page(keyword: str, search_url: str) -> str: async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page() try: await page.goto(search_url, timeout=60000) await page.fill('input[name="q"]', keyword) await page.press('input[name="q"]', "Enter") await page.wait_for_timeout(3000) content = await page.content() return content finally: await browser.close()如果你的目标站点是固定的结果页 URL,可以跳过搜索框定位,直接把关键词拼进 URL 访问,这样更稳定:
# 示例:把关键词做 URL 编码后拼接到搜索地址 from urllib.parse import quote def build_search_url(base_url: str, keyword: str) -> str: return f"{base_url}?q={quote(keyword)}"5.3 网页正文提取模块
拿到 HTML 之后先清洗,去掉 script、style 标签,再取正文区域。用 BeautifulSoup 可以快速完成。
from bs4 import BeautifulSoup def extract_text(html: str, max_chars: int = 3000) -> str: soup = BeautifulSoup(html, "html.parser") for tag in soup(["script", "style", "nav", "footer", "aside"]): tag.decompose() text = soup.get_text(separator="\n", strip=True) return text[:max_chars]截断这一步很重要。网页全文可能几万字,塞进 LLM 会浪费 token,还会稀释关键信息。先限制在 3000 字符以内,后续如果发现漏信息,再针对具体页面放大。
5.4 LLM 结构化提取模块
from openai import OpenAI import json client = OpenAI( api_key=os.getenv("LLM_API_KEY"), base_url=os.getenv("LLM_BASE_URL"), ) def extract_with_llm(text: str, task_desc: str) -> dict: prompt = f""" 你是一个信息提取助手。请根据下面的任务描述提取关键信息。 任务描述:{task_desc} 网页正文: {text} 要求输出 JSON,字段包括: - title: 标题 - date: 日期 - is_relevant: 0 或 1,是否与任务相关 - summary: 200 字以内的摘要 - risk_level: 高 / 中 / 低 / 无 只输出 JSON,不要输出其他内容。 """ resp = client.chat.completions.create( model=os.getenv("LLM_MODEL"), messages=[{"role": "user", "content": prompt}], temperature=0.1, response_format={"type": "json_object"}, ) return json.loads(resp.choices[0].message.content)如果使用的模型服务不支持response_format参数,就去掉这一行,并在提示词里强调“只输出 JSON”。否则解析时容易因为模型输出了额外文字而报错。
5.5 完整任务执行流程
把搜索、提取、存储串起来:
def run_task(task: dict): keyword = task["keywords"][0] html = asyncio.run(search_page(keyword, task["target_urls"][0])) text = extract_text(html) result = extract_with_llm(text, task["name"]) save_result(task["name"], result)实际使用中,一个关键词可能对应多个搜索结果页,需要循环处理;如果结果太多,还要做去重。
5.6 定时任务调度
用 APScheduler 实现每日定时执行:
from apscheduler.schedulers.blocking import BlockingScheduler import yaml def load_tasks(): with open("config.yaml", "r", encoding="utf-8") as f: return yaml.safe_load(f) def scheduled_job(task): try: run_task(task) log("success", task["name"]) except Exception as e: log("error", f"{task['name']}: {e}") if __name__ == "__main__": config = load_tasks() scheduler = BlockingScheduler() for task in config["tasks"]: scheduler.add_job(scheduled_job, "cron", hour=8, minute=0, args=[task]) scheduler.start()如果不依赖 Python 进程,也可以直接用系统 cron:
# 每天早上 8 点执行 0 8 * * * cd /path/to/project && .venv/bin/python scheduler.py >> data/logs/cron.log 2>&16. 功能测试与效果验证
系统跑起来之后,不要直接放量监控,先按下面的顺序做验证。
6.1 单条抓取测试
先用单任务、单关键词、单页面跑通最小链路:
python agent.py --task project_tender --keyword "智慧园区 招标"预期结果:终端能看到搜索页面被打开、正文被截断、LLM 返回 JSON、结果写入data/results。任何一步失败都会有明确报错。
判断标准:is_relevant字段基本准确,summary能概括网页关键信息。
6.2 结构化输出准确性测试
准备 10 条人工标注过的网页样本,让 LLM 提取,对比字段准确率。重点看三个问题:
- 日期是否识别正确。
- 无关信息是否被过滤(
is_relevant=0)。 - 摘要是否出现事实性错误。
如果准确率不理想,优先调整提示词,给模型更多字段示例;不要一上来就换模型。
6.3 定时任务测试
把 cron 时间改成当前时间后 1 分钟,观察任务是否能准时触发。确认跑通后,再改回正式时间。
6.4 失败恢复测试
在抓取阶段故意传入一个不存在的 URL,确认程序能捕获异常并记录日志,而不是直接崩溃。这样可以确保以后某个目标网站改版时,整个调度任务不会停摆。
7. 接口 API 与批量任务扩展
如果不想只在本机跑,可以封装成 HTTP 接口,让其他系统调用。
7.1 FastAPI 服务封装
# api_server.py from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class TaskRequest(BaseModel): task_name: str keywords: list[str] target_urls: list[str] @app.post("/run_task") def create_task(req: TaskRequest): task = { "name": req.task_name, "keywords": req.keywords, "target_urls": req.target_urls, } run_task(task) return {"status": "ok", "message": "task finished"}启动服务:
uvicorn api_server:app --host 127.0.0.1 --port 80007.2 curl 调用示例
curl -X POST http://127.0.0.1:8000/run_task \ -H "Content-Type: application/json" \ -d '{ "task_name": "risk_monitor", "keywords": ["目标公司 经营异常"], "target_urls": ["https://example.com/company"] }'7.3 Python 调用示例
import requests url = "http://127.0.0.1:8000/run_task" payload = { "task_name": "project_tender", "keywords": ["智慧园区 招标", "数字化 政府采购"], "target_urls": ["https://example.com/tender"], } resp = requests.post(url, json=payload, timeout=300) print(resp.json())7.4 批量任务设计
批量任务的核心不是加快抓取速度,而是做好任务隔离。建议每个任务独立日志文件,失败重试最多 3 次,间隔递增:
import time def run_with_retry(task, max_retries=3): for attempt in range(max_retries): try: run_task(task) return True except Exception as e: log("error", f"attempt {attempt + 1}: {e}") time.sleep(10 * (attempt + 1)) return False并发控制也要注意。如果多个任务同时启动,Playwright 会拉起多个浏览器进程,内存压力会很大。更稳妥的方式是任务队列串行执行,或者设置最多 2 个并发。
8. 资源占用与性能观察
这套方案的资源占用主要集中在采集层,而不是模型推理层。
- 云端 LLM API 方案:本地不占用显存,主要消耗 CPU、内存和网络带宽。每个 Playwright 浏览器进程大约占用 200-400MB 内存,具体要看页面复杂度。
- 本地 LLM 推理方案:需要按模型规格确认显存要求。比如部分 7B 参数的量化模型需要 6GB 以上显存,13B 以上模型需要更多。这一项必须以你实际部署的模型为准,不要根据经验直接买卡。
- CPU 占用:多数时间很低,任务跑起来时浏览器渲染和页面解析会短时拉高 CPU。
降低资源占用的三个方法:
- 使用无头模式,并且每次用完立即关闭浏览器,避免进程残留。
- 减小正文截断长度,比如 3000 字符改为 1500 字符,能明显减少 LLM 调用时间和 token 消耗。
- 控制调度频率。信息类站点没必要每小时抓一次,早晚各一次通常足够。抓太频繁反而容易被限制。
观察资源占用时,可以盯三个指标:任务执行耗时、LLM API 的 token 消耗、磁盘目录大小。这三个指标最能反映系统是否健康。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动后页面打不开 | 端口冲突或 chromium 未安装 | 查看终端报错,执行playwright install chromium | 更换端口,或重新安装浏览器内核 |
| 搜索结果页返回 403 | 目标站点有基础反爬 | 检查是否触发频率限制,换无头模式或加等待时间 | 降低抓取频率,必要时换数据源 |
| 遇到登录验证码 | 目标页面需要账号权限 | 无法自动绕过,记录日志并跳过该页面 | 改用公开页面,或由人工登录后执行 |
| LLM 返回内容无法解析成 JSON | 模型输出带额外文字,或 response_format 不支持 | 打印原始返回值 | 去掉response_format,在提示词中强调只输出 JSON |
| 定时任务不触发 | 时区设置错误或进程未常驻 | 查看data/logs/cron.log,用crontab -l确认任务 | 检查系统时区,改用 APScheduler 并保持进程运行 |
| 内存持续升高 | 浏览器进程没关闭,或有异步任务泄漏 | 查看系统进程,确认 chromium 残留 | 在finally里关闭 browser,限制并发数 |
| 多个关键词重复结果 | 同一信息在多个页面出现 | 对比结果 title 和 URL | 按 title+URL 做本地去重 |
| 摘要出现事实错误 | 提示词约束不足,或正文截断导致上下文缺失 | 检查截断后的文本是否包含关键信息 | 优化正文提取逻辑,或调整截断位置 |
10. 最佳实践与合规建议
10.1 工程化建议
第一次跑通后,马上做三件事:
- 固定一套最小可运行配置,备份
requirements.txt和config.yaml,以后环境再乱也能快速恢复。 - 把抓取结果按日期建目录,例如
data/results/2025-01-01/,避免历史结果被覆盖。 - 在结果里记录抓取时间和来源 URL,既方便回溯,也方便日后排查信息准确性问题。
10.2 提示词调优
同样的模型,提示词写得好不好,效果差距很大。结构化提取任务的提示词应该包含:任务背景、输入格式、输出字段、字段示例、禁止行为。例如面试官式的要求“只输出 JSON”一定要写,否则模型很容易在 JSON 前后加说明文字。
10.3 合规红线
自动上网搜集公开信息本身是合法技术应用,但不能越过几条红线:
- 不访问需要账号密码才能获取的数据,不抓取个人隐私数据。
- 不破解验证码、不绕过反爬机制、不影响目标站点正常服务。
- 不把搜集到的信息用于投诉、骚扰、抹黑他人等不当用途。
- 商业场景下,对外发布或使用信息前确认版权和授权范围。
10.4 人工复核机制
每天生成的 AI 晨报,建议留出 10 分钟人工过一遍。重点看摘要是否有歧义、风险等级是否合理、是否有明显误报。系统只是把信息从“散落各处”变成“集中一处”,最终判断还是要靠人。
11. 总结与下一步
这套“AI 自动上网找项目、找钱、查风险”的方案,最有价值的地方不是单个技术点,而是把浏览器自动化、LLM 结构化提取、定时调度和消息通知组合成了一个可以长期跑的闭环。先做单任务验证,跑通后再加关键词、加站点、加通知渠道,是最稳的推进路径。
最容易踩的坑有三个:一是忽略正文截断,token 消耗过大;二是没做失败重试,某个网站改版后整个任务停摆;三是提示词没有约束输出格式,解析代码频繁报错。这些都在前面的章节里给出了具体解决方式。
如果你把自动上网跑顺之后,想继续研究 AI Agent 的行为决策和多智能体协作,可以关注 GitHub 上的开源项目https://github.com/mewamew/my_ai_town。它属于 AI 小镇类的 Agent 环境模拟项目,适合用来观察多个智能体在环境里的自主行为和记忆机制,对理解 Agent 长期任务执行和状态管理会有不少启发。
建议先把今天的方案跑通,输出第一份 AI 晨报,再决定要不要往多 Agent 方向深挖。系统稳定运行后,你会慢慢发现:每天最耗时间的“开网页找信息”动作,终于可以一键交给机器了。