crawler-intra-mart/CLAUDE.md
Do Duy eb46e739e7 Add intra-mart documentation crawler
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>
2026-05-20 16:29:54 +07:00

4.1 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Overview

Documentation crawler that renders pages from api.intra-mart.jp and document.intra-mart.jp with Playwright, extracts the main content, and converts it to Markdown via Pandoc. Output is structured for Obsidian / MkDocs / Docusaurus / RAG ingestion.

Commands

# Install (Python >= 3.11)
pip install -r requirements.txt
playwright install chromium
# pandoc must be on PATH (apt install pandoc / https://pandoc.org/installing.html)

# Run the crawler
python main.py

# Lint / format / typecheck / test (dev extras)
pip install -e ".[dev]"
ruff check .
black .
mypy .
pytest                            # asyncio_mode=auto is preset
pytest tests/test_x.py::test_name # single test

Re-running python main.py resumes from the SQLite queue — finished URLs are skipped, processing rows are reset to retry on startup.

Architecture

The pipeline lives in main.py as a single DocumentationCrawler class that orchestrates per-module components. Flow per page:

URLManager.normalize/is_allowed
        ↓
PlaywrightClient.fetch_page    (Chromium, JS-rendered, retries via tenacity)
        ↓
HTMLCleaner.clean              (drop REMOVE_SELECTORS)
        ↓
HTMLExtractor.extract_main_content  (first match of CONTENT_SELECTORS)
        ↓
AssetDownloader                (download + rewrite asset links to local paths)
        ↓
MarkdownConverter.html_to_markdown  (Pandoc → GFM, with YAML frontmatter)
        ↓
FileWriter / PathMapper        (output/markdown/<domain>/<sanitized path>.md)
        ↓
extract_links + SphinxDiscovery + NavigationParser → enqueue new URLs

State is in SQLite, not in memory

storage/metadata_db.py (data/metadata.db) is the source of truth. Tables:

  • crawl_queue — drives the loop (pending / processing / retry / failed / done); get_next_pending_url orders by priority DESC, depth ASC, discovered_at ASC.
  • pages — successful crawl metadata + markdown output path.
  • assets, page_links, discovery_sources, failures — provenance and the site graph.

The crawl loop is sequential single-worker even though README mentions concurrency — there is no asyncio worker pool. BROWSER_RECYCLE_EVERY = 500 restarts Playwright periodically to bound memory.

Discovery is multi-source

Links are discovered three ways and merged before enqueue: (1) generic <a href> extraction in DocumentationCrawler.extract_links (also targets Sphinx-specific selectors like a.reference.internal, .toctree-wrapper a, link[rel=next/prev/up]), (2) SphinxDiscovery (probes searchindex.js, genindex.html, etc.), (3) NavigationParser (builds a nav tree for SUMMARY.md / navigation_tree.json export).

URL → filesystem mapping

storage/path_mapper.py produces <output_dir>/<domain>/<sanitized path>.md. Sanitizes Windows-invalid chars + reserved names, hashes query strings into a __<hash> suffix, and shortens names > 180 chars with an MD5 tail. Asset link rewriting in extractor/asset_downloader.py computes paths relative to the current page's output path, so don't change PathMapper output layout without also revisiting rewrite_asset_links.

Configuration

config.yaml exists but main.py does not read it — the live configuration is the module-level constants at the top of main.py (START_URLS, ALLOWED_DOMAINS, REMOVE_SELECTORS, CONTENT_SELECTORS, MAX_PAGES_PER_RUN, BROWSER_RECYCLE_EVERY, MAX_ATTEMPTS). Update those, not the YAML.

Logging

utils/logger.py configures loguru on import (just from utils.logger import logger). Writes rotating logs to logs/crawler.log, logs/errors.log, logs/debug.log — no extra setup needed.

Conventions

  • Line length 79 (black and ruff both pinned to this). Target Python 3.11.
  • pytest-asyncio runs in auto mode — async test functions need no decorator.
  • Output dirs (output/, data/, logs/) are runtime artifacts; output/assets/, output/markdown/, data/ are gitignored.