Giới thiệu
Gần đây, tôi đã nảy ra một ý tưởng khá đau đầu: "Hãy xây dựng một công cụ tìm kiếm từ đầu. Nó có khó khăn đến mức nào?" Kết quả là... nó rất khó.
Tôi vẫn đang trong quá trình thực hiện, nhưng tôi muốn chia sẻ những rào cản, sai lầm và những cơn đau đầu mà tôi gặp phải khi cố gắng ghép nối một crawler, parser, indexer và frontend thành một thứ gì đó hoạt động giống như Google, nhưng tệ hơn nhiều.
Kiến trúc mà tôi nghĩ là hợp lý
Tôi muốn dự án này có tính mô-đun để từng phần có thể hoạt động độc lập và thậm chí có thể mở rộng riêng. Dưới đây là phân bổ sơ bộ:
- Crawler (Python): thu thập dữ liệu từ các trang web.
- Parser (Go): trích xuất HTML/văn bản hữu ích.
- Indexer (Go): xây dựng chỉ mục tìm kiếm.
- Search API (Python): xử lý truy vấn.
- Frontend (Vue.js): nơi người dùng nhập truy vấn.
- Database (PostgreSQL): giữ tất cả lại với nhau.
Nghe có vẻ sạch sẽ, phải không? Nhưng thực tế thì khác xa.
Crawler (Python)
Kỳ vọng
"Tôi chỉ cần sử dụng requests và BeautifulSoup. Dễ thôi."
Thực tế
- Xử lý
robots.txtphức tạp hơn tôi nghĩ. - Tôi đã gặp phải các vòng lặp vô hạn (thu thập dữ liệu cùng một trang nhiều lần).
- Một số trang web chặn crawler trừ khi bạn giả mạo header.
- Còn có cả giới hạn tốc độ... Tôi đã từng làm hỏng WiFi của mình.
Tôi nhận ra rằng việc thu thập dữ liệu là một cái hố sâu đầy các trường hợp biên. Đột nhiên, tôi hiểu tại sao các công ty chi hàng triệu chỉ để duy trì các crawler.
Phân tích trong Go
Tại sao lại chọn Go?
Thực lòng mà nói, tôi muốn hiệu suất và khả năng xử lý đồng thời tốt hơn.
Những khó khăn gặp phải
- Phân tích HTML trong Go không đơn giản như trong Python.
- Các thư viện cảm giác cơ bản, điều này tốt cho tốc độ nhưng không dễ sử dụng.
- Đối phó với HTML bị hỏng là một cơn ác mộng vì web rất lộn xộn.
- Ngoài ra, việc liên tục chuyển đổi giữa Python và Go cũng rất mệt mỏi.
Lập chỉ mục trong Go
Tôi nghĩ phần này sẽ thú vị. Thay vào đó, tôi đã gặp phải:
- Phân tách từ (tokenization) giữa các ngôn ngữ khác nhau. Tôi có thực sự muốn hỗ trợ tìm kiếm tiếng Nhật ngay bây giờ không? Không.
- Lưu trữ các chỉ mục đảo ngược một cách hiệu quả.
- Thiết kế các truy vấn mà không dẫn đến quét toàn bộ bảng trong PostgreSQL.
Hóa ra việc viết một công cụ tìm kiếm liên quan đến việc tái phát minh nhiều bánh xe mà ElasticSearch đã giải quyết từ nhiều năm trước. Nhưng này, đó là một trải nghiệm học hỏi, đúng không?
Xử lý truy vấn tìm kiếm
Đây là nơi tôi đang gặp khó khăn.
- Làm thế nào để tôi cân bằng điểm số tính liên quan?
- Tôi nên sử dụng TF-IDF, BM25, hay chỉ trả về những gì khớp?
- Phân trang có vẻ đơn giản cho đến khi bạn nhận ra rằng các truy vấn cần giữ nguyên trong suốt quá trình làm mới.
Ngoài ra, tôi đã chọn sử dụng FastAPI cho điểm cuối tìm kiếm vì nó nhanh và hiện đại. Thực tế là:
Việc viết các route thì ổn, nhưng quản lý mã đồng bộ và không đồng bộ với indexer Go là khá rối.
Mỗi thay đổi nhỏ đều khiến tôi phải khởi động lại mọi thứ chỉ để kiểm tra xem API của tôi có liên lạc với index đúng cách không.
Tài liệu khiến nó trông đơn giản, nhưng khi tôi gặp phải các vấn đề thực tế như vấn đề CORS, quirk trong serial hóa JSON, hay các gọi DB bị chặn, tôi chỉ ngồi đó nhìn màn hình.
Hiện tại, kết quả tìm kiếm là ngẫu nhiên nhất có thể. Nếu bạn tìm kiếm "Python", bạn có thể thấy các bài viết về nấu ăn. Tôi gọi đó là tìm kiếm sáng tạo.
Frontend với Vue.js
Thực lòng mà nói, đây là phần dễ nhất. Vue khiến việc xây dựng thanh tìm kiếm và trang kết quả trở nên thú vị. Khó khăn thực sự nằm ở backend lúc này.
Frontend cho thấy rõ sự kém cỏi của backend. Không gì đáng thất vọng hơn việc nhập một truy vấn và thấy dự án của mình thất bại.
Cuộc phiêu lưu với PostgreSQL
Tôi rất thích Postgres, nhưng:
- Quyết định những gì nên đưa vào cơ sở dữ liệu so với những gì nên giữ lại trong bộ nhớ đã gặp khó khăn.
- Tôi đã phải đặt lại và xây dựng lại schema khoảng mười lần.
- Tìm kiếm toàn văn trong Postgres là khá ổn, nhưng việc kết hợp nó với indexer Go tùy chỉnh của tôi thì khá rối.
Những điều tôi đã học được cho đến nay
- Tìm kiếm là khó. Thực sự khó.
- Tối ưu hóa sớm giết chết động lực. Tôi có lẽ không cần Go cho việc phân tích và lập chỉ mục ngay bây giờ.
- Web là hỗn loạn. Crawlers cho thấy nhiều website bị hỏng như thế nào.
- Thật tốt khi đứng trên vai của những gã khổng lồ. ElasticSearch, Solr, Meilisearch, họ tồn tại vì lý do gì đó.
Vẫn gặp khó khăn với...
- Làm cho việc lập chỉ mục đủ nhanh mà không tiêu tốn quá nhiều bộ nhớ.
- Điểm số tính liên quan không cảm thấy ngẫu nhiên.
- Giữ cho các mô-đun giao tiếp với nhau một cách trơn tru (Python tới Go tới Postgres tới Vue).
- Động lực, thực sự. Đôi khi tôi tự hỏi liệu tôi có nên chỉ xây dựng một ứng dụng quản lý công việc.
Kết luận
Dù có thất vọng, tôi đã học được nhiều hơn về cách các công cụ tìm kiếm thực sự hoạt động trong vài tuần qua hơn là tôi từng làm chỉ bằng cách sử dụng chúng.
Nếu bạn đang cân nhắc việc tự xây dựng một cái, hãy làm điều đó vì trải nghiệm học hỏi, không chỉ cho sản phẩm cuối cùng. Hoặc ít nhất, đừng mong đợi vượt qua Google chỉ với một nỗ lực hackathon cuối tuần.