10美元构建域名搜索引警:SQLite+FastAPI实现50万记录快速检索

发布时间:2026/8/30 2:59:04
10美元构建域名搜索引警:SQLite+FastAPI实现50万记录快速检索 这次我们来看一个很有意思的独立开发项目作者用一个周末的时间、大约 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 LogsHTTPS 证书在签发时会写入公开日志日志里包含大量域名字段。通过解析 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.comname裸域名如 exampletld后缀如 com、net、xyzlength域名长度可用来快速找短域名has_hyphen是否含连字符has_digit是否含数字status注册状态备注可留空source数据来源updated_at更新时间这里再强调一次不要保存不必要的 whois 个人字段。只存“域名是否存在”这样的布尔或枚举状态就能满足大多数搜索场景。4. 技术选型与系统设计50 万条记录是个很微妙的规模。它不能用 Excel 直接打开但也完全不需要分布式搜索引擎。从 10 美元预算和周末开发的约束来看技术选型核心就三个字不折腾。4.1 可选存储方案对比SQLite FTS5 是一个很自然的选项。SQLite 自带全文搜索扩展 FTS550 万条文本数据对它来说压力不大数据库就是一个单文件备份、迁移、部署都非常简单。缺点是 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 都可以。Python3.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 duckdb5.3 目录结构domain-search/ ├── data/ │ ├── domains.csv │ └── domains.db ├── app/ │ ├── __init__.py │ ├── main.py │ ├── search.py │ └── update.py ├── static/ │ └── index.html ├── requirements.txt └── README.md6. 核心功能实现域名导入与搜索这一章给出核心代码模板。实际项目可能有差异但整体思路是通用的。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.pyimport 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, encodingutf-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.com、notely.xyz等结果。包含搜索输入note返回所有域名裸名里包含 note 的记录。TLD 过滤只搜.com或.io。长度过滤短域名优先。搜索模块app/search.pyimport 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 rows50 万条数据下LIKE note%不一定能走标准索引实际响应时间需要在本机测试。如果单次查询能在几百毫秒内返回对个人工具完全够用如果等不了就给name字段建 FTS5 全文索引。FTS5 建表CREATE VIRTUAL TABLE domains_fts USING fts5( domain, name, tld, contentdomains, content_rowidid ); 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.pyfrom fastapi import FastAPI, Query from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from search import search_domains app FastAPI(titleDomain 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(, descriptionTLD 后缀例如 com), limit: int Query(50, ge1, le100) ): results search_domains(keyword, tldtld, limitlimit) return { code: 0, message: ok, data: results } app.post(/api/search) def api_search_post(req: SearchRequest): results search_domains(req.keyword, tldreq.tld, limitreq.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?keywordnotetldio7.2 curl 调用curl http://127.0.0.1:8000/api/search?keywordnotetldiolimit10返回结构{ 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, outputresults.jsonl): results [] for kw in keywords: try: resp requests.get( f{BASE_URL}/api/search, params{keyword: kw, tld: tld, limit: 20}, timeout5 ) 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, encodingutf-8) as f: for item in results: f.write(json.dumps(item, ensure_asciiFalse) \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 VPS1 核、1GB 内存、20GB 存储价格通常在每月几美元。如果你选择 1 核 512MB 的机器跑 FastAPI SQLite 也够用。前端部分可以用 Cloudflare Pages 或 GitHub Pages 免费托管进一步节省 VPS 带宽。8.2 使用 systemd 托管服务写一个 systemd 服务文件/etc/systemd/system/domain-search.service[Unit] DescriptionDomain Search API Afternetwork.target [Service] Userubuntu WorkingDirectory/home/ubuntu/domain-search ExecStart/home/ubuntu/domain-search/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 Restartalways RestartSec3 EnvironmentPYTHONUNBUFFERED1 [Install] WantedBymulti-user.target启用并启动sudo systemctl daemon-reload sudo systemctl enable domain-search sudo systemctl start domain-search查看日志sudo journalctl -u domain-search -f8.3 前端页面前端可以直接写一个静态页面用 Nginx 或 Caddy 反代到 API。简单示例static/index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleDomain Search/title /head body h1域名搜索/h1 input typetext idkw placeholder输入关键词如 note / select idtld