Selenium 爬虫固定开头:

复制代码
def build_driver(headless: bool = True) -> webdriver.Chrome:
    chrome_opts = Options()
    if headless:
        chrome_opts.add_argument("--headless=new")
    chrome_opts.add_argument("--disable-gpu")
    chrome_opts.add_argument("--no-sandbox")
    chrome_opts.add_argument("--window-size=1400,900")
    chrome_opts.add_argument(
        '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
        'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'
    )
    chrome_opts.add_argument("--disable-blink-features=AutomationControlled")

    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service, options=chrome_opts)
    driver.set_page_load_timeout(180)
    driver.implicitly_wait(3)
    return driver


def human_like_scroll(driver: webdriver.Chrome):
    """Simulate human scrolling behavior, to prevent being too "robotic"."""
    try:
        # Scroll a few times, each time to a different height, with random pauses in between
        scroll_steps = random.randint(2, 5)
        for _ in range(scroll_steps):
            # 0.3 ~ 1.0 times the page height randomly
            factor = random.uniform(0.3, 1.0)
            driver.execute_script(
                "window.scrollTo(0, document.body.scrollHeight * arguments[0]);",
                factor,
            )
            time.sleep(random.uniform(0.5, 1.5))
    except Exception as e:
        print("[WARN] human_like_scroll error: ", e)

搜索框键入搜索:

复制代码
def test_search(url: str, keyword: str):
    driver = build_driver(headless=False)   # 方便你看到效果
    driver.get(url)
    time.sleep(1)

    # 找到搜索框
    search_input = driver.find_element(By.CSS_SELECTOR, ".search-container .sh-inpt input")
    search_input.clear()
    search_input.send_keys(keyword)
    time.sleep(0.3)

    # 点击"搜索"按钮(Selenium 4 推荐方式)
    search_button = driver.find_element(By.CSS_SELECTOR, ".search-container .sh-btn")
    search_button.click()

    # 等待页面加载
    time.sleep(3)

    # 模拟滚动
    human_like_scroll(driver)

    print("页面标题:", driver.title)
    print("当前URL:", driver.current_url)

    # 你可在这里加"爬取结果"的代码
    # html = driver.page_source
    # print(html[:300])

    time.sleep(2)
    driver.quit()


if __name__ == "__main__":
    test_search(
        url="http://search.people.cn/",     # ← 要测试的网站
        keyword="数字化转型"            # ← 测试关键词
    )

爬虫 + LLM

在本地调用大模型 API,不能对网址链接进行访问。因为大模型本身是语言模型,只能处理文本输入输出,无法直接发起HTTP请求,无法执行浏览器操作。

所以一般的方法是,先用爬虫工具,如 BeautifulSoup4、requests、Selenium 等将文本类型的数据爬取下来,然后将文本数据导入大模型进行语义分析和目标关键数据的提取。

配置环境变量:

复制代码
from dotenv import load_dotenv
load_dotenv()

读取环境变量:

复制代码
base_url = os.getenv("AI_BASE_URL", "")     # e.g. https://api.aiiai.top/v1
api_key = os.getenv("AI_API_KEY", "")
model = os.getenv("AI_MODEL_TYPE", "")      # e.g. gemini-2.5-pro

用 OpenAI 兼容 SDK 调用:

复制代码
client = OpenAI(
    base_url=base_url,
    api_key=api_key,
)

completion = client.chat.completions.create(
    model=model,
    messages=messages,
)
content = completion.choices[0].message.content

OpenAI 文档明确说明请求 JSON 必须包含:

复制代码
{
  "model": "gpt-5.2",
  "messages": [
    {"role": "system", "content": "..." },
    {"role": "user", "content": "..." }
  ]
}

实例:

复制代码
def call_local_llm(messages: List[Dict[str, str]]) -> Any:
    """
    Call the local / OpenAI compatible model:
    - Read AI_BASE_URL / AI_API_KEY / AI_MODEL_TYPE from environment variables
    - Default base_url = https://api.aiiai.top/v1
    - Default model = gemini-2.5-pro
    - Return the Python object (dict / list / None) after JSON parsing
    """
    init_logger()

    base_url = os.getenv("AI_BASE_URL", "")
    api_key = os.getenv("AI_API_KEY", "")
    model = os.getenv("AI_MODEL_TYPE", "")

    if not base_url:
        base_url = "https://api.aiiai.top/v1"
    if not model:
        model = "gemini-2.5-pro"

    if not api_key:
        logger.error("API key is empty")
        raise ValueError("API key is empty")

    client = OpenAI(
        base_url=base_url,
        api_key=api_key,
    )

    logger.info(f"Calling LLM, model={model}")
    completion = client.chat.completions.create(
        model=model,
        messages=messages,
    )

    content = completion.choices[0].message.content
    if content is None:
        logger.error("LLM returned empty content")
        raise RuntimeError("LLM returned empty content")

    content = content.strip()
    logger.info(f"LLM raw output: {content}")

    # Try JSON parsing, compatible with ```json ... ``` wrapped cases
    if isinstance(content, str):
        try:
            return json.loads(content)
        except json.JSONDecodeError:
            cleaned = content.strip().strip("`")
            lower = cleaned.lower()
            if lower.startswith("json\n") or lower.startswith("json\r\n"):
                cleaned = "\n".join(cleaned.splitlines()[1:])
            return json.loads(cleaned)
    else:
        # It should not reach here, but keep compatible
        return content

SDK = Software Development Kit(软件开发工具包)。它是一套官方提供的工具,用来方便开发者调用某个服务。

对于 OpenAI,SDK 封装了 HTTP 请求,不需要自己写复杂的 POST body、headers,SDK 会自动处理错误、重试、流式输出等。

用 SDK 的写法:

复制代码
completion = client.chat.completions.create(
    model=model,
    messages=messages,
)

如果不用 SDK,就必须手写 HTTP:

复制代码
import requests

requests.post(
    "https://api.openai.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "model": "...",
        "messages": [...],
    }
)

把要分析的文本传入大模型:

  1. SYSTEM_PROMPT: 从外部文件加载规则

    def load_system_prompt():
    base_dir = os.path.dirname(os.path.abspath(file))
    ht_path = os.path.join(base_dir, "ht_jg.txt")
    ...
    SYSTEM_PROMPT = load_system_prompt()

  2. messages: 一条一条地喂合同记录

    def build_messages_for_single_record(
    region: str,
    organization_name: Optional[str],
    contract_record: Dict[str, Any],
    ) -> List[Dict[str, str]]:
    """
    Construct the messages for a single contract record:
    - system: system instruction (SYSTEM_PROMPT), which should clearly state: this time only process this one contract
    - user: contains region / organization_name / this current contract_record / demo_str
    """
    user_payload = {
    "region": region,
    "organization_name": organization_name or "",
    "contract_record": contract_record,
    "demo_str": demo_str,
    }

    复制代码
     messages = [
         {"role": "system", "content": SYSTEM_PROMPT},
         {
             "role": "user",
             "content": (
                 "下面是本次任务的具体输入参数 JSON(仅包含一条合同记录),"
                 "你只能基于这条记录判断是否与目标地区/机构相关,并抽取结构化信息:\n\n"
                 + json.dumps(user_payload, ensure_ascii=False, indent=2)
             ),
         },
     ]
     return messages

逐条处理:每次只给模型一条 contract_record(title/url/date/content),杜绝"混合同"错位。

混合同错位(cross-record hallucination 或 cross-record mixup)指模型在处理一批合同记录时,把 A 合同的 URL、B 合同的内容、C 合同的业务场景混在一起输出。

表现为:

  • 输出的 URL ≠ 它根据内容抽取出的业务场景

  • 输出内容包含另一个合同的片段

  • 模型生成了不存在的合同(虚构 URL)

  • 第 N 条合同的输出明显引用了第 N + 1 条文本的信息

你之前遇到的这段就是典型"混合同":模型把不存在的 URL 输出成真实合同,并且内容来自完全不同文章

出现这样的原因是:

  1. 大模型的 "上下文融合机制"

    LLM 的本质是把输入的所有内容当成一个统一的语境进行概率预测。

    这意味着:如果你给模型一次性输入了 200 条合同,模型不会理解"这是 200 条独立样本",它会认为这是"一个巨大的语料库",并在其中"寻找它认为合理的关联"。

    换句话说:LLM 不天然支持"一条一条分开处理"这个概念。批量输入 = 让模型混淆边界 = 产生错位

相关推荐
安然无虞6 小时前
Python自动化测试·Selenium操控元素的方法
python·selenium·测试工具
深蓝电商API7 小时前
Flutter App逆向初探:Dart编译产物的分析思路
爬虫
Data_Journal7 小时前
使用 Selenium 进行网页抓取:分步指南
selenium·测试工具·microsoft
搬砖柯7 小时前
系列12-接口压测怎么做成平台能力?自研 httpx 引擎、分布式 Worker 与 Locust 选型对照
分布式·测试工具·性能优化·开源·自动化·httpx
搬砖柯1 天前
系列10-接口/UI 自动化怎么验落库?数据工厂与数据库断言实践
数据库·测试工具·ui·开源·自动化
LT10157974441 天前
2026年在线兼容性测试工具推荐|零部署网页 / APP / 小程序实测对比
测试工具·小程序
程序员杰哥1 天前
接口自动化测试项目框架详解
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
taocarts_bidfans1 天前
Playwright 浏览器指纹伪装 + 住宅代理池 日系电商爬虫防封禁实战
爬虫·bidfans
许彰午1 天前
73_Python爬虫Scrapy框架入门
爬虫·python·scrapy