跳到主内容
快讯直播
AI智模界
教程

用 Claude Code 挖星表:筛出疑似新天体

这篇能做出什么

先说结论:照着走完,你会得到一份 CSV 候选表,每行是一个天体,带坐标、星等、颜色、自行、变光周期,以及"是否已被已知星表收录"的标记。表里通常会有几十到几百个对象,其中绝大多数是已知天体、伪影或重复记录,剩下少数几个值得进一步看一眼——这就是"疑似"两个字的重量。

具体产出:

  • 一个可复现的 Python 项目,查询、清洗、匹配、出图四个脚本分开
  • 一份从天区查询到候选筛选的流水线
  • 一张候选表 candidates.csv
  • 一套人工复核清单,帮你把假阳性挡在报告之前

需要说明的是:业余或个人环境下做出的候选,几乎不可能直接变成"新发现"。真正的确认要靠专业台站的光谱和多年观测。把它当成一次完整的数据挖掘演练,心态会好很多。

前置条件清单

环境

  • Python 3.10 以上(具体版本以 Python 官方文档当前版本为准)
  • pip 可用的虚拟环境
  • 内存 4 GB 以上,磁盘留 1 GB 以上(TESS 光变曲线文件会占空间)
  • 能访问 CDS/VizieR、Gaia Archive、MAST 这几个数据服务(部分网络环境需要配置代理)

Python 包

```bash

python -m venv .venv

source .venv/bin/activate # Windows 用 .venv\Scripts\activate

pip install astropy astroquery pandas numpy matplotlib lightkurve

```

Claude Code

安装方式看官方文档,版本以官方文档当前版本为准。它能读写你项目里的文件、执行 shell 命令,所以务必在一个专用目录里开工,别在 home 目录裸奔。

知识准备

不需要天文专业背景。只要知道:星表就是一张大表格,每行一个天体,列是坐标(ra/dec,单位度)、亮度(星等,数值越小越亮)、颜色(两个波段星等之差)。坐标匹配是整件事的核心动作。

第 1 步:给 Claude Code 立规矩

很多人一上来就让 Claude Code "写个脚本找新天体",结果得到的代码看起来专业、跑起来报错。正确做法是先写一份 CLAUDE.md 放在项目根目录,把约束讲清楚。

```markdown

项目:星表候选天体筛选

目标

从 Gaia 公开星表取一个天区,交叉匹配已知变星表,输出候选列表。

硬性约束

  • 只用 astropy / astroquery / pandas / numpy / lightkurve
  • 每个脚本必须能独立运行,且写出文件到 data/ 或 output/
  • 不要在脚本里硬编码 API key
  • 任何查询都要有 TOP 限制或半径限制,禁止全表扫描
  • 输出统一用 ECSV 或 CSV,列名保持原样不要重命名

工作方式

  • 改代码前先说明改动理由
  • 每写完一个脚本,立刻运行一次并贴出结果
  • 报错不要猜,先看完整 traceback

```

这份文件的作用是让 Claude Code 的每次输出都落在同一个框架里,而不是每次重新发明一套写法。

第 2 步:搭骨架

让 Claude Code 建目录,或者自己敲:

```

astro-dig/

├── CLAUDE.md

├── data/ # 原始星表

├── output/ # 中间结果与图

├── 01_fetch_gaia.py

├── 02_clean_features.py

├── 03_crossmatch.py

├── 04_lightcurve.py

└── 05_report.py

```

提示词可以这样写:

```

读 CLAUDE.md。创建 01_fetch_gaia.py:

用 astroquery.gaia 查询 Gaia DR3,天区中心 ra=210.0, dec=54.0,半径 0.5 度,

G 星等限制在 12 到 16 之间,取 TOP 5000。

需要的列:source_id, ra, dec, parallax, parallax_error, pmra, pmdec,

phot_g_mean_mag, bp_rp, ruwe, astrometric_excess_noise。

结果写到 data/gaia_field.ecsv。打印行数和前 5 行。

写完直接运行给我看结果。

```

生成的脚本大致长这样:

```python

from astroquery.gaia import Gaia

from astropy.table import Table

QUERY = """

SELECT TOP 5000

source_id, ra, dec, parallax, parallax_error,

pmra, pmdec, phot_g_mean_mag, bp_rp,

ruwe, astrometric_excess_noise

FROM gaiadr3.gaia_source

WHERE 1 = CONTAINS(

POINT('ICRS', ra, dec),

CIRCLE('ICRS', 210.0, 54.0, 0.5))

AND phot_g_mean_mag BETWEEN 12 AND 16

"""

job = Gaia.launch_job_async(QUERY)

tbl = job.get_results()

tbl.write("data/gaia_field.ecsv", overwrite=True)

print(len(tbl))

print(tbl[:5])

```

跑通再往下走。如果这一步就报网络错误,先解决网络,别急着写后面的逻辑。

第 3 步:清洗与特征构造

原始星表里有一堆要看一眼才知道能不能用的列。让 Claude Code 写 02_clean_features.py,做这几件事:

```python

import numpy as np

from astropy.table import Table

t = Table.read("data/gaia_field.ecsv")

视差信噪比:小于 5 的视差基本不可信

t["parallax_snr"] = t["parallax"] / t["parallax_error"]

自行总模

t["pm_total"] = np.hypot(t["pmra"], t["pmdec"])

颜色缺失的直接丢掉

t = t[np.isfinite(t["bp_rp"])]

常用经验阈值:RUWE 明显高于 1.4 往往意味着非单星解

t["ruwe_flag"] = t["ruwe"] > 1.4

t.write("output/gaia_features.ecsv", overwrite=True)

print(f"保留 {len(t)} 行")

```

这里有个要点:ruwe > 1.4 是社区常用的经验线,不是判据。它筛选出的是"值得看一眼"的对象,不是"就是双星"。让 Claude Code 把这类阈值集中写在文件开头,方便你后面调。

第 4 步:交叉匹配已知星表

这一步是整个流程里最有价值的部分。用 VizieR 查 AAVSO 的变星表(B/vsx/vsx),再用 astropy 做球面匹配。

```python

import astropy.units as u

from astropy.coordinates import SkyCoord

from astropy.table import Table

from astroquery.vizier import Vizier

t = Table.read("output/gaia_features.ecsv")

viz = Vizier(row_limit=-1)

center = SkyCoord(ra=210.0 * u.deg, dec=54.0 * u.deg, frame="icrs")

cats = viz.query_region(center, radius=0.5 * u.deg,

catalog=["B/vsx/vsx"])

known = cats[0]

print(f"已知变星 {len(known)} 颗")

src = SkyCoord(ra=t["ra"], dec=t["dec"], frame="icrs")

kn = SkyCoord(ra=known["RAJ2000"], dec=known["DEJ2000"], frame="icrs")

idx, sep, _ = src.match_to_catalog_sky(kn)

t["vsx_match_sep_arcsec"] = sep.arcsec

t["vsx_matched"] = t["vsx_match_sep_arcsec"] < 3.0

cand = t[~t["vsx_matched"]]

cand.write("output/candidates.csv", overwrite=True)

print(f"未匹配已知变星:{len(cand)} 个")

```

匹配半径取 3 角秒是个折中。半径太小会漏掉真实对应体(不同星表位置精度不同),太大会把邻居错认成同一个源。可以先画一张 sep 的直方图,看看分布再定。

第 5 步:加一层光变验证

坐标匹配只能说明"不在已知表里",不能说明"真的有变化"。用 TESS 的公开光变曲线补上这一环。这一步需要先知道目标的 TIC 编号,可以在 MAST 的 TIC 星表里按坐标查。

```python

import lightkurve as lk

tic_id = "TIC 123456789" # 换成候选表里的真实编号

sr = lk.search_lightcurve(tic_id, mission="TESS")

if len(sr) > 0:

lc = sr.download_all().stitch().remove_nans().normalize()

pg = lc.to_periodogram(method="lombscargle", normalization="amplitude")

period = 1.0 / pg.frequency_at_max_power

print(f"主周期约 {period:.5f} 天")

pg.plot().savefig("output/periodogram.png")

```

把每个候选都跑一遍会很慢,建议先挑一二十个出来,人工过一遍。周期图有明显尖峰的才值得继续。

第 6 步:人工复核清单

在把任何东西写进报告之前,逐个回答这些问题:

1. 坐标附近有没有亮得多的邻居?点扩散函数溢出会造成假变光。

2. 该源在 Gaia 里 parallax_snr 是多少?小于 5 就别谈距离。

3. ruwe 是不是高得离谱(比如超过 2)?那可能是解算失败而非物理双星。

4. 在 SIMBAD 里按坐标查一次,看看有没有文献提到过。

5. 用 ESASky 或 Aladin 之类的工具看一眼图像,确认不是星系或伪源。

6. TESS 光变曲线有没有数据缺口模式和数据完全对齐的迹象?那通常是仪器效应。

常见坑与排错

TAP 查询超时:半径缩小、加 TOP、用 launch_job_async 而不是同步查询。Gaia 的表是全量扫描,不加限制会被拒。

单位搞混:VizieR 返回的列名带下划线前缀(如 _RAJ2000)和普通列(如 RAJ2000)含义不同,前者是匹配后的位置。混用会导致几十角秒的偏差。让 Claude Code 打印前几行和前几列的 unit,确认再继续。

历元不一致:Gaia 的位置对应特定参考历元,高自行天体在不同历元的位置会差好几角秒。如果候选自行很大,先按自行把坐标外推到同一历元再匹配。

列为空:bp_rp、parallax 在某些源上是 NaN,不做过滤会污染后续统计。用 np.isfinite 统一处理。

Claude Code 编出看起来对的 API:这是高频问题。函数名、参数名都像真的,但模块里没有。对策是让它在写完立刻运行,报错就把完整 traceback 贴回去,它会自己改。别接受"应该可以运行"这种说法。

下载限速:MAST 批量下载会触发速率限制。加 sleep,或者一次性下载少量目标。

下一步建议

  • 扩大时域数据:ZTF、ASAS-SN 的公开数据能覆盖更长的时间基线,对找长周期变光更有用。
  • 换筛选思路:当前流程是"亮 + 不在已知表里"。也可以反过来,从高自行、视差异常、色指数异常这些维度找样本。
  • 上异常检测:把特征矩阵丢给孤立森林或自编码器,让模型告诉你哪些源"长得不像邻居"。
  • 走正规提交渠道:如果确实找到一个稳定周期、多次观测一致的候选,可以按 AAVSO 的变星提交流程报过去,或者写成观测提案。流程细节以相关机构官方页面为准。
  • 把流程做成模板:把天区半径、星等范围、匹配阈值都抽成配置文件,下次换天区只改参数,不动代码。

整个流程跑下来,你收获的与其说是一个天体,不如说是一套能反复使用的数据挖掘方法:定问题、拉数据、构造特征、交叉验证、人工复核。这套东西换个数据集照样能用。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。