游客发表
用 Python 写一个能跑起来的何用网页搜索引擎,核心代码不超过 200 行。制作别被“搜索引擎”四个字吓到——你不需要处理全网百亿页面,网页只要索引几千个网页,搜索用 Flask 加 SQLite 就能搭出可用的引擎原型。
先解决爬虫。何用requests 加 BeautifulSoup 是制作最省事的组合,但得注意两点:一是网页设置 User-Agent,否则很多站点直接返回 403;二是搜索用 urljoin 处理相对链接,不然爬两页就断了。引擎我试过用 Scrapy,何用功能强但配置繁琐,制作对小型项目反而拖慢节奏。网页下面这段代码爬取指定域名下的搜索所有页面,限制深度为 2:
import requests from bs4 import BeautifulSoup from urllib.parse import urljoin,引擎 urlparse def crawl(start_url, max_depth=2): visited = set() queue = [(start_url, 0)] pages = [] while queue: url, depth = queue.pop(0) if url in visited or depth > max_depth: continue visited.add(url) try: resp = requests.get(url, timeout=5, headers={ 'User-Agent': 'Mozilla/5.0'}) soup = BeautifulSoup(resp.text, 'html.parser') text = soup.get_text(separator=' ', strip=True) pages.append({ 'url': url, 'text': text}) for link in soup.find_all('a', href=True): full = urljoin(url, link['href']) if urlparse(full).netloc == urlparse(start_url).netloc: queue.append((full, depth + 1)) except Exception as e: print(f'跳过 { url}: { e}') return pages 索引部分决定搜索质量。最朴素的做法是把每个页面的文本拆成词,建立“词 → 页面 ID”的倒排表。中文需要分词,jieba 够用;英文按空格和标点切分即可。别用 TF-IDF 的完整实现,先做布尔检索——只要页面包含查询词就命中。我在本地测试时发现,500 个页面用纯 Python 字典建索引,内存占用不到 20MB,查询延迟在 10ms 以内。
import jieba from collections import defaultdict def build_index(pages): index = defaultdict(set) for pid, page in enumerate(pages): words = set(jieba.cut(page['text'])) for w in words: if len(w.strip()) > 1: index[w].add(pid) return index 查询接口用 Flask 暴露。用户输入“Python 爬虫”,先分词得到 ['Python', '爬虫'],然后取两个词对应页面集合的交集。如果交集为空,退化成并集并按页面长度排序——长页面通常覆盖更多相关内容。这里有个坑:jieba 默认把英文也当整体,所以“Python”不会被拆开,反而省事。
from flask import Flask, request, jsonify app = Flask(__name__) index = { } pages = [] @app.route('/search') def search(): q = request.args.get('q', '') words = [w for w in jieba.cut(q) if len(w.strip()) > 1] if not words: return jsonify([]) sets = [index.get(w, set()) for w in words] result = set.intersection(*sets) if all(sets) else set.union(*sets) ranked = sorted(result, key=lambda pid: len(pages[pid]['text']), reverse=True)[:10] return jsonify([{ 'url': pages[pid]['url'], 'snippet': pages[pid]['text'][:200]} for pid in ranked]) 跑起来只需三行:爬取、建索引、启动服务。实际体验中,搜索“Flask 路由”能准确返回相关页面,但同义词问题很明显——搜“网页抓取”匹配不到“爬虫”页面。解决办法是加一层同义词映射,或者引入 word2vec 做向量检索,不过那是另一个量级的工作了。
最后提醒:别对公网大规模爬取,容易封 IP 甚至惹麻烦。本地文档、公司内部 wiki、个人博客归档,这些场景才是这个原型的最佳用武之地。代码我放在 gist 上,改改就能用。
友情链接