ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Python爬虫实战:构建中国地震台网数据自动化采集与分析系统

Python爬虫实战:构建中国地震台网数据自动化采集与分析系统 1. 项目概述从数据洪流中捕捉地球的脉搏最近几年无论是行业内的数据分析师还是对公共安全数据感兴趣的个人开发者都越来越关注一个领域如何高效、稳定地获取并利用公开的灾害监测数据。中国地震台网中心CENC官方发布的地震信息因其权威性、实时性和结构化程度高成为了一个非常理想的数据源。这个项目就是围绕“爬取中国地震台网地震数据”展开的一次实战。它远不止是写几行代码把网页上的表格扒下来那么简单背后涉及对公开数据接口的逆向工程、应对反爬策略的攻防、海量时序数据的存储设计以及最终让数据产生价值的分析场景。简单来说这个项目的核心目标是构建一个自动化管道能够7x24小时不间断地从中国地震台网中心的官方数据发布页面抓取全球范围内发生的地震事件信息包括震级、震中位置、发震时刻、深度等关键参数并将其清洗、结构化后存入数据库为后续的地震活动性分析、区域风险评估甚至是一些科普应用提供数据基础。无论你是想研究地震活动的时空分布规律还是为你的智慧城市项目集成灾害预警信息亦或是单纯想做一个实时显示全球地震的“地球心跳”可视化大屏这个项目都能给你提供一个坚实、可靠的起点。我之所以花时间折腾这个是因为在之前的一个区域地质风险评估项目中需要长时间序列、高精度的地震目录数据。市面上的商业数据库要么太贵要么数据维度不全。而官方渠道的数据虽然免费公开但手动下载效率极低且无法实现实时监控。于是自己动手搭建一个稳定可靠的数据爬虫就成了必然选择。在这个过程中我踩了不少坑也总结了一套相对成熟的方案今天就把从思路到实现的完整过程以及那些“教科书上不会写”的实操细节分享给大家。2. 核心思路与架构设计稳健比炫技更重要面对一个官方数据源我们的第一原则必须是合法、合规、友善。中国地震台网中心的数据是面向公众开放的公益数据我们的爬虫行为应当尽可能模拟正常用户的访问避免对服务器造成不必要的压力。因此整个架构设计的核心思想是“稳健”和“可持续”而非追求极致的抓取速度。2.1 数据源分析与选择中国地震台网中心对外提供数据的主要渠道有几个我们需要根据需求进行选择官方数据发布页面这是最常用也是数据最全面的源。页面通常以表格形式列出最新地震事件包含发震时刻、震级、参考位置、深度等字段。数据更新频率高几分钟到几十分钟但页面结构可能随时间调整。数据文件下载服务中心会提供按日、按月打包的地震目录文件如CSV或文本格式。这种方式数据规整适合历史数据批量补全但实时性差。JSON/XML格式的接口通过浏览器开发者工具抓包分析有时可以发现网站背后调用的数据接口。这种接口返回结构化数据JSON是最理想的爬取目标但需要仔细寻找和解析。对于实时爬虫项目我们的主攻方向是第1项和第3项。首先需要人工打开数据发布页面使用浏览器的“检查”Inspect功能切换到“网络”Network选项卡刷新页面观察加载过程中有哪些XHR或Fetch请求。寻找那些返回数据列表的请求其响应体往往就是我们梦寐以求的JSON数据。实操心得不要一上来就硬解析HTML。花半小时仔细分析网络请求如果能找到直接的数据接口后续的开发复杂度会直线下降稳定性和效率也会大幅提升。我曾经发现过一个返回最近24小时地震数据的JSON接口其URL参数规律明显这成为了整个项目的基石。2.2 技术栈选型与考量一个完整的爬虫系统不止是抓取Crawl还包括调度Schedule、解析Parse、存储Store和监控Monitor。以下是经过实践验证的技术栈编程语言Python 3.8。生态丰富是王道。requests用于网络请求BeautifulSoup4或lxml用于HTML解析如果找不到JSON接口pandas用于数据清洗和初步分析sqlalchemy用于数据库操作apscheduler或celery用于定时任务调度。Python几乎为爬虫的每一个环节都提供了成熟的工具。网络请求库Requests 自定义Session。Requests库简单易用足以应对大多数场景。关键在于使用Session对象来保持连接和Cookies并精心设置请求头User-Agent, Referer等让自己看起来更像一个真实的浏览器。解析库优先尝试直接解析JSON。如果幸运地找到了数据接口直接用response.json()即可。如果只能解析HTML则使用lxml因为它的解析速度比BeautifulSoup快得多在需要处理大量页面时优势明显。数据存储关系型数据库PostgreSQL/MySQL。地震数据是典型的时序、结构化数据。关系型数据库在复杂查询、事务支持和数据一致性方面表现更好。我选择PostgreSQL因为它对JSON字段、地理空间数据PostGIS扩展的支持更原生方便后续做空间查询分析。任务调度APScheduler。对于这种“每隔一段时间执行一次”的定时爬取任务APScheduler是一个轻量级且强大的选择。它可以很容易地集成到Python脚本中实现“后台服务”式的定时抓取。部署与监控Docker 日志。将整个爬虫应用容器化便于在不同环境部署。使用Python内置的logging模块记录详细日志包括每次抓取的时间、获取的数据条数、遇到的异常等这是后期排查问题的生命线。架构设计图文字描述整个系统运行在一个定时调度器APScheduler的控制下。调度器每隔N分钟如5分钟触发一次爬取任务。任务执行器首先会向目标数据接口或页面发起HTTP请求获取原始数据JSON/HTML。接着数据解析器将原始数据转化为结构化的Python对象字典列表。然后数据清洗器会处理缺失值、格式化时间字段、统一坐标格式等。清洗后的数据被送入数据存储层与数据库中的已有记录进行比对通过发震时刻和位置判断是否为新事件只插入新增的地震事件。同时整个过程的每一步都会生成日志。系统还需要一个简单的健康检查机制比如连续多次抓取失败则发送报警通知。3. 核心环节实现从请求到落库的完整链路这一部分我们深入到代码层面看看每一个环节具体如何实现以及有哪些需要特别注意的“魔鬼细节”。3.1 请求头伪装与会话管理这是绕过基础反爬机制的第一步。一个过于简单的请求头就像在脸上写着“我是爬虫”。import requests from datetime import datetime import time class EarthquakeSpider: def __init__(self): self.session requests.Session() # 精心构造请求头模仿一个常见的浏览器 self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: application/json, text/javascript, */*; q0.01, # 表明希望接收JSON Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, # 接受压缩节省带宽 Connection: keep-alive, Referer: http://www.ceic.ac.cn/, # 正确设置来源页非常重要 X-Requested-With: XMLHttpRequest, # 暗示这是一个Ajax请求 } self.session.headers.update(self.headers) def fetch_data(self, url, paramsNone): 发起网络请求获取原始数据 try: # 添加随机延迟模拟人类操作避免请求过于频繁 time.sleep(2 random.random()) response self.session.get(url, paramsparams, timeout15) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 # 检查返回内容类型优先按JSON解析 content_type response.headers.get(Content-Type, ) if application/json in content_type: return response.json() else: # 如果不是JSON再按文本处理后续可能用HTML解析 return response.text except requests.exceptions.RequestException as e: self.logger.error(f请求失败: {url}, 错误: {e}) return None注意事项Referer字段非常重要。很多服务器会校验请求是否来自其站内页面。将其设置为数据网站的首页或列表页地址能大大提高成功率。User-Agent可以准备一个列表定期轮换但对于地震台网这种公益网站一个稳定的、常见的UA通常就足够了频繁更换反而可疑。超时设置一定要设置timeout参数如15秒。网络环境复杂没有超时控制的爬虫很容易因为某个请求卡死而耗尽资源。异常处理网络请求的每一步都可能出错连接超时、DNS解析失败、服务器返回404/500等。必须用try...except包裹并进行日志记录不能让整个程序因为一次请求失败就崩溃。3.2 数据解析与清洗假设我们找到了一个返回JSON的接口解析工作就变得非常简单。但清洗工作同样重要它决定了入库数据的质量。import pandas as pd from dateutil import parser # 用于灵活解析各种格式的时间字符串 def parse_and_clean(json_data): 解析JSON数据并清洗 if not json_data or data not in json_data: return [] raw_items json_data[data] # 假设数据在‘data’字段下 cleaned_events [] for item in raw_items: try: event {} # 1. 解析时间 (字段名可能是‘O_TIME’, ‘origintime’等需根据实际情况调整) time_str item.get(O_TIME) if time_str: # 原始时间字符串可能为‘2023-10-27 08:15:32’使用dateutil.parser更稳健 event[origin_time] parser.parse(time_str).strftime(%Y-%m-%d %H:%M:%S) else: continue # 没有时间的记录无效跳过 # 2. 解析震级 (字段名可能是‘M’, ‘magnitude’) mag_str item.get(M, 0.0) try: event[magnitude] float(mag_str) except ValueError: event[magnitude] 0.0 # 解析失败设为0但记录日志 self.logger.warning(f震级解析失败: {mag_str}) # 3. 解析经纬度和深度 event[latitude] float(item.get(EPI_LAT, 0)) event[longitude] float(item.get(EPI_LON, 0)) event[depth] float(item.get(EPI_DEPTH, 0)) # 单位通常是千米 # 4. 解析位置描述 event[location] item.get(LOCATION_C, ).strip() # 5. 生成唯一标识例如时间经纬度的哈希 # 这是一个关键步骤用于后续去重 unique_id hashlib.md5(f{event[origin_time]}_{event[latitude]}_{event[longitude]}.encode()).hexdigest() event[event_id] unique_id cleaned_events.append(event) except Exception as e: self.logger.error(f解析单条数据时出错: {item}, 错误: {e}) continue # 单条解析失败不影响其他数据 return cleaned_events清洗要点时间格式化源数据时间格式可能不统一使用dateutil.parser可以处理大多数情况最终统一为数据库友好的格式如YYYY-MM-DD HH:MM:SS。数值型字段震级、经纬度、深度需转换为float类型并处理可能的缺失值如设为None或默认值。文本字段位置描述等信息去除首尾空格。唯一标识为每条地震事件生成一个唯一ID至关重要。通常使用发震时刻和震中经纬度组合后取哈希值。这是后续去重和更新的依据。异常捕获在清洗每条数据时进行try...except防止一条脏数据导致整批数据丢失。3.3 数据存储与去重策略数据清洗好后就要存入数据库。这里的关键是“去重插入”——只插入数据库中不存在的新事件。import sqlalchemy from sqlalchemy import create_engine, Column, String, Float, DateTime, Text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy.dialects.postgresql import insert Base declarative_base() class EarthquakeEvent(Base): __tablename__ earthquake_events id Column(String(32), primary_keyTrue) # 对应我们生成的event_id origin_time Column(DateTime, nullableFalse, indexTrue) # 加索引便于按时间查询 magnitude Column(Float) latitude Column(Float) longitude Column(Float) depth Column(Float) location Column(Text) created_at Column(DateTime, defaultdatetime.utcnow) # 数据创建时间 class DataStorage: def __init__(self, db_urlpostgresql://user:passwordlocalhost/earthquake_db): self.engine create_engine(db_url) Base.metadata.create_all(self.engine) # 创建表如果不存在 self.Session sessionmaker(bindself.engine) def upsert_events(self, events): 插入或更新事件基于主键event_id去重 if not events: return 0 session self.Session() inserted_count 0 try: # 使用SQLAlchemy Core的Insert...on_conflict_do_nothing语法PostgreSQL # 这种方式比先查询再插入效率高得多 stmt insert(EarthquakeEvent.__table__).values(events) # 如果主键冲突即event_id已存在则什么都不做忽略 stmt stmt.on_conflict_do_nothing(index_elements[id]) result session.execute(stmt) session.commit() inserted_count result.rowcount # 返回实际插入的行数 self.logger.info(f成功插入 {inserted_count} 条新地震事件。) except Exception as e: session.rollback() self.logger.error(f数据入库失败: {e}) finally: session.close() return inserted_count存储策略详解表结构设计除了基本的地震参数created_at字段记录了数据被抓取到我们数据库的时间便于追溯。origin_time字段建立索引因为按时间范围查询是最常见的操作。去重机制这是爬虫存储层的核心。我们使用数据库的“唯一约束冲突忽略”机制来实现高效去重。将我们生成的event_id设为主键当执行插入时如果id已存在数据库会自动忽略这条插入而不会报错。这比“先查询是否存在再决定是否插入”的传统方式性能高出一个数量级尤其是在高频抓取时。使用ORM与Core结合SQLAlchemy的ORM对象关系映射用于定义表结构和简单的查询。但在进行批量插入/更新upsert时使用SQLAlchemy Core的表达式语言通常性能更好也更灵活。事务管理使用try...except...finally确保数据库会话被正确关闭发生错误时进行回滚保证数据一致性。4. 反爬应对与健壮性提升即使是公益网站也可能存在一些基本的访问频率限制或技术屏障。我们的爬虫必须足够“礼貌”和“健壮”。4.1 频率控制与随机化控制请求频率是网络爬虫的基本礼仪也是对自身稳定性的保护。import random import time class PoliteRequester: def __init__(self, base_delay3.0, random_range2.0): self.base_delay base_delay # 基础延迟秒数 self.random_range random_range # 随机延迟范围 self.last_request_time 0 def wait(self): 根据上次请求时间计算并等待合适的间隔 elapsed time.time() - self.last_request_time wait_time self.base_delay random.uniform(0, self.random_range) if elapsed wait_time: time.sleep(wait_time - elapsed) self.last_request_time time.time() # 在爬虫的fetch_data方法调用前先执行requester.wait()策略解析固定的延迟如每秒一次仍然容易被识别为机器行为。加入随机因子random_range使得请求间隔时间在[base_delay, base_delayrandom_range]之间波动更接近人类操作的不确定性。4.2 代理IP池的备用方案对于访问量极大或限制非常严格的网站可能需要使用代理IP。但对于中国地震台网在遵守合理频率的前提下通常不需要。不过作为一个健壮的系统我们可以预留这个接口。class ProxyManager: def __init__(self, proxy_listNone): self.proxies proxy_list or [] # 格式: [http://user:passhost:port, ...] self.current_index 0 def get_proxy(self): if not self.proxies: return None # 不使用代理 proxy self.proxies[self.current_index] self.current_index (self.current_index 1) % len(self.proxies) return {http: proxy, https: proxy} # 在requests.get中传入proxies参数 # response session.get(url, proxiesproxy_manager.get_proxy(), timeout15)注意事项免费代理IP质量极不稳定延迟高、存活时间短。如果必须使用建议搭建一个简单的代理IP健康检查机制定期测试可用性并优先考虑可靠的付费代理服务。4.3 异常重试与电路熔断网络请求充满不确定性一次失败不代表任务终结。我们需要一个重试机制。from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type import requests.exceptions retry( stopstop_after_attempt(3), # 最多重试3次 waitwait_exponential(multiplier1, min2, max10), # 等待时间指数增长 (2s, 4s, 8s...) retryretry_if_exception_type((requests.exceptions.ConnectionError, requests.exceptions.Timeout)), reraiseTrue # 重试次数用尽后抛出原始异常 ) def fetch_data_with_retry(url, session): 带重试机制的请求函数 return session.get(url, timeout15)这里使用了tenacity这个强大的重试库。它允许我们定义复杂的重试策略例如只对连接错误和超时进行重试重试次数最多3次每次重试的等待时间呈指数级增长避免在服务器临时故障时加剧其压力。如果重试耗尽仍失败则向上抛出异常由外层的任务调度器或监控系统捕获。4.4 日志记录与监控告警日志是爬虫的“黑匣子”没有详细的日志线上问题排查将如同大海捞针。import logging import sys def setup_logger(name): logger logging.getLogger(name) logger.setLevel(logging.INFO) # 控制台处理器 ch logging.StreamHandler(sys.stdout) ch.setLevel(logging.INFO) # 文件处理器按日期滚动 from logging.handlers import TimedRotatingFileHandler fh TimedRotatingFileHandler(earthquake_spider.log, whenmidnight, interval1, backupCount7) fh.setLevel(logging.INFO) # 日志格式 formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s) ch.setFormatter(formatter) fh.setFormatter(formatter) logger.addHandler(ch) logger.addHandler(fh) return logger # 在爬虫类中初始化 self.logger setup_logger(EarthquakeSpider)日志要点分级记录INFO级别记录常规操作如“开始抓取”、“成功插入N条数据”WARNING记录可恢复的异常如“某字段解析失败”ERROR记录严重错误如“数据库连接失败”、“连续抓取失败”。输出到文件和控制台方便开发时查看也便于生产环境归档。日志轮转使用TimedRotatingFileHandler可以避免日志文件无限增大自动按天切割并保留最近7天的日志。监控告警可以写一个简单的监控脚本定期扫描日志文件中的ERROR信息或者检查数据库在最近一段时间内是否有新数据入库。如果没有则通过邮件、钉钉、企业微信等渠道发送报警通知。5. 任务调度与自动化部署一个完整的爬虫系统需要能自动、持续地运行。5.1 基于APScheduler的定时调度from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.interval import IntervalTrigger def main_job(): 主要的抓取任务 spider EarthquakeSpider() storage DataStorage() logger setup_logger(Scheduler) logger.info(开始执行地震数据抓取任务...) try: # 1. 获取数据 data spider.fetch_data(target_url) if not data: logger.warning(本次未获取到数据。) return # 2. 解析清洗 events spider.parse_and_clean(data) # 3. 存储入库 count storage.upsert_events(events) logger.info(f任务完成新增{count}条记录。) except Exception as e: logger.error(f抓取任务执行失败: {e}, exc_infoTrue) # exc_infoTrue会打印堆栈跟踪 if __name__ __main__: scheduler BlockingScheduler() # 每5分钟执行一次 trigger IntervalTrigger(minutes5) scheduler.add_job(main_job, trigger, idearthquake_crawl_job) logger.info(地震数据爬虫调度器已启动按 CtrlC 退出。) try: scheduler.start() except (KeyboardInterrupt, SystemExit): logger.info(调度器被手动停止。)BlockingScheduler会阻塞当前进程适合将爬虫脚本作为一个独立的守护进程运行。在生产环境中你可能希望使用BackgroundScheduler以便集成到Web应用或其他更大的系统中。5.2 使用Docker容器化部署将整个爬虫环境打包进Docker容器可以确保运行环境的一致性简化部署流程。Dockerfile示例FROM python:3.9-slim WORKDIR /app # 安装系统依赖如PostgreSQL客户端库 RUN apt-get update apt-get install -y \ gcc \ libpq-dev \ rm -rf /var/lib/apt/lists/* # 复制依赖文件并安装Python包 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 复制应用代码 COPY . . # 设置环境变量如数据库连接字符串 # ENV DATABASE_URLpostgresql://... # 运行命令 CMD [python, scheduler_main.py]docker-compose.yml示例如果包含数据库version: 3.8 services: postgres: image: postgres:13 environment: POSTGRES_DB: earthquake_db POSTGRES_USER: user POSTGRES_PASSWORD: your_strong_password volumes: - postgres_data:/var/lib/postgresql/data ports: - 5432:5432 crawler: build: . depends_on: - postgres environment: DATABASE_URL: postgresql://user:your_strong_passwordpostgres/earthquake_db restart: unless-stopped # 容器意外退出时自动重启 logging: driver: json-file options: max-size: 10m max-file: 3 volumes: postgres_data:使用docker-compose up -d即可一键启动数据库和爬虫服务。restart: unless-stopped保证了服务的持续运行。6. 数据应用与扩展思路数据抓取下来不是终点而是起点。有了稳定、干净的数据流我们可以做很多事情。6.1 基础可视化实时地震地图使用Folium或Plotly等库可以轻松地将地震数据展示在地图上。一个简单的思路是定期如每小时从数据库中查询最近24小时内的地震事件根据震级大小决定地图上标记点的半径和颜色生成一个静态的HTML地图页面并通过Web服务器如Nginx对外提供访问。import folium from sqlalchemy import create_engine import pandas as pd def generate_map(): engine create_engine(postgresql://...) # 查询最近24小时的数据 df pd.read_sql( SELECT latitude, longitude, magnitude, location, origin_time FROM earthquake_events WHERE origin_time NOW() - INTERVAL 24 hours ORDER BY origin_time DESC , engine) # 创建以中国为中心的地图 m folium.Map(location[35, 105], zoom_start4) for _, row in df.iterrows(): # 震级越大标记点越大颜色越深如红色 radius row[magnitude] * 2 color red if row[magnitude] 5.0 else orange if row[magnitude] 3.0 else blue popup_text f 时间: {row[origin_time]}br 震级: {row[magnitude]}br 位置: {row[location]}br 深度: {row.get(depth, N/A)} km folium.CircleMarker( location[row[latitude], row[longitude]], radiusradius, colorcolor, fillTrue, fillOpacity0.6, popuppopup_text ).add_to(m) m.save(/var/www/html/earthquake_map.html) # 保存到Web服务器目录6.2 简单分析地震活动性统计利用pandas和数据库的聚合查询可以轻松实现一些统计分析每日/每周/每月地震频次统计。不同震级区间的分布统计。主要地震带的活跃度分析如环太平洋地震带、欧亚地震带。震源深度分布直方图。这些统计结果可以定期生成报告如CSV文件或简单的HTML页面帮助快速把握全球地震活动态势。6.3 扩展方向预警与集成阈值告警在爬虫解析数据后立即判断震级是否超过某个阈值例如周边地区5.0级以上全球7.0级以上如果超过则立即触发告警发送邮件、短信或调用消息机器人API。数据API服务搭建一个简单的Flask或FastAPI应用将数据库中的数据通过RESTful API对外提供供其他系统或小程序调用。可以设计查询接口如按时间范围、地理位置边界、震级范围查询地震事件。与GIS系统集成将地震数据表与PostGIS扩展结合实现更复杂的空间查询例如“查找过去一个月内距离某城市200公里范围内所有3级以上地震”。7. 常见问题与排查实录在实际运行中你几乎一定会遇到下面这些问题。这里是我的排查笔记。7.1 问题突然抓不到数据了返回空列表或错误页面。排查步骤手动访问第一时间用浏览器打开目标页面确认网站是否可正常访问数据格式是否已变更。这是最直接有效的方法。检查日志查看爬虫日志中的请求URL和响应状态码。如果是403 Forbidden或429 Too Many Requests说明触发了反爬。对比请求头使用浏览器开发者工具抓取一次正常访问的请求仔细对比你的爬虫请求头与浏览器请求头的差异。重点检查User-Agent,Referer,Cookie,Accept等字段。网站可能加强了对Referer或特定Cookie的校验。分析页面结构如果之前是解析HTML现在页面结构可能变了。重新分析DOM树更新XPath或CSS选择器。寻找新接口再次使用开发者工具抓包看是否有新的JSON接口出现。数据源迁移接口是常有的事。我的案例有一次网站将数据从直接渲染在HTML中改为了通过JavaScript动态加载。原来的解析方法立刻失效。解决方案是要么使用Selenium或Playwright这类能执行JS的浏览器自动化工具要么更仔细地抓包找到了一个新的、带有时效性Token的Ajax接口。我选择了后者因为效率更高。7.2 问题数据解析出错某些字段为None或格式异常。排查步骤打印原始数据在解析函数中将出错的单条item原始数据完整打印到日志或控制台。检查字段名确认你代码中使用的字段名如item.get(M)是否与JSON响应中的键名完全一致大小写、下划线。网站更新可能微调了字段名。处理数据多样性有些地震事件可能缺少某些字段如深度depth未知。你的清洗代码必须能优雅地处理缺失值为其设置合理的默认值如None或0而不是直接崩溃。类型转换容错在将字符串转为float或int时务必使用try...except包裹因为源数据可能是-、null或空字符串。7.3 问题数据库连接失败或插入速度慢。排查步骤检查连接字符串确认数据库IP、端口、用户名、密码、数据库名是否正确。网络策略如云服务器的安全组是否允许访问。检查数据库状态登录数据库查看服务是否运行磁盘空间是否已满。优化插入如果一次性插入的数据量很大比如补全历史数据使用上面提到的insert().on_conflict_do_nothing()批量操作而不是逐条INSERT。对于MySQL可以使用INSERT IGNORE INTO ...或ON DUPLICATE KEY UPDATE语法。建立索引确保在经常用于查询和去重比对的字段上建立了索引如origin_time,event_id。但注意索引会降低插入速度需权衡。7.4 问题爬虫运行一段时间后内存占用越来越高。排查步骤检查循环引用在长时间运行的爬虫中如果创建了大量对象且存在循环引用Python的垃圾回收器可能无法及时释放内存。确保在大的循环中临时变量能及时被回收。使用生成器如果处理的数据流很大考虑使用生成器yield来逐条处理数据而不是一次性将所有数据加载到内存的列表中。分批次处理对于批量补全历史数据这种任务一定要分页或分批次请求和插入比如一次处理1000条。监控工具使用memory_profiler等工具对代码进行逐行内存分析找到内存泄漏的元凶。这个项目从构思到稳定运行我花了大约一周的业余时间。最大的感触是爬虫工程的难点往往不在“爬”本身而在于如何让这个自动化系统像钟表一样可靠、持续地运行下去并能优雅地应对各种外部变化。它考验的是开发者的系统工程思维和问题排查能力。希望这份详细的复盘能帮你少走些弯路更快地搭建起属于自己的地球脉搏监听器。数据在手接下来的分析和应用就是展现你创造力的时刻了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进