# Intra-Mart Documentation Crawler → Markdown Exporter ## 🚀 Quickstart với [`uv`](https://docs.astral.sh/uv/) (khuyến nghị) Project đã được lock vào `uv.lock` + ghim Python qua `.python-version`. `uv` sẽ tự cài đúng phiên bản Python, dựng `.venv`, và sync deps từ lock file — không cần `pip`, không cần `pyenv`. ### 1. Cài `uv` | OS | Lệnh | | ------------------- | --------------------------------------------------------------------------------- | | **macOS / Linux** | `curl -LsSf https://astral.sh/uv/install.sh \| sh` | | **Windows (PowerShell)** | `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 \| iex"` | | **macOS (Homebrew)** | `brew install uv` | | **Bất kỳ OS nào có pipx** | `pipx install uv` | Xác nhận đã cài: `uv --version`. ### 2. Cài Pandoc (bắt buộc, không nằm trong `uv`) `MarkdownConverter` gọi binary `pandoc` qua subprocess, nên Pandoc phải có sẵn trên `PATH`. | OS | Lệnh | | ------------------------ | --------------------------------------------------- | | **macOS (Homebrew)** | `brew install pandoc` | | **Ubuntu / Debian** | `sudo apt install pandoc` | | **Fedora / RHEL** | `sudo dnf install pandoc` | | **Arch** | `sudo pacman -S pandoc` | | **Windows (winget)** | `winget install --id JohnMacFarlane.Pandoc` | | **Windows (Chocolatey)** | `choco install pandoc` | | **Khác** | Tải installer tại | Xác nhận: `pandoc --version`. ### 3. Clone & sync deps ```bash git clone https://github.com/IricsDo/intra_mart_doc_crawler.git cd intra_mart_doc_crawler uv sync # tạo .venv, cài đúng Python 3.11 + toàn bộ deps từ uv.lock uv run playwright install chromium # tải Chromium cho Playwright ``` > Trên **Linux**, có thể `playwright` báo thiếu system lib (libnss3, libatk, …). Fix: > `uv run playwright install-deps chromium` (cần sudo, dùng apt). Trên **macOS/Windows** thường không cần. ### 4. Chạy crawler ```bash uv run python main.py ``` Hoặc activate venv rồi chạy như Python thường: ```bash # macOS / Linux source .venv/bin/activate python main.py # Windows (PowerShell) .venv\Scripts\Activate.ps1 python main.py # Windows (cmd.exe) .venv\Scripts\activate.bat python main.py ``` Chạy lại = **resume** từ SQLite queue (`data/metadata.db`). URL đã `done` bị skip. ### 5. Dev tools (lint / format / type-check / test) ```bash uv sync --extra dev # cài thêm black, ruff, mypy, pytest uv run ruff check . uv run black . uv run mypy . uv run pytest # asyncio_mode = auto đã preset trong pyproject.toml ``` ### Lệnh `uv` thường dùng | Tác dụng | Lệnh | | ------------------------------------- | ------------------------------------- | | Sync env đúng theo `uv.lock` | `uv sync` | | Sync + cài optional dep `dev` | `uv sync --extra dev` | | Thêm 1 package mới | `uv add ` | | Bỏ 1 package | `uv remove ` | | Update lock theo `pyproject.toml` | `uv lock` | | Upgrade 1 package | `uv lock --upgrade-package ` | | Chạy lệnh trong venv mà không activate | `uv run ` | > ℹ️ Vẫn dùng `pip` được — xem mục **Cài đặt môi trường (pip)** ở dưới. Nhưng `uv` nhanh hơn 10–100× và đảm bảo cùng phiên bản deps trên mọi máy nhờ `uv.lock`. --- ## Mục tiêu project Crawl tài liệu từ: * [https://api.intra-mart.jp/iap/](https://api.intra-mart.jp/iap/) * [https://document.intra-mart.jp/library/](https://document.intra-mart.jp/library/) rồi convert toàn bộ sang Markdown có: * giữ nguyên hierarchy theo URL path * giữ internal links (rewrite sang đường dẫn local) * tải assets (ảnh, css, file đính kèm) về local * hỗ trợ JS-rendered docs (Playwright + Chromium) * resume được khi chạy lại (state nằm trong SQLite) * output sạch để dùng với: * Obsidian * MkDocs * Docusaurus * RAG / LLM * GitBook --- ## Kiến trúc tổng thể Toàn bộ pipeline nằm trong `main.py` (`DocumentationCrawler`), điều phối các module con. Flow mỗi page: ```text URLManager.normalize / is_allowed ↓ PlaywrightClient.fetch_page (Chromium, JS-rendered, retry qua tenacity) ↓ HTMLCleaner.clean (loại REMOVE_SELECTORS) ↓ HTMLExtractor.extract_main_content (match CONTENT_SELECTORS đầu tiên) ↓ AssetDownloader (download + rewrite asset link sang local) ↓ MarkdownConverter.html_to_markdown (Pandoc → GFM, kèm YAML frontmatter) ↓ FileWriter / PathMapper (output/markdown//.md) ↓ extract_links + SphinxDiscovery + NavigationParser → enqueue URL mới ``` ### State nằm ở SQLite, không phải in-memory `storage/metadata_db.py` (file `data/metadata.db`) là source of truth. Bảng chính: * `crawl_queue` — drive vòng lặp crawl: `pending` / `processing` / `retry` / `failed` / `done`. Lấy URL kế tiếp theo `priority DESC, depth ASC, discovered_at ASC`. * `pages` — metadata page crawl thành công + đường dẫn markdown output. * `assets`, `page_links`, `discovery_sources`, `failures` — provenance + site graph. Chạy lại `python main.py` sẽ resume: URL `done` bị bỏ qua, URL đang `processing` bị reset về `retry` khi khởi động. > ⚠️ Crawl loop hiện chạy **sequential single-worker** (chưa có asyncio worker pool). Để giới hạn memory, Playwright được restart mỗi `BROWSER_RECYCLE_EVERY = 500` page. ### Discovery đa nguồn URL được phát hiện từ 3 nguồn, merge lại trước khi enqueue: 1. `DocumentationCrawler.extract_links` — generic `` + selector đặc thù Sphinx (`a.reference.internal`, `.toctree-wrapper a`, `link[rel=next/prev/up]`). 2. `SphinxDiscovery` — probe `searchindex.js`, `genindex.html`, … 3. `NavigationParser` — dựng nav tree để export `SUMMARY.md` / `navigation_tree.json`. ### URL → filesystem mapping `storage/path_mapper.py` map sang `//.md`: * sanitize ký tự Windows-invalid + reserved names * hash query string thành suffix `__` * rút gọn tên > 180 ký tự bằng MD5 tail Asset link rewrite trong `extractor/asset_downloader.py` tính path **tương đối với output path của page hiện tại** — nếu đổi layout output của `PathMapper`, phải sửa luôn `rewrite_asset_links`. --- ## Công nghệ sử dụng | Thành phần | Công nghệ | | ------------------- | ------------------ | | Rendering | Playwright (Chromium) | | HTTP fast-path | httpx | | HTML Parse | BeautifulSoup4 + lxml | | URL handling | urllib.parse | | Markdown conversion | Pandoc (qua pypandoc / markdownify) | | State & queue | SQLite | | Logging | loguru | | Retry | tenacity | | Config | constants ở đầu `main.py` (xem mục Configuration) | --- ## Cấu trúc project ```text intra-mart-crawler/ │ ├── README.md ├── CLAUDE.md ├── requirements.txt ├── pyproject.toml ├── config.yaml ← KHÔNG được main.py đọc (xem Configuration) ├── main.py ← entrypoint + DocumentationCrawler │ ├── crawler/ │ ├── crawler.py │ ├── http_client.py │ ├── playwright_client.py │ ├── url_manager.py │ ├── sitemap_parser.py │ ├── sphinx_discovery.py │ └── navigation_parser.py │ ├── extractor/ │ ├── html_extractor.py │ ├── html_cleaner.py │ └── asset_downloader.py │ ├── converter/ │ ├── markdown_converter.py │ └── link_rewriter.py │ ├── storage/ │ ├── file_writer.py │ ├── metadata_db.py ← SQLite source of truth │ └── path_mapper.py │ ├── models/ │ ├── page.py │ └── crawl_result.py │ ├── utils/ │ ├── logger.py │ ├── retry.py │ └── helpers.py │ ├── output/ ← runtime artifact (gitignored) │ ├── markdown/ │ └── assets/ │ ├── data/ ← runtime artifact (gitignored) │ └── metadata.db │ └── logs/ ← rotating logs (gitignored) ├── crawler.log ├── errors.log └── debug.log ``` --- ## Cài đặt môi trường (pip — cách cũ) > Nếu bạn dùng `uv` thì có thể bỏ qua section này — xem **Quickstart với `uv`** ở đầu file. Yêu cầu: **Python >= 3.11** và **Pandoc** có trên PATH. ```bash # 1. Python deps pip install -r requirements.txt # 2. Browser cho Playwright playwright install chromium # 3. Pandoc sudo apt install pandoc # Ubuntu/Debian # macOS: brew install pandoc # Windows: winget install --id JohnMacFarlane.Pandoc # hoặc https://pandoc.org/installing.html ``` ### Dev environment (lint / format / type-check / test) ```bash pip install -e ".[dev]" ruff check . black . mypy . pytest # asyncio_mode = auto đã preset pytest tests/test_x.py::test_name # chạy 1 test ``` Line length pinned ở **79** cho cả `black` và `ruff`. Target Python 3.11. --- ## Configuration > ⚠️ `config.yaml` **không được `main.py` đọc**. Đây là tàn dư từ thiết kế cũ — sẽ refactor sau. Cấu hình "live" là các hằng số ở đầu `main.py`: | Constant | Ý nghĩa | | ----------------------- | ------------------------------------------------------- | | `START_URLS` | Seed URLs để crawl | | `ALLOWED_DOMAINS` | Whitelist domain — ngoài list này sẽ không enqueue | | `OUTPUT_DIR` | Thư mục output gốc (mặc định `output/`) | | `REMOVE_SELECTORS` | CSS selectors bị drop trước khi extract | | `CONTENT_SELECTORS` | Selectors thử lần lượt để lấy main content | | `MAX_PAGES_PER_RUN` | Số page tối đa mỗi lần chạy | | `MAX_ATTEMPTS` | Số lần retry trước khi mark `failed` | | `BROWSER_RECYCLE_EVERY` | Restart Chromium sau mỗi N page (giới hạn memory leak) | Muốn đổi behaviour → sửa các constant này, **không phải `config.yaml`**. --- ## Chạy project ```bash python main.py ``` Chạy lại lệnh trên = resume từ SQLite queue. URL đã `done` bị skip, URL `processing` reset về `retry`. --- ## Output ví dụ ```text output/ ├── markdown/ │ ├── api.intra-mart.jp/ │ │ └── iap/ │ │ ├── index.md │ │ ├── javadoc/ │ │ │ └── ... │ │ └── apilist-ssjs/ │ │ └── ... │ │ │ └── document.intra-mart.jp/ │ └── library/ │ ├── getting-started.md │ └── workflow.md │ └── assets/ └── ... (ảnh, css, file đính kèm) ``` Mỗi file `.md` có YAML frontmatter (source URL, crawl time, ...) và đã rewrite internal link + asset link sang relative path. Ngoài ra: * `SUMMARY.md` — index theo nav tree (cho GitBook / mdBook). * `navigation_tree.json` — nav tree dạng JSON (cho MkDocs / Docusaurus / custom sidebar). --- ## Logging `utils/logger.py` cấu hình loguru ngay khi import (chỉ cần `from utils.logger import logger`). Ghi rotating log vào `logs/crawler.log`, `logs/errors.log`, `logs/debug.log` — không cần setup gì thêm. --- ## Hướng phát triển tiếp theo ### Trong scope crawler 1. **Async concurrent crawling** — hiện crawl sequential, có thể thêm asyncio worker pool + semaphore + rate limiting. 2. **Better extraction fallback** — bổ sung `trafilatura` / `readability-lxml` cho page không match `CONTENT_SELECTORS` nào. 3. **Duplicate detection** — hash HTML/markdown content để skip page trùng nội dung khác URL. 4. **Đọc `config.yaml` thực sự** (hoặc bỏ hẳn file này) — đồng bộ với constants trong `main.py`. 5. **Incremental update** — re-crawl chỉ những page có `ETag` / `Last-Modified` thay đổi. ### Hệ sinh thái sau crawler 1. Build MkDocs / Docusaurus site từ output. 2. Build vector database + embedding docs. 3. Tạo RAG chatbot trên docs đã embed. 4. Semantic search UI. 5. Git sync pipeline cho output markdown.