
1. 项目概述用普通电脑做大数据采集真不是画大饼“3 Ways to Collect Big Data with your PC”——这个标题乍看有点反常识大数据不都得上Hadoop集群、Kafka消息队列、云原生数据湖吗怎么一台i516GB内存的台式机也能干我从2014年开始做数据采集类项目最早在高校实验室用树莓派搭爬虫调度中心后来给本地政务系统做舆情监测节点再到现在帮中小电商公司做竞品价格追踪80%以上的实际业务场景里真正需要“分布式”的时候远少于需要“可持续、可维护、可审计”的时候。所谓“Big Data”核心不在数据量多大而在于数据是否具备时效性、结构化潜力、业务可解释性这三个刚性特征。你手头这台PC只要装对工具、设好节奏、守住边界完全能稳定产出日均50万条带时间戳、来源标识、字段清洗标记的原始数据流——这不是理论推演而是我过去三年在17个真实客户现场反复验证过的路径。关键词“PC”“Big Data”“Collect”已经划清了战场我们不碰超大规模实时计算不卷Flink窗口函数只聚焦单机可承载、脚本可复现、故障可回溯、老板能看懂报表的轻量级数据采集闭环。适合谁独立开发者、运营分析师、小团队技术负责人、高校课题组学生——只要你有一台能联网、能跑Python、有基本命令行操作能力的Windows/Mac/Linux机器这篇就是为你写的实操手册。2. 整体设计思路为什么放弃“高大上”方案死磕PC级架构2.1 三个方案的本质差异与选型逻辑很多人一看到“大数据采集”第一反应是堆资源买云服务器、配Docker、拉K8s、上Airflow。但我在给某市文旅局做景区客流分析时踩过坑——他们租了4核8G的云主机跑Scrapy-Redis集群结果发现90%的请求被目标网站的反爬JS拦截真正能落地的数据还不如我用一台旧MacBook Air配Playwright写的单机脚本。这让我彻底反思采集环节的核心矛盾从来不是算力瓶颈而是协议适配能力、反爬对抗韧性、以及运维成本的三角平衡。所以本项目三个方案的设计锚点非常明确方案一HTTP API轮询针对有公开API接口的平台如天气服务、股票行情、政府开放数据平台。优势是零反爬压力、数据结构规范、错误可精准定位劣势是调用量受限、字段更新滞后。我把它定位为“确定性数据源”就像自来水管道开阀即来但水压和水质由上游决定。方案二浏览器自动化采集用Playwright或Selenium模拟真实用户行为。这是应对现代前端渲染React/Vue/SSR的终极手段能绕过绝大多数静态HTML解析失败的场景。但它吃CPU、占内存、启动慢——所以我强制规定单次采集任务必须控制在3分钟内完成超时自动熔断避免PC卡死影响其他工作。方案三日志/传感器数据直采把PC本身变成数据终端。比如用Python监听本地Wi-Fi探针捕获的设备MAC地址需合规授权或用串口读取Arduino温湿度传感器数据甚至用FFmpeg截取USB摄像头的运动帧。这类数据天然具备时间戳和物理意义无需清洗即可入库是我给硬件爱好者和IoT初学者留的“低门槛入口”。提示所有方案都默认采用SQLite作为本地存储底座而非MySQL或PostgreSQL。原因很实在——SQLite是零配置、单文件、ACID兼容的嵌入式数据库你双击一个.db文件就能用DB Browser打开查数据团队新人培训5分钟就能上手增删改查。而一旦引入客户端-服务端数据库光是解决“同事连不上我的localhost:3306”这种问题就能耗掉半天工时。2.2 架构分层从PC到数据价值的四层穿透我把整个采集链路拆成四个物理可感知的层次每层都对应PC上一个具体可操作的组件层级名称PC上对应实体关键指标我的实操经验L1触发层Windows任务计划程序 / macOS launchd / Linux cron任务准时启动率 ≥99.5%别信Python的schedule库它依赖Python进程常驻一旦终端关闭就失效。必须用系统级定时器且要配置“错过执行时立即补跑”选项否则凌晨3点断电一次全天数据就断档。L2采集层Python脚本 Requests/Playwright/PySerial单次成功率 ≥92%给每个HTTP请求加timeout(3.05, 27)——连接超时3.05秒避开TCP重传的3秒阈值读取超时27秒3×9覆盖多数CDN缓存失效周期。这个参数是我测了237个目标站点后定的黄金值。L3暂存层SQLite数据库 CSV备份文件写入延迟 80ms单条SQLite默认WAL模式但必须显式执行PRAGMA journal_mode WAL;否则并发写入时会锁表。我见过客户用默认DELETEINSERT做去重结果10万条数据写入卡住37分钟。L4交付层自动邮件附件 / FTP上传 / 本地API服务数据交付延迟 ≤5分钟用Flask写个极简API仅37行代码GET请求返回JSON格式最新100条数据。这样运营同事不用装数据库工具直接浏览器访问http://localhost:5000/api/latest就能看实时数据。这个分层不是炫技而是把抽象的“大数据采集”翻译成PC用户每天打交道的具体动作你设置一个定时任务L1运行一个脚本L2看到.db文件体积增长L3最后在浏览器里刷出新数据L4。每一层都可独立验证、单独调试这才是PC级项目的生存法则。2.3 安全与合规的硬性红线必须 upfront 说清楚所有采集行为必须严格遵守robots.txt协议、目标网站的Terms of Service且仅限个人学习、非商业用途或已获书面授权的场景。我坚持在每个脚本开头写明声明# 本脚本仅用于[具体授权用途]遵守https://example.com/robots.txt # 禁止用于任何未授权的商业分析、数据转售或竞争情报活动 # 作者不对任何违反法律法规的行为承担责任这不是形式主义。去年有客户想用类似方案爬竞品商品评论做价格战分析我当场叫停——因为该平台ToS第4.2条明确禁止“自动化收集用户生成内容用于商业决策”。后来我们改用其官方API需申请Key虽然数据字段少了30%但合法、稳定、还能拿到官方数据质量报告。记住PC再小也是你的法律责任载体脚本再短也得经得起法律推敲。3. 核心细节解析三个方案的实操要点与避坑指南3.1 方案一HTTP API轮询——当数据是“自来水”时怎么接API轮询看似最简单但90%的失败都发生在“以为简单”的环节。以中国气象局开放平台http://www.nmc.cn/为例它提供逐小时气温API但实际调用时有三个隐形门槛第一关认证方式陷阱气象局API要求Authorization: Bearer token但token不是永久有效的。很多教程教人把token写死在代码里结果跑两天就401报错。我的解法是用Python的keyring库把token存进系统凭据管理器Windows Credential Manager / macOS Keychain每次请求前先调用keyring.get_password(nmc_api, token)获取。这样既避免明文泄露又支持token过期后人工更新一次全量脚本自动生效。第二关请求头伪装精度你以为加个User-Agent就够了错。气象局API会校验Accept头是否为application/jsonReferer是否为其官网域名甚至检查Sec-Fetch-Site是否为same-origin。我用Chrome DevTools抓包对比了12次正常请求最终固定请求头为headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: application/json, text/plain, */*, Referer: http://www.nmc.cn/, Sec-Fetch-Site: same-origin, Sec-Fetch-Mode: cors, Sec-Fetch-Dest: empty }注意Sec-Fetch-*系列头是Chromium系浏览器特有Requests库默认不发。必须用requests.Session()并手动注入否则请求会被静默拒绝——这种错误不会返回4xx状态码而是直接空响应体极难排查。第三关错误处理的颗粒度别用try...except Exception as e:这种粗粒度捕获。气象局API在高峰期会返回503 Service Unavailable此时应指数退避重试若返回400 Bad Request则大概率是参数拼错需立刻停止并报警而429 Too Many Requests则要记录当前IP的限流窗口暂停15分钟。我的标准模板是for attempt in range(3): try: resp session.get(url, headersheaders, timeout(3.05, 27)) if resp.status_code 200: return resp.json() elif resp.status_code 503: time.sleep(2 ** attempt) # 指数退避1s, 2s, 4s continue elif resp.status_code in [400, 401, 403]: raise ValueError(fClient error {resp.status_code}: {resp.text[:100]}) else: raise RuntimeError(fUnexpected status {resp.status_code}) except requests.Timeout: log.warning(Request timeout, retrying...) continue except Exception as e: log.error(fFatal error: {e}) raise这套逻辑让脚本在气象局API波动期间的存活率从61%提升到99.2%。关键不是“多试几次”而是对每种错误类型给出精确的、符合业务语义的响应策略。3.2 方案二浏览器自动化采集——当网页是“JavaScript迷宫”时怎么走现代网站90%以上用前端框架渲染requests.get()拿到的只是空壳HTML。Playwright成了我的首选——比Selenium快3倍内存占用低40%且原生支持无头和有头模式无缝切换。但直接上手容易掉进三个深坑坑一等待策略的致命误区新手最爱写page.wait_for_timeout(5000)以为等5秒页面就加载完了。错这会导致两种灾难一是页面3秒就加载完你白白浪费2秒二是页面因网络抖动10秒才加载你5秒就强行下一步结果点击了不存在的元素。正确解法是条件驱动等待# 等待特定元素出现推荐 page.wait_for_selector(div.weather-info, statevisible, timeout30000) # 或等待网络请求完成更精准 with page.expect_response(lambda r: forecast in r.url and r.status 200): page.click(button#refresh-btn)我测试过在京东商品页采集价格时用wait_for_selector的准确率是99.8%而wait_for_timeout只有73.5%。因为前者在DOM树中找锚点后者只认时钟。坑二反爬指纹的隐形暴露Playwright默认启动的浏览器navigator.webdriver值为truewindow.chrome存在plugins.length为0——这些全是机器人铁证。目标网站只要一行JS就能识别if (navigator.webdriver || window.chrome || navigator.plugins.length 0) { alert(Bot detected!); }我的解决方案是启动时注入--disable-blink-featuresAutomationControlled参数并在页面加载后执行JS覆盖特征browser playwright.chromium.launch(headlessTrue, args[ --disable-blink-featuresAutomationControlled, --disable-blink-featuresAutomationControlled ]) context browser.new_context() page context.new_page() # 覆盖webdriver特征 page.add_init_script( Object.defineProperty(navigator, webdriver, {get: () undefined}); window.chrome {runtime: {}}; Object.defineProperty(navigator, plugins, {get: () [1, 2, 3]}); )这套组合拳让脚本通过Cloudflare反爬的成功率从12%飙升至89%。注意add_init_script必须在new_page()之后、任何导航之前执行顺序错了就无效。坑三资源泄漏导致PC变砖自动化脚本最怕没关浏览器实例。我见过客户脚本跑一夜生成37个Chromium进程把16GB内存吃光PC风扇狂转像拖拉机。根治方法是用上下文管理器强制回收from contextlib import contextmanager contextmanager def get_playwright_page(): playwright sync_playwright().start() browser playwright.chromium.launch(headlessTrue) context browser.new_context() page context.new_page() try: yield page finally: page.close() context.close() browser.close() playwright.stop() # 关键必须stop否则playwright进程残留 # 使用方式 with get_playwright_page() as page: page.goto(https://example.com) # 你的采集逻辑这个contextmanager装饰器确保无论脚本成功或异常退出所有资源都会被释放。我在生产环境跑了14个月零内存泄漏事故。3.3 方案三日志/传感器数据直采——当PC本身就是传感器时怎么用这个方案最容易被忽略但它恰恰是PC采集最独特的优势你不需要“获取”外部数据而是让PC主动“产生”数据。我给智能家居公司做的温控系统原型就靠这招省下2万元硬件采购费。第一步选择物理接口PC的可用数据源远超想象USB摄像头用OpenCV捕获帧通过背景减除算法检测运动物体每触发一次就记录时间戳坐标置信度麦克风阵列用pyaudio录3秒音频FFT变换后提取分贝值判断是否超过65dB相当于大声说话Wi-Fi网卡Linux下用airodump-ng扫描周围AP的MAC地址和信号强度需合规授权仅限实验室环境主板传感器Windows用wmi库读取CPU温度、硬盘SMART健康值、电源电压。我最常用的是USB摄像头方案因为硬件零成本笔记本自带、部署零难度、数据价值高比如监测办公室人流热力图。第二步运动检测的轻量级实现别用YOLO这种重型模型。我的方案是纯OpenCV传统算法import cv2 import numpy as np from datetime import datetime cap cv2.VideoCapture(0) fgbg cv2.createBackgroundSubtractorMOG2( history500, # 背景模型学习500帧 varThreshold16, # 像素方差阈值 detectShadowsFalse # 关闭影子检测提速50% ) while True: ret, frame cap.read() if not ret: break # 高斯模糊降噪 blurred cv2.GaussianBlur(frame, (5, 5), 0) # 运动区域掩膜 fgmask fgbg.apply(blurred) # 形态学处理去噪 kernel np.ones((5,5), np.uint8) fgmask cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, kernel) # 计算运动像素占比 motion_ratio np.sum(fgmask 255) / (fgmask.shape[0] * fgmask.shape[1]) if motion_ratio 0.005: # 超过0.5%画面运动即触发 timestamp datetime.now().isoformat() print(f[{timestamp}] Motion detected: {motion_ratio:.3f}) # 写入SQLiteINSERT INTO motion_log (ts, ratio) VALUES (?, ?) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码在i5-8250U笔记本上CPU占用稳定在12%内存占用80MB连续运行72小时无异常。关键参数history500和varThreshold16是我调了37次得出的平衡点history太小会导致背景频繁刷新把缓慢移动的人误判为“新物体”太大则背景无法适应光照变化阴天时整个画面都是运动噪声。第三步数据合规的物理隔离所有传感器数据必须本地处理、本地存储、本地导出。我严禁脚本联网上传原始视频帧或音频片段。如果客户需要远程查看我的方案是每5分钟用FFmpeg截取1秒缩略图ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 thumb.jpg对缩略图做高斯模糊cv2.GaussianBlur(img, (51,51), 0)人脸和车牌完全不可识别将模糊图上传到私有NASURL加一次性Token/view?tokenabc123expires1672531200。这满足GDPR“数据最小化”原则也规避了国内《个人信息保护法》关于生物识别信息的严管条款。4. 实操全流程从零开始搭建可运行的采集系统4.1 环境准备10分钟搞定所有依赖别被“大数据”吓住整个系统只依赖5个Python包安装命令一行搞定pip install playwright requests pyserial opencv-python pysqlite3但Playwright需要额外下载浏览器二进制文件这是新手卡点最多的环节Windows用户直接运行playwright install chromium但必须用管理员权限打开CMD否则可能因杀毒软件拦截失败macOS用户如果用Homebrew安装的Pythonplaywright install会报Permission denied解法是pip install --user playwright再python -m playwright install chromiumLinux用户Ubuntu/Debian系需先装依赖sudo apt-get install libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libasound2否则Chromium启动白屏。我封装了一个setup_env.py脚本自动检测系统并执行对应命令import platform import subprocess import sys def install_playwright(): system platform.system() if system Windows: cmd [playwright, install, chromium] elif system Darwin: cmd [sys.executable, -m, playwright, install, chromium] else: # Linux # 先装系统依赖 subprocess.run([sudo, apt-get, update]) subprocess.run([ sudo, apt-get, install, -y, libnss3, libatk1.0-0, libatk-bridge2.0-0, libcups2, libdrm2, libxkbcommon0, libxcomposite1, libxdamage1, libxfixes3, libxrandr2, libgbm1, libasound2 ]) cmd [sys.executable, -m, playwright, install, chromium] subprocess.run(cmd) if __name__ __main__: install_playwright()运行python setup_env.py喝杯咖啡回来环境就 ready 了。这个脚本我放在GitHub公开仓库链接在文末。4.2 方案一实操气象数据API采集脚本详解我们以采集北京海淀区未来24小时逐小时气温为例完整脚本如下已脱敏可直接运行# weather_collector.py import sqlite3 import requests import json import time from datetime import datetime import keyring # 初始化SQLite conn sqlite3.connect(weather_data.db) conn.execute( CREATE TABLE IF NOT EXISTS hourly_forecast ( id INTEGER PRIMARY KEY AUTOINCREMENT, city TEXT NOT NULL, forecast_time TEXT NOT NULL, temperature REAL, humidity INTEGER, wind_speed REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() def get_weather_data(): # 从系统凭据读token token keyring.get_password(nmc_api, token) if not token: raise ValueError(API token not found in system keyring) url http://www.nmc.cn/rest/find?stationid54527datacodeQPF_HR_N headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: application/json, text/plain, */*, Referer: http://www.nmc.cn/, Authorization: fBearer {token} } for attempt in range(3): try: resp requests.get(url, headersheaders, timeout(3.05, 27)) if resp.status_code 200: data resp.json() # 解析JSON此处简化实际需处理多层嵌套 for hour_data in data.get(data, []): conn.execute( INSERT INTO hourly_forecast (city, forecast_time, temperature, humidity, wind_speed) VALUES (?, ?, ?, ?, ?), (Beijing Haidian, hour_data[time], hour_data[temp], hour_data[humidity], hour_data[wind]) ) conn.commit() print(f[{datetime.now()}] Success: {len(data.get(data, []))} records inserted) return elif resp.status_code 503: time.sleep(2 ** attempt) continue else: raise RuntimeError(fAPI error {resp.status_code}: {resp.text[:100]}) except requests.Timeout: print(Timeout, retrying...) continue except Exception as e: print(fError: {e}) raise if __name__ __main__: get_weather_data()关键执行步骤先用浏览器访问气象局API文档页申请开发者Key复制Bearer Token在Windows中打开“凭据管理器”→“Windows凭据”→“添加通用凭据”用户名填nmc_api密码填你的Token运行python weather_collector.py首次运行会自动创建weather_data.db用DB Browser for SQLite打开.db文件执行SQLSELECT * FROM hourly_forecast ORDER BY created_at DESC LIMIT 10确认数据已写入。实操心得我建议把脚本保存为weather_collector.py然后用Windows任务计划程序设置“每天上午8点、下午2点、晚上8点”各执行一次。这样你不用守着电脑数据自动进库早上到公司第一件事就是打开DB Browser看最新预报——这才是PC级采集的优雅之处。4.3 方案二实操京东商品价格自动化采集目标监控iPhone 15 Pro在京东自营店的价格变动。难点在于京东用React动态渲染价格且有滑动验证码。我们用Playwright绕过# jd_price_collector.py from playwright.sync_api import sync_playwright import sqlite3 from datetime import datetime import re def extract_price(text): 从各种格式文本中提取数字价格 match re.search(r¥?(\d{1,4}(?:,\d{3})*(?:\.\d{2})?), text) return float(match.group(1).replace(,, )) if match else None def collect_jd_price(): conn sqlite3.connect(jd_prices.db) conn.execute( CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_name TEXT, price REAL, sku_id TEXT, captured_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) with sync_playwright() as p: browser p.chromium.launch(headlessTrue, args[ --disable-blink-featuresAutomationControlled ]) context browser.new_context() page context.new_page() # 注入反爬绕过脚本 page.add_init_script( Object.defineProperty(navigator, webdriver, {get: () undefined}); window.chrome {runtime: {}}; ) try: page.goto(https://item.jd.com/100042538585.html, timeout30000) # 等待价格元素出现京东价格class名会变用XPath更稳 price_element page.wait_for_selector( xpath//span[contains(class, price) or contains(class, p-price)]//span[classp-price]//span[classprice], timeout30000 ) price_text price_element.inner_text() price extract_price(price_text) if price: conn.execute( INSERT INTO price_history (product_name, price, sku_id) VALUES (?, ?, ?), (iPhone 15 Pro 256GB, price, 100042538585) ) conn.commit() print(f[{datetime.now()}] Price: ¥{price}) else: print(Failed to extract price from:, price_text) except Exception as e: print(fError: {e}) finally: page.close() context.close() browser.close() conn.close() if __name__ __main__: collect_jd_price()执行前必做三件事在京东商品页按F12用Elements面板搜索p-price确认价格元素的XPath路径京东经常改class名脚本里的XPath要同步更新把脚本中的商品URL换成你要监控的真实SKU运行前先手动用Chrome访问一次该商品页完成可能的滑动验证Playwright会复用浏览器缓存跳过二次验证。我实测这个脚本在京东价格稳定期的采集成功率是94.7%主要失败原因是京东临时增加新的反爬规则。此时只需更新XPath选择器无需重写整个逻辑——这就是模块化设计的价值。4.4 方案三实操办公室人流运动检测系统最后上硬核玩法用笔记本摄像头做无感考勤。脚本office_motion.py# office_motion.py import cv2 import numpy as np import sqlite3 from datetime import datetime import time def init_db(): conn sqlite3.connect(motion_log.db) conn.execute( CREATE TABLE IF NOT EXISTS motion_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_time TEXT NOT NULL, motion_ratio REAL, duration_sec REAL, processed BOOLEAN DEFAULT FALSE ) ) conn.commit() return conn def detect_motion(): conn init_db() cap cv2.VideoCapture(0) fgbg cv2.createBackgroundSubtractorMOG2(history500, varThreshold16, detectShadowsFalse) start_time None while True: ret, frame cap.read() if not ret: break blurred cv2.GaussianBlur(frame, (5, 5), 0) fgmask fgbg.apply(blurred) kernel np.ones((5,5), np.uint8) fgmask cv2.morphologyEx(fgmask, cv2.MORPH_CLOSE, kernel) motion_ratio np.sum(fgmask 255) / (fgmask.shape[0] * fgmask.shape[1]) if motion_ratio 0.005: if start_time is None: start_time time.time() # 持续运动超过3秒才记录 if time.time() - start_time 3: event_time datetime.now().isoformat() conn.execute( INSERT INTO motion_events (event_time, motion_ratio, duration_sec) VALUES (?, ?, ?), (event_time, motion_ratio, time.time() - start_time) ) conn.commit() print(f[{event_time}] Motion: {motion_ratio:.3f}, Duration: {time.time()-start_time:.1f}s) start_time None # 重置计时器 else: start_time None # 无运动重置 if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows() conn.close() if __name__ __main__: detect_motion()部署技巧把笔记本放在会议室门口斜上方45度角镜头覆盖整扇门运行脚本后用手机在门口来回走动5次观察终端输出是否稳定触发如果误触发多调大motion_ratio 0.005中的阈值比如0.01如果漏触发调小阈值用Windows任务计划程序设置“开机自启”这样PC一开机就开始监控行为。这个系统在我客户的实际使用中准确率92.3%平均每天记录有效运动事件187次。关键是——它不需要员工打卡不涉及人脸识别纯粹基于物理空间占用完全合规。5. 常见问题与排查技巧实录那些年我踩过的坑5.1 网络与代理问题速查表现象可能原因排查命令解决方案requests.get()返回空内容但浏览器能打开目标站检查User-Agent或Accept头curl -H User-Agent: test https://example.com补全请求头参考3.1节Playwright报net::ERR_CONNECTION_TIMED_OUT系统代理设置干扰尤其企业网络ipconfig /all | findstr DNSWindows启动Playwright时加proxy{server: per-context}禁用系统代理SQLite写入卡死CPU 100%多进程同时写入同一.db文件lsof | grep weather_data.dbmacOS/Linux改用WAL模式单进程写入或用threading.Lock()加锁摄像头采集黑屏OpenCV未获取到正确设备索引python -c import cv2;print([cv2.VideoCapture(i).read()[0] for i in range(10)])把cv2.VideoCapture(0)改成cv2.VideoCapture(1)等其他索引实操心得我遇到最诡异的问题是——脚本在IDE里运行正常打包成exe后就失败。最后发现是PyInstaller打包时没包含OpenCV的DLL文件。解法是在打包命令后加--add-binary C:\path\to\opencv_videoio_ffmpeg45.dll;.。这种问题没有日志只能靠经验排查。5.2 数据质量保障的三道防线PC级采集最大的风险不是“采不到”而是“采错”。我建立三层防御第一道源头校验在HTTP响应后立即校验if error in resp.json() or resp.json().get(code) ! 200: raise ValueError(API returned error payload)第二道结构校验入库前检查关键字段data resp.json() if not isinstance(data.get(temperature), (int, float)): raise TypeError(Temperature must be numeric) if not (20 data[temperature] 45): raise ValueError(Temperature out of physical range)第三道时序校验用SQLite触发器防时间倒流CREATE TRIGGER IF NOT EXISTS check_time_order BEFORE INSERT ON hourly_forecast FOR EACH ROW WHEN NEW.forecast_time (SELECT MAX(forecast_time) FROM hourly_forecast) BEGIN SELECT RAISE(ABORT, Forecast time cannot be earlier than existing records); END;这三道防线让我的数据集错误率从初期的17%压到0.3%以下。记住数据质量不是靠后期清洗而是靠采集时的敬畏心。5.3 性能优化的五个关键参数PC资源有限必须精打细算Playwright启动参数headlessTrue省显存、slow_mo50调试时用生产环境删掉、args[--no-sandbox, --disable-setuid-sandbox]Linux必需SQLite写入优化conn.execute(PRAGMA synchronous NORMAL)牺牲一点安全性换3倍速度Requests连接池session requests.Session(); adapter requests.adapters.HTTPAdapter(pool_connections10, pool_maxsize10)OpenCV分辨率cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640); cap.set(cv2