逐步教程(Step-by-Step) — 適合初學者與教學類文章
背景(為什么要這樣做)
對于足球迷、資訊編輯與數據分析師來說,最快、最準確把握一場比賽的核心信息至關重要:比分、關鍵事件(進球、點球、紅黃牌、換人、判罰爭議等)、以及球員表現。傳統基于規則的爬蟲在面對不同媒體頁面與頻繁改版時常顯笨拙:XPath/正則需要頻繁維護,動態加載內容常常漏抓。
引入生成式 AI(Large Language Models, LLM)后,我們可以把抓到的比賽報道或直播實錄交給模型,讓它“讀懂”文章并輸出結構化結果(JSON),同時生成簡明的自然語言摘要,極大提高抽取的魯棒性與可讀性。本教程以實戰角度,演示如何抓取 ESPN、虎撲、騰訊體育 三類站點關于五大聯賽(英超、西甲、德甲、意甲、法甲)的比賽報道,并用 AI 總結比分、關鍵事件與球員表現。
環境準備(快速清單)
- Python 3.9+
- 安裝必要依賴:
pip install requests beautifulsoup4 playwright
playwright install
(若使用外部 LLM SDK,再安裝對應庫;若只做靜態抓取可不安裝 Playwright)
- 代理(示例:爬蟲代理)—— 生產環境請用你自己的憑證并通過環境變量或密鑰管理存儲:
- 域名:
proxy.16yun.cn
(示例) - 端口:
3100
- 用戶名:
16YUN
、密碼:16IP
(示例,請替換)
- 域名:
- 目標站點示例:
- ESPN 足球:
https://www.espn.com/soccer/
- 虎撲足球:
https://soccer.hupu.com/
- 騰訊體育足球頻道:
https://sports.qq.com/soccer/
(站點結構會變,按實際頁面定位)
- ESPN 足球:
高層流程(核心步驟)
- 采集(抓取):通過代理請求或用 Playwright 渲染頁面,獲得完整 HTML/文本。
- 清洗(預處理):去除腳本、廣告、無關導航文本,合并段落,獲得可讀文本片段。
- 分塊:將長文本按模型上下文限制切分(保留比賽關鍵段落完整性)。
- 調用 LLM 抽取:把文本與明確的 prompt 一起發給 LLM,要求返回結構化 JSON(例如:比賽基本信息、事件列表、球員表現)。
- 校驗與落地:對 LLM 返回做 schema 校驗(例如:比分格式、時間合法性),保存到數據庫或 JSON 文件;對低置信結果標注回流以便人工復核并改進 prompt。
- 可視化/下游:生成比分時間線、關鍵事件標簽云、球員評分面板等。
關鍵設計要點(實踐建議)
- 多來源聚合:同一場比賽建議抓取賽后報道、賽事直播頁面與賽后評論三類頁面,合并信息能提高完整性與準確度。
- Prompt 設計:在 prompt 中提供明確返回 schema(要求只輸出 JSON 且字段名固定),并給出示例。
- 后驗校驗:用正則或簡單規則驗證日期/分鐘格式、進球數是否一致,防止 hallucination。
- 費用與性能策略:先用輕量模型做命中過濾(是否為目標聯賽/是否為比賽報道),再將真正需要深度解析的文本送入大型模型,節省調用成本。
- 合規性:遵循目標站點的 robots.txt 與版權/轉載規則;對含個人信息的內容注意合規與隱私。
示例代碼(精煉版,易讀為主)
下面給出一個精簡、可運行的示例骨架,演示從網頁抓文本并調用偽 LLM 接口獲得結構化輸出。請把偽接口替換成你實際使用的 LLM SDK 或公司內部服務;把代理憑證替換為生產憑證。
# file: football_ai_pipeline.py
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup
import json, time, os# ---設置代理(億牛云示例 www.16yun.cn) ---
PROXY_HOST = os.getenv("PROXY_HOST", "proxy.16yun.cn")
PROXY_PORT = os.getenv("PROXY_PORT", "3100")
PROXY_USER = os.getenv("PROXY_USER", "16YUN")
PROXY_PASS = os.getenv("PROXY_PASS", "16IP")
proxy_url = f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
proxies = {"http": proxy_url, "https": proxy_url}HEADERS = {"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120 Safari/537.36")
}def fetch_html(url, use_proxy=True, timeout=15):kwargs = {"headers": HEADERS, "timeout": timeout}if use_proxy:kwargs["proxies"] = proxiesresp = requests.get(url, **kwargs)resp.raise_for_status()return resp.textdef extract_text(html):soup = BeautifulSoup(html, "lxml")for tag in soup(["script", "style", "noscript", "header", "footer", "aside"]):tag.decompose()texts = [s.strip() for s in soup.stripped_strings]return "\n".join(texts)def call_llm_for_match(text_snippet):"""將文本片段送給 LLM 并返回結構化字典。在此用示例返回占位數據;生產環境替換為真實 API 調用并處理返回結果。"""# 示例返回結構(實際按你的 schema 定制)return {"league": "英超","home_team": "曼聯","away_team": "利物浦","score": "2-1","events": [{"minute": 14, "type": "goal", "team": "曼聯", "player": "拉什福德"},{"minute": 46, "type": "goal", "team": "利物浦", "player": "薩拉赫"},{"minute": 78, "type": "goal", "team": "曼聯", "player": "B費"}],"player_summary": "拉什福德狀態出色,門將幾次關鍵撲救,替補球員改變了戰局。"}def process_match_url(url):html = fetch_html(url)text = extract_text(html)# 若文本過長,可切分并合并 LLM 輸出snippet = text[:5000] # 示例截斷,按模型上下文調整result = call_llm_for_match(snippet)# 后驗簡單校驗(示例)if "score" in result:pass # 在此做正則或數值校驗return resultif __name__ == "__main__":urls = ["https://www.espn.com/soccer/report?gameId=xxxx", # 替換為實際賽報鏈接"https://soccer.hupu.com/games/xxxx","https://sports.qq.com/a/xxxx.htm"]results = []for u in urls:try:r = process_match_url(u)results.append(r)except Exception as e:print("抓取/解析失敗:", u, e)time.sleep(1.5)with open("matches_aggregated.json", "w", encoding="utf-8") as f:json.dump(results, f, ensure_ascii=False, indent=2)
常見問題與排查建議
- 頁面是空白或只剩殼:多數是前端渲染,改用 Playwright/Selenium 或分析 XHR 請求直接調用后端接口。
- 抓到的“報道”是簡短新聞稿,細節不足:同時抓取直播實錄(live blog)或賽后深度評述頁,合并信息。
- LLM 輸出里出現“虛構”事件:增加 prompt 約束(要求基于給定文本輸出),并對關鍵字段做規則校驗;對低置信度結果人工復核。
- 被目標站點限制訪問:降低請求速率、使用合規的代理池、并遵守站點授權與版權政策。