10美元构建域名搜索引警:SQLite+FastAPI实现50万记录快速检索
2026/8/30 2:58:51 网站建设 项目流程

这次我们来看一个很有意思的独立开发项目:作者用一个周末的时间、大约 10 美元的成本,搭建了一套覆盖 50 万条域名记录的搜索引警,面向的是 makers——独立开发者、创客、经常要起项目名和选域名的人。

这类工具解决的痛点非常具体:做产品前要起名字,起完名字要确认域名还在不在,还要看相近的域名是否被抢注。手动去注册商网站一个个查,速度慢、体验差、来回切换页面很折腾。于是就有了这种思路:把几十万条域名记录提前拉进本地数据库,用关键词搜索、后缀过滤、长度筛选来快速缩小候选范围。相比“打开注册商慢慢试”,效率不是一个量级。

值得关注的是这组数字背后的技术取舍:50 万条数据量不算大,但也不是 Excel 能处理的规模;10 美元预算意味着不能上重型基础设施;一个周末开发意味着技术栈必须足够简单、足够顺手。这三个条件组合下来,基本决定了它不会走“Elasticsearch 集群 + 多节点部署”的路线,而是轻量、单机、够用就行的方案。

这篇文章会把这类“小成本域名搜索引警”的完整搭建思路拆开讲一遍:数据从哪来、怎么清洗入库、用什么存储方案、搜索逻辑怎么写、如何把能力包装成 HTTP API,以及部署上线时怎么把成本控制在个位数美元。如果你正在考虑做类似的工具站、内部搜索服务,或者只是想把“搜索”这个能力加进自己的独立开发流程里,这篇文章可以直接收藏。

1. 核心能力速览

先给一张规格表,快速判断这个项目适不适合你。

能力项说明
项目类型域名搜索引警 / 轻量级域名查询工具
数据规模约 50 万条域名记录(项目描述)
核心功能关键词搜索、TLD 后缀过滤、域名长度筛选、状态标注
目标用户makers、独立开发者、创业者、SEO/品牌调研人员
开发成本约 10 美元(项目描述,主要用于服务器和基础设施)
开发周期一个周末(项目描述)
常见技术栈Python + FastAPI + SQLite/DuckDB + 静态前端
支持平台Linux 服务器;开发机 Windows/macOS 均可
是否支持 API支持,HTTP JSON 接口
是否支持批量任务可通过批量脚本或接口循环实现
显存要求不需要 GPU,纯 CPU 应用

注意一点:标题里的 10 美元和周末开发周期,决定了这类项目的核心原则是“轻”。轻量存储、轻量服务、轻量前端,任何需要单独维护集群的组件都不在考虑范围内。

2. 适用场景与使用边界

这个工具到底适合谁?从我的角度理解,它主要服务以下几类场景:

  • 独立开发者在产品立项时快速生成域名候选,不用一个后缀一个后缀去注册商页面验证。
  • 内容站或 SEO 团队批量筛选与品牌词相关的域名,做竞品词和长尾词调研。
  • 域名投资收藏者建立自己的本地域名库,按关键词和长度检索稀有域名。
  • 需要在离线环境批量检查域名记录的运维或运营人员。

它能解决的问题也很明确:一次性导入 50 万条记录,后续搜索都是在本地完成,响应速度远快于挨个查询注册商;把搜索封装成 API 后,可以进一步接到品牌命名工具、落地页生成器或者聊天机器人里;总体维护成本低,放在个人服务器上不会产生额外负担。

但使用边界同样要讲清楚:

  • 它不是通用的互联网搜索引警,不能对标 Google 或 Bing,只能搜索你已经收集到的域名数据。
  • 它不负责实时 whois 查询。数据库里的注册状态是历史的,不能替代注册商实时结果。
  • 它不能作为域名交易、商标注册、法律纠纷的唯一判断依据,最终确认必须回到注册局或注册商页面。
  • 如果数据集来源不合法,或者工具被用于批量抢注和恶意扫描,会带来明显的合规风险。

合规是这个项目最需要注意的部分,尤其是 whois 数据,里面可能包含注册人的个人信息。在多数地区,批量抓取和展示 whois 个人信息会触碰隐私保护规则。更稳妥的做法是只保留“该域名是否处于已注册状态”这样的结果,而不是把注册人、邮箱、电话等字段都存档展示。

3. 数据准备与合规采集

50 万条域名记录不是凭空长出来的,数据来源和清洗链路是整个项目的地基。选错数据源,后面搜索再好也没有用。

3.1 公开域名数据源

常见合法来源有这几种:

  • 证书透明度日志(Certificate Transparency Logs):HTTPS 证书在签发时会写入公开日志,日志里包含大量域名字段。通过解析 CT 日志可以批量提取域名,再经过去重和字段清洗得到可用列表。
  • Common Crawl 公开爬取数据:定期发布的互联网网页快照数据,里面包含海量 URL,提取域名后可以补充很多长尾站点记录。
  • 注册局公开列表:部分 TLD 的注册局会提供 zone file 访问权限,但申请门槛因注册局而异,而且不是所有后缀都能拿到。能拿到的前提下,这是最权威的数据源之一。

这类外部数据源的共同问题是原始文件巨大,需要做大量过滤。比较好的处理流程是:先解析原始数据,去掉端口号、路径、协议前缀,再统一域名格式,最后按主域名维度去重。

3.2 自建爬虫的边界

如果不想完全依赖外部列表,也可以写受限爬虫从公开网页链接中提取域名。但这里有两个明确边界:一是遵守目标网站的 robots 协议和访问频率,不能对注册商、whois 服务做高强度请求;二是不要做大范围端口扫描和子域名爆破,这类行为容易引发安全和法律问题。一个周末时间要完成 50 万条数据收集,建议优先用公开数据集,爬虫只做补充。

3.3 数据清洗字段

拿到原始域名列表后,按以下规则做清洗:

  • 去除重复项。
  • 过滤空域名和含非法字符的记录。
  • 决定是否要保留子域名。域名搜索工具一般只保留注册主域名,即example.com,而不是sub.example.com,这样搜索“example”时结果更干净。
  • 提取 TLD 后缀、域名裸名、域名长度、是否数字域名、是否含连字符等特征字段。
  • 给每条记录标记数据来源和更新时间,便于后续增量更新。

3.4 存储字段设计

建议按这种结构建表:

字段说明
id主键
domain完整域名,如 example.com
name裸域名,如 example
tld后缀,如 com、net、xyz
length域名长度,可用来快速找短域名
has_hyphen是否含连字符
has_digit是否含数字
status注册状态备注,可留空
source数据来源
updated_at更新时间

这里再强调一次:不要保存不必要的 whois 个人字段。只存“域名是否存在”这样的布尔或枚举状态,就能满足大多数搜索场景。

4. 技术选型与系统设计

50 万条记录是个很微妙的规模。它不能用 Excel 直接打开,但也完全不需要分布式搜索引擎。从 10 美元预算和周末开发的约束来看,技术选型核心就三个字:不折腾。

4.1 可选存储方案对比

SQLite + FTS5 是一个很自然的选项。SQLite 自带全文搜索扩展 FTS5,50 万条文本数据对它来说压力不大,数据库就是一个单文件,备份、迁移、部署都非常简单。缺点是 FTS5 的分词和 MATCH 语法需要一些学习成本,而且不太适合复杂的分词场景,但对域名这种前缀/后缀特征明显的文本来说足够了。

DuckDB 适合做分析型查询,列式存储、导入 CSV 很快,适合一次性批量过滤和导出,比如“找出所有 .io 后缀且长度小于 6 的域名”。缺点是需要额外安装 Python 包,不适合高频点查场景。

PostgreSQL + pg_trgm 能做模糊匹配,功能很强,但对一台 512MB 内存的便宜 VPS 来说维护成本偏高,不建议作为首选。

综合来看,如果是自己搭这类工具,优先考虑 SQLite + FTS5,或者更朴素的 SQLite + 普通索引。FTS5 性能更好,普通索引更适合快速上线。

4.2 系统架构建议

一个最小可运行的架构分成四层:

data/source.csv 原始域名数据 data/domains.db SQLite 数据库 app/main.py FastAPI 服务入口 app/search.py 搜索逻辑 app/update.py 数据更新脚本 static/index.html 搜索页面

服务层用 FastAPI,前端用纯静态 HTML,数据层用 SQLite。整条链路上没有消息队列、没有 Redis、没有容器编排。这套设计在 10 美元预算下是合理的。

5. 环境准备与开发环境搭建

先看环境要求。这个项目不依赖 GPU,纯 CPU 应用,服务器成本可以压得很低。

5.1 推荐环境

  • 操作系统:Ubuntu 22.04 或 24.04,开发机用 macOS、Windows 都可以。
  • Python:3.10 及以上。
  • 磁盘:数据库文件加原始 CSV,预留 1GB 足够。
  • 内存:512MB 到 1GB 的 VPS 可以跑,搜索是轻量操作。
  • 数据库:SQLite 自带,不需要额外安装服务。

5.2 初始化项目

mkdir domain-search cd domain-search python3 -m venv venv source venv/bin/activate pip install fastapi uvicorn

如果使用 DuckDB,再装一个依赖:

pip install duckdb

5.3 目录结构

domain-search/ ├── data/ │ ├── domains.csv │ └── domains.db ├── app/ │ ├── __init__.py │ ├── main.py │ ├── search.py │ └── update.py ├── static/ │ └── index.html ├── requirements.txt └── README.md

6. 核心功能实现:域名导入与搜索

这一章给出核心代码模板。实际项目可能有差异,但整体思路是通用的。

6.1 建表

打开 SQLite,创建域名表:

CREATE TABLE IF NOT EXISTS domains ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, name TEXT NOT NULL, tld TEXT NOT NULL, length INTEGER NOT NULL, has_hyphen INTEGER DEFAULT 0, has_digit INTEGER DEFAULT 0, status TEXT, source TEXT, updated_at TEXT ); CREATE INDEX IF NOT EXISTS idx_domains_tld ON domains(tld); CREATE INDEX IF NOT EXISTS idx_domains_length ON domains(length);

6.2 导入 50 万条域名数据

假设数据文件是 CSV 格式,每行一个域名:

domain example.com notely.xyz

导入脚本app/update.py

import csv import sqlite3 from pathlib import Path DB_PATH = Path("data/domains.db") CSV_PATH = Path("data/domains.csv") def normalize_domain(line: str) -> str: line = line.strip().lower() if not line or line.startswith("#"): return None return line.rstrip(".") def main(): conn = sqlite3.connect(DB_PATH) cur = conn.cursor() with open(CSV_PATH, "r", encoding="utf-8") as f: reader = csv.DictReader(f) rows = [] for row in reader: domain = normalize_domain(row.get("domain", "")) if not domain or "." not in domain: continue parts = domain.split(".") name = ".".join(parts[:-1]) tld_part = parts[-1] rows.append(( domain, name, tld_part, len(name), 1 if "-" in name else 0, 1 if any(ch.isdigit() for ch in name) else 0, "unknown", "public-list", "2025-06-01" )) if len(rows) >= 5000: cur.executemany( "INSERT OR IGNORE INTO domains " "(domain, name, tld, length, has_hyphen, has_digit, status, source, updated_at) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", rows ) conn.commit() rows = [] if rows: cur.executemany( "INSERT OR IGNORE INTO domains " "(domain, name, tld, length, has_hyphen, has_digit, status, source, updated_at) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", rows ) conn.commit() cur.execute("SELECT COUNT(*) FROM domains") print("total:", cur.fetchone()[0]) conn.close() if __name__ == "__main__": main()

批量写入 5000 条一次提交,避免 50 万行逐条 insert 导致速度过慢。INSERT OR IGNORE配合唯一约束可以天然去重,重复导入不会产生脏数据。

6.3 搜索逻辑

先实现最基础的两个搜索维度:

  • 前缀搜索:输入note,返回note.comnotely.xyz等结果。
  • 包含搜索:输入note,返回所有域名裸名里包含 note 的记录。
  • TLD 过滤:只搜.com.io
  • 长度过滤:短域名优先。

搜索模块app/search.py

import sqlite3 from pathlib import Path DB_PATH = Path("data/domains.db") def search_domains(keyword: str, tld: str = "", limit: int = 50): if not keyword: return [] conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row cur = conn.cursor() sql = """ SELECT domain, tld, length, status FROM domains WHERE name = ? OR name LIKE ? """ params = [keyword, f"{keyword}%"] if tld: sql += " AND tld = ?" params.append(tld) sql += " ORDER BY length ASC, domain ASC LIMIT ?" params.append(limit) cur.execute(sql, params) rows = [dict(r) for r in cur.fetchall()] conn.close() return rows

50 万条数据下,LIKE 'note%'不一定能走标准索引,实际响应时间需要在本机测试。如果单次查询能在几百毫秒内返回,对个人工具完全够用;如果等不了,就给name字段建 FTS5 全文索引。

FTS5 建表:

CREATE VIRTUAL TABLE domains_fts USING fts5( domain, name, tld, content='domains', content_rowid='id' ); INSERT INTO domains_fts(rowid, domain, name, tld) SELECT id, domain, name, tld FROM domains;

搜索时改用:

SELECT domain, tld FROM domains WHERE id IN ( SELECT rowid FROM domains_fts WHERE name MATCH 'note*' )

FTS5 的 MATCH 语法对点号等字符的处理比较特殊,正式使用前建议先在一千条小数据集上验证,避免匹配结果不符合预期。

7. 搜索引警接口 API 与批量查询

工具做成以后,最有扩展价值的一步是暴露 HTTP API。这样前端可以调用,脚本可以调用,后续接聊天机器人也可以调用。

7.1 FastAPI 接口

app/main.py

from fastapi import FastAPI, Query from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from search import search_domains app = FastAPI(title="Domain Search API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) class SearchRequest(BaseModel): keyword: str tld: str = "" limit: int = 50 @app.get("/api/search") def api_search( keyword: str = Query(..., description="搜索关键词"), tld: str = Query("", description="TLD 后缀,例如 com"), limit: int = Query(50, ge=1, le=100) ): results = search_domains(keyword, tld=tld, limit=limit) return { "code": 0, "message": "ok", "data": results } @app.post("/api/search") def api_search_post(req: SearchRequest): results = search_domains(req.keyword, tld=req.tld, limit=req.limit) return { "code": 0, "message": "ok", "data": results } @app.get("/health") def health(): return {"status": "up"}

启动服务:

uvicorn app.main:app --host 127.0.0.1 --port 8000

启动后,浏览器直接访问:

http://127.0.0.1:8000/api/search?keyword=note&tld=io

7.2 curl 调用

curl "http://127.0.0.1:8000/api/search?keyword=note&tld=io&limit=10"

返回结构:

{ "code": 0, "message": "ok", "data": [ { "domain": "note.io", "tld": "io", "length": 4, "status": "unknown" } ] }

7.3 Python 批量查询脚本

如果要把域名搜索接到批量流程里,比如一次性跑几千个关键词,建议写一个脚本,控制并发和限速:

import requests import time import json BASE_URL = "http://127.0.0.1:8000" def batch_search(keywords, tld="", output="results.jsonl"): results = [] for kw in keywords: try: resp = requests.get( f"{BASE_URL}/api/search", params={"keyword": kw, "tld": tld, "limit": 20}, timeout=5 ) if resp.status_code == 200: data = resp.json().get("data", []) results.append({"keyword": kw, "results": data}) else: results.append({"keyword": kw, "error": resp.status_code}) except Exception as e: results.append({"keyword": kw, "error": str(e)}) time.sleep(0.2) # 限速,避免打满服务 with open(output, "w", encoding="utf-8") as f: for item in results: f.write(json.dumps(item, ensure_ascii=False) + "\n") print("done, total:", len(results)) if __name__ == "__main__": kws = ["note", "memo", "planner", "pixel", "drift"] batch_search(kws)

批量任务里最容易被忽略的是失败重试。接口偶尔会因为网络抖动或资源占用返回 5xx,建议在脚本里加 retry 逻辑:遇到超时或 500,等 1 秒后再试一次,连续失败则把错误写入日志,而不是直接中断整个任务。

7.4 接口安全建议

如果服务要暴露到公网,至少做三件事:

  • 加访问令牌,请求头里带Authorization: Bearer <token>
  • 用限流中间件或 Nginx 层限制单 IP 请求频率。
  • 只开放需要公开的接口,不需要的接口不要暴露。

8. 部署上线与成本控制

10 美元预算怎么分配?核心原则是:能白嫖就白嫖,能用免费开源就不用商业服务,能单文件就单文件。

8.1 服务器选择

这类项目对配置要求很低。常见选择是买一台便宜 Linux VPS,1 核、1GB 内存、20GB 存储,价格通常在每月几美元。如果你选择 1 核 512MB 的机器,跑 FastAPI + SQLite 也够用。前端部分可以用 Cloudflare Pages 或 GitHub Pages 免费托管,进一步节省 VPS 带宽。

8.2 使用 systemd 托管服务

写一个 systemd 服务文件/etc/systemd/system/domain-search.service

[Unit] Description=Domain Search API After=network.target [Service] User=ubuntu WorkingDirectory=/home/ubuntu/domain-search ExecStart=/home/ubuntu/domain-search/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 Restart=always RestartSec=3 Environment="PYTHONUNBUFFERED=1" [Install] WantedBy=multi-user.target

启用并启动:

sudo systemctl daemon-reload sudo systemctl enable domain-search sudo systemctl start domain-search

查看日志:

sudo journalctl -u domain-search -f

8.3 前端页面

前端可以直接写一个静态页面,用 Nginx 或 Caddy 反代到 API。简单示例static/index.html

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Domain Search</title> </head> <body> <h1>域名搜索</h1> <input type="text" id="kw" placeholder="输入关键词,如 note" /> <select id="tld"> <

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询