Grabber
A flexible and extensible template-based web crawler with automatic site detection. Easily crawl news articles, stock data, and more from Korean websites.
✨ Features
- 🎯 Automatic Template Detection: Automatically selects the optimal template based on URL
- 🚀 Simple Interface: One-line crawling with the
Grabberclass - ⚡ High-Performance Async: Optimized async crawling up to 10,000+ RPS locally
- 📰 30+ Supported Sites: Major Korean news sites and financial platforms
- 🔧 Extensible: Easy to add custom templates for new sites
- 💾 Multiple Export Formats: Save as JSON, CSV, or TXT
- 🔄 Batch Processing: Crawl multiple URLs efficiently with connection pooling
- 🛡️ Smart Bot Detection Bypass: Advanced header management and session persistence
- 🎛️ Adaptive Performance: Dynamic concurrency and delay adjustment based on site response
- 📊 Site Profiling: Pre-configured optimal settings for each news site
- 🖥️ Mode-based CLI: One
grabbercommand for HTML, news, audio and site-exploration modes - 🤖 Agent-friendly: JSON/NDJSON output, meaningful exit codes, and a self-describing
capabilitiescommand
📦 Installation
git clone https://github.com/unohee/grabber.git
cd grabber
pip install -e .
# With JS rendering support (SPA/SSR news sites)
pip install -e ".[js]" && playwright install chromium
🖥️ Command Line
Installing the package puts a grabber command on your PATH. Modes are
subcommand groups:
grabber doctor # check binaries, optional extras and credentials
grabber grab <URL> # HTML / article scraping
grabber news search "반도체" # keyword search via the Naver News API
grabber audio get <URL> # YouTube / SoundCloud audio download
grabber explore run suno # Suno / Udio media discovery
grabber probe <URL> # which template and backend would handle this URL?
grabber capabilities # dump the whole command surface as JSON
HTML mode
# Single URL, human output
grabber grab "https://www.newsis.com/view/NISX20240101_0002"
# Batch from a file, 8 workers, machine-readable stream
grabber grab -f urls.txt -w 8 --ndjson > out.jsonl
# From stdin, trimmed for an LLM's context budget
cat urls.txt | grabber grab -f - --json --max-chars 2000 --fields url,title,content
# Export to CSV / Markdown (format inferred from the extension)
grabber grab "$URL" -o article.md
News mode
Needs NAVER_CLIENT_ID / NAVER_CLIENT_SECRET in .env (or --env-file).
grabber news search "삼성전자" --days 3 --max 50 --json
grabber news collect "환율" --max 10 --max-chars 1500 -o out.md # search + scrape
Audio mode
Requires yt-dlp on PATH. Downloads go to
$GRABBER_DATA_DIR/audio/<source>/ (default ~/.grabber), or -d <dir>.
grabber audio info "https://youtu.be/VIDEOID" --json # metadata only
grabber audio search "lofi piano" -n 10 --urls-only # SoundCloud search
grabber audio get "https://youtu.be/VIDEOID" -d ./downloads
# Long corpus runs: queue once, drain repeatedly (resume-safe)
grabber audio queue -f songs.csv --source youtube
grabber audio batch --source youtube -w 4 --limit 500
audio queue accepts a plain URL list or a CSV carrying a youtube_id,
video_id, track_id or url column.
Connections are direct by default; pass --proxy URL or --proxy-pool to route
through a proxy. Recent yt-dlp versions need a JavaScript runtime (e.g. deno)
for YouTube downloads — grabber doctor flags this. SoundCloud is unaffected.
Explore mode
Requires the js extra (pip install -e ".[js]" && playwright install chromium).
grabber explore run suno --pages 20 --batches 3
grabber explore stats suno --json
grabber explore export suno --type audio -o suno_audio.txt
grabber explore download suno --type audio --limit 100
For agents
grabber capabilities --json # every command, option, type, default and choice
stdout carries only the payload; progress and logs go to stderr. Exit codes:
0 success · 1 partial · 2 usage error · 3 all failed · 4 missing
dependency or credentials · 5 no results. Full contract — record schema, error
codes, recipes — in docs/AGENT_CLI.md.
🚀 Quick Start
Simple Usage
from grabber import Grabber
# One-line crawling
data = Grabber.quick_grab("https://www.ajunews.com/view/20240101000000000")
if data:
print(f"Title: {data.data.get('title')}")
print(f"Content: {data.data.get('content')}")
Basic Usage
from grabber import Grabber
# Create a Grabber instance
grabber = Grabber()
# Crawl a news article (automatic template detection)
data = grabber.grab("https://n.news.naver.com/article/001/0014000000")
if data:
print(f"Source: {data.source}")
print(f"Title: {data.data.get('title')}")
print(f"Content: {data.data.get('content')}")
Batch Crawling
# Crawl multiple URLs
urls = [
"https://www.ajunews.com/view/20240101000000000",
"https://www.businesspost.co.kr/BP?command=article_view&num=123456",
"https://www.thebell.co.kr/free/content/ArticleView.asp?key=202401010000000000"
]
results = grabber.grab_batch(urls)
for result in results:
if result:
print(f"{result.source}: {result.data.get('title')}")
Stock Data Crawling
# Crawl stock information
data = grabber.grab_stock("005930", source="naver") # Samsung Electronics
if data:
print(f"Company: {data.data.get('company_name')}")
print(f"Price: {data.data.get('current_price')}")
🆕 Asynchronous Crawling (High Performance)
import asyncio
from grabber.core import AsyncTemplateCrawler
from templates.async_naver_news_template import AsyncNaverNewsTemplate
async def async_crawl():
# Create async template with optimization
template = AsyncNaverNewsTemplate()
# Create async crawler with performance tuning
async with AsyncTemplateCrawler(
template=template,
max_concurrent=32, # Optimized for Ryzen 5800X
delay=0.3, # Optimal delay for news sites
timeout=30
) as crawler:
# Single crawl
result = await crawler.crawl("인공지능")
# Multiple crawls concurrently
keywords = ["AI", "머신러닝", "딥러닝"]
results = await crawler.crawl_multiple(keywords)
# Performance: 10x faster than synchronous
print(f"Crawled {len(results)} keywords")
# Run async crawling
asyncio.run(async_crawl())
📋 Supported Sites
News Sites
- 아주뉴스 (ajunews.com)
- 비즈니스포스트 (businesspost.co.kr)
- 이데일리 (edaily.co.kr)
- 이투데이 (etoday.co.kr)
- 한국경제 (hankyung.com)
- 매일경제 (mk.co.kr)
- 네이버 뉴스 (news.naver.com)
- 뉴시스 (newsis.com)
- 뉴스핌 (newspim.com)
- 더벨 (thebell.co.kr)
- And 20+ more...
Financial Sites
- 네이버 금융 (finance.naver.com)
- FnGuide (comp.fnguide.com)
🔧 Advanced Usage
Custom Template
from grabber import Grabber, SiteTemplate
class MyCustomTemplate(SiteTemplate):
def get_site_name(self):
return "MyCustomSite"
def build_url(self, target):
return f"https://mycustomsite.com/{target}"
def extract_data(self, soup):
return {
"title": soup.find("h1").text,
"content": soup.find("article").text
}
# Use custom template
grabber = Grabber(template=MyCustomTemplate)
data = grabber.grab("article/123")
Save Results
# Save crawled data
data = grabber.grab(url)
if data:
# Save as JSON
grabber.save_to_file(data, "output.json", format="json")
# Save as CSV
grabber.save_to_file(data, "output.csv", format="csv")
# Save as TXT
grabber.save_to_file(data, "output.txt", format="txt")
List Available Templates
# Get all available templates
templates = grabber.list_templates()
for name, site_name in templates.items():
print(f"{name}: {site_name}")
# Get supported domains
domains = grabber.get_supported_domains()
print(f"Supported domains: {domains}")
🏗️ Architecture
grabber/
├── grabber/ # Core package
│ ├── core/ # Core components
│ ├── grabber.py # Main interface
│ └── tools/ # Utilities
├── templates/ # Site-specific templates
│ ├── *_template.py # Individual site templates
│ ├── extractors/ # Data extractors
│ └── validators/ # Data validators
└── examples/ # Usage examples
🧪 Testing
# Install development dependencies
pip install -e ".[dev]"
# Run tests
pytest
# Run with coverage
pytest --cov=grabber
🤝 Contributing
Contributions are welcome! To add support for a new site:
- Create a new template in
templates/directory - Inherit from
SiteTemplateclass - Implement required methods:
get_site_name()build_url(target)extract_data(soup)clean_data(raw_data)validate_data(data)
Example:
from grabber.core.site_template import SiteTemplate
class NewSiteTemplate(SiteTemplate):
def get_site_name(self):
return "NewSite"
def build_url(self, target):
return f"https://newsite.com/{target}"
def extract_data(self, soup):
# Implementation here
pass
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Built with BeautifulSoup4 and Selenium
- Uses undetected-chromedriver for anti-detection
- Inspired by the need for flexible web scraping solutions
📧 Contact
- Issues: GitHub Issues
🚀 Performance Optimization
System Requirements
- CPU: Multi-core processor recommended (tested on AMD Ryzen 7 5800X)
- RAM: 4GB+ recommended for large-scale crawling
- Network: Stable internet connection with low latency
Performance Benchmarks
| Site | RPS | Concurrency | Success Rate |
|---|---|---|---|
| Naver News | 11.9 | 32 | 100% |
| HTTPBin (local) | 10,544 | 128 | 100% |
| General News | 7-15 | 16-32 | 95%+ |
Optimization Tips
- Use async crawling for I/O-bound operations
- Adjust concurrency based on target site's rate limits
- Enable connection pooling for batch operations
- Use site profiling for automatic optimization
📈 Roadmap
- Add more news sites
- Support for international sites
- Async crawling support (v1.1.0)
- Performance optimization for news sites (v1.2.0)
- Smart bot detection bypass (v1.2.0)
- REST API interface
- Docker support
- Cloud deployment guides
- Distributed crawling support
Made with ❤️ by unohee
Release files for grabber-cli 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| grabber_cli-2.0.0.tar.gz | 173.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grabber_cli-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 438.4 kB
Release files / grabber_cli-2.0.0.tar.gz
| Download URL | grabber_cli-2.0.0.tar.gz |
|---|---|
| Size | 173.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
69bc5286ca0c03d949e9672f19f2d28e3312a9b7e7ffb845bf04fbd86e717e6a
|
|
BLAKE2b-256 checksum How to use checksums |
f3b2c9de4a1d30538082d28198c8d4dff31450b83254700157c97de2e6d1e46e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / grabber_cli-2.0.0-py3-none-any.whl
| Download URL | grabber_cli-2.0.0-py3-none-any.whl |
|---|---|
| Size | 264.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0165d324e52fac525f95375a493724fb58e9e621575ef07425a47942ccf81bd0
|
|
BLAKE2b-256 checksum How to use checksums |
33ffee0c80f4348f0000e98339210f9c59962d959ba6753f10e620c2db8d23ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|