gaokao-site/scripts/scrape_tuition_v2.py

256 lines
8.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
阳光高考院校收费数据爬虫 v2
使用 Playwright 批量抓取学校收费数据
流程:
1. 从 gaokao_henan.db 读取学校列表
2. 用 Playwright 搜索学校获取 schId
3. 访问收费公示页,解析学费/住宿费
4. 结果存入 JSON + 更新 schools 表 tuition 字段
"""
import sqlite3
import json
import time
import random
import re
import os
import sys
DB_PATH = "/www/wwwroot/gaokao/gaokao_henan.db"
OUTPUT_JSON = "/tmp/school_tuition.json"
SCHID_CACHE = "/tmp/school_schid_cache.json"
BATCH_SIZE = 100
DELAY = 1.5 # 秒
def get_schools_from_db():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
SELECT DISTINCT school_name
FROM schools
WHERE school_name IS NOT NULL AND school_name != ''
ORDER BY school_name
""")
schools = [row[0] for row in cur.fetchall()]
conn.close()
return schools
def load_schid_cache():
if os.path.exists(SCHID_CACHE):
with open(SCHID_CACHE) as f:
return json.load(f)
return {}
def save_schid_cache(cache):
with open(SCHID_CACHE, 'w') as f:
json.dump(cache, f, ensure_ascii=False, indent=2)
def add_tuition_column():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
try:
cur.execute("ALTER TABLE schools ADD COLUMN tuition TEXT")
print("[+] 已添加 tuition 列")
except Exception:
pass
conn.commit()
conn.close()
def save_tuition_result(results):
with open(OUTPUT_JSON, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
def parse_fee_page(html_content):
"""
解析收费公示页HTML提取学费和住宿费信息
返回格式: {"tuition": "5000-8000元/年", "dorm": "800-1200元/年", "raw": "..."}
"""
tuition_patterns = [
r'学费[:]\s*(\d+\.?\d*[\u4e00-\u9fa5]*/\u5b66\u751f[\u00B7\u2022]?\u5e74?)',
r'(\d{4})\u5e74\u5b66\u8d39\u6807\u51c6[\uff1a:]\s*(\d+)',
r'\u5b66\u8d39\u6807\u51c6[\uff1a::\s]*(\d+)',
r'(\d{4})[\u5e74]*\u5b66\u8d39[:]\s*(\d+)',
r'\u5b66\u8d39\s*(\d+)\s*[\u5143\u5147]',
]
result = {"tuition": None, "dorm": None, "raw": ""}
# 尝试提取学费信息
for pattern in tuition_patterns:
matches = re.findall(pattern, html_content)
if matches:
result["tuition"] = str(matches[0])
break
# 如果没找到,返回页面中包含"元/年"的片段作为参考
if not result["tuition"]:
yuan_nian = re.findall(r'[\d\.]+\s*[\u5143\u5147]\s*/\s*\u5b66\u5e74|[一二三<E4BA8C>\u4e00-\u9fa5]{2,4}\u5b66\u8d39[\uff1a:]\s*\d+', html_content)
if yuan_nian:
result["raw"] = "; ".join(yuan_nian[:5])
return result
def main():
print("=" * 60)
print(" 阳光高考院校收费数据爬虫 v2")
print("=" * 60)
# 1. 初始化数据库
add_tuition_column()
# 2. 加载学校列表和缓存
schools = get_schools_from_db()
schid_cache = load_schid_cache()
print(f"[i] 数据库共 {len(schools)} 所学校")
print(f"[i] 已有 schId 缓存: {len(schid_cache)}")
# 3. 检查 Playwright
try:
from playwright.sync_api import sync_playwright
print("[+] Playwright 可用")
except ImportError:
print("[x] 未安装 Playwright正在安装...")
os.system("pip install playwright -q")
os.system("playwright install chromium --with-deps")
from playwright.sync_api import sync_playwright
print("[+] Playwright 安装完成")
# 4. 启动 Playwright 浏览器
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
locale="zh-CN",
)
page = context.new_page()
# 设置超时
page.set_default_timeout(15000)
results = {}
skip_count = 0
fetch_count = 0
for i, school_name in enumerate(schools):
if i < skip_count:
continue
if (i + 1) % BATCH_SIZE == 0:
print(f" 进度 {i+1}/{len(schools)} ({fetch_count} 所已抓取)")
save_schid_cache(schid_cache)
time.sleep(2)
# 检查缓存
if school_name in schid_cache:
sch_id = schid_cache[school_name]
else:
# 搜索学校获取 schId
try:
search_url = f"https://gaokao.chsi.com.cn/sch/search.do"
page.goto(search_url, wait_until="domcontentloaded")
time.sleep(0.8)
# 输入学校名
page.fill("input[type='text'], input[placeholder*='院校名称']", school_name)
time.sleep(0.3)
page.click("button:has-text('搜索'), button[type='submit']")
time.sleep(2.0)
# 从当前 URL 提取 schId
url = page.url
match = re.search(r'schId[=-](\d+)', url)
if match:
sch_id = match.group(1)
schid_cache[school_name] = sch_id
else:
# 从页面内容找 schId
content = page.content()
match = re.search(r'schId[=-](\d+)', content)
if match:
sch_id = match.group(1)
schid_cache[school_name] = sch_id
else:
results[school_name] = {"error": "未找到 schId", "schId": None}
time.sleep(DELAY)
continue
except Exception as e:
results[school_name] = {"error": str(e), "schId": None}
time.sleep(DELAY)
continue
time.sleep(DELAY + random.uniform(0, 0.5))
# 访问收费公示页
try:
# 收费项目页 URL 格式
fee_url = f"https://gaokao.chsi.com.cn/sch/schoolInfo--schId-{sch_id},categoryId-7721443883,mindex-7.dhtml"
page.goto(fee_url, wait_until="domcontentloaded")
time.sleep(1.0)
html = page.content()
parsed = parse_fee_page(html)
results[school_name] = {
"schId": sch_id,
"fee_url": fee_url,
"tuition": parsed["tuition"],
"dorm": parsed.get("dorm"),
"raw_note": parsed.get("raw"),
"status": "ok" if parsed["tuition"] else "no_tuition_data"
}
fetch_count += 1
# 定期保存
if fetch_count % 20 == 0:
save_tuition_result(results)
print(f" [saved] 已保存 {len(results)} 条结果")
except Exception as e:
results[school_name] = {"error": str(e), "schId": sch_id, "status": "error"}
time.sleep(DELAY + random.uniform(0, 0.5))
browser.close()
# 5. 保存结果
save_tuition_result(results)
save_schid_cache(schid_cache)
# 6. 统计
ok = sum(1 for v in results.values() if v.get("status") == "ok")
no_data = sum(1 for v in results.values() if v.get("status") == "no_tuition_data")
errors = sum(1 for v in results.values() if v.get("status") == "error")
print()
print("=" * 60)
print(" 抓取完成")
print(f" 总计处理: {len(results)}")
print(f" 成功获取学费: {ok}")
print(f" 无学费数据: {no_data}")
print(f" 错误: {errors}")
print(f" 结果文件: {OUTPUT_JSON}")
print(f" schId 缓存: {SCHID_CACHE}")
print("=" * 60)
# 7. 更新 schools 表
print()
print("更新 schools 表 tuition 字段...")
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
updated = 0
for school_name, data in results.items():
if data.get("tuition"):
cur.execute(
"UPDATE schools SET tuition = ? WHERE school_name = ? AND tuition IS NULL",
(data["tuition"], school_name)
)
updated += cur.rowcount
conn.commit()
conn.close()
print(f"[+] 已更新 {updated} 条 tuition 记录")
if __name__ == "__main__":
main()