Playwright-based crawler that renders pages from api.intra-mart.jp and document.intra-mart.jp, extracts main content, and converts to Markdown via Pandoc. SQLite-backed queue drives a resumable sequential pipeline across crawler, extractor, converter, and storage modules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| converter | ||
| crawler | ||
| extractor | ||
| models | ||
| storage | ||
| utils | ||
| .gitignore | ||
| .python-version | ||
| CLAUDE.md | ||
| config.yaml | ||
| LICENSE | ||
| main.py | ||
| pyproject.toml | ||
| README.md | ||
| requirements.txt | ||
| uv.lock | ||
Intra-Mart Documentation Crawler → Markdown Exporter
🚀 Quickstart với 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 https://pandoc.org/installing.html |
Xác nhận: pandoc --version.
3. Clone & sync deps
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ể
playwrightbá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
uv run python main.py
Hoặc activate venv rồi chạy như Python thường:
# 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)
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 <package> |
| Bỏ 1 package | uv remove <package> |
Update lock theo pyproject.toml |
uv lock |
| Upgrade 1 package | uv lock --upgrade-package <package> |
| Chạy lệnh trong venv mà không activate | uv run <command> |
ℹ️ Vẫn dùng
pipđược — xem mục Cài đặt môi trường (pip) ở dưới. Nhưnguvnhanh 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ừ:
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:
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/<domain>/<sanitized path>.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 theopriority 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 = 500page.
Discovery đa nguồn
URL được phát hiện từ 3 nguồn, merge lại trước khi enqueue:
DocumentationCrawler.extract_links— generic<a href>+ selector đặc thù Sphinx (a.reference.internal,.toctree-wrapper a,link[rel=next/prev/up]).SphinxDiscovery— probesearchindex.js,genindex.html, …NavigationParser— dựng nav tree để exportSUMMARY.md/navigation_tree.json.
URL → filesystem mapping
storage/path_mapper.py map sang <output_dir>/<domain>/<sanitized path>.md:
- sanitize ký tự Windows-invalid + reserved names
- hash query string thành suffix
__<hash> - 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
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
uvthì có thể bỏ qua section này — xem Quickstart vớiuvở đầu file.
Yêu cầu: Python >= 3.11 và Pandoc có trên PATH.
# 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)
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.yamlkhông đượcmain.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
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ụ
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
- Async concurrent crawling — hiện crawl sequential, có thể thêm asyncio worker pool + semaphore + rate limiting.
- Better extraction fallback — bổ sung
trafilatura/readability-lxmlcho page không matchCONTENT_SELECTORSnào. - Duplicate detection — hash HTML/markdown content để skip page trùng nội dung khác URL.
- Đọc
config.yamlthực sự (hoặc bỏ hẳn file này) — đồng bộ với constants trongmain.py. - Incremental update — re-crawl chỉ những page có
ETag/Last-Modifiedthay đổi.
Hệ sinh thái sau crawler
- Build MkDocs / Docusaurus site từ output.
- Build vector database + embedding docs.
- Tạo RAG chatbot trên docs đã embed.
- Semantic search UI.
- Git sync pipeline cho output markdown.