A powerful, async Python library for scraping Google and Bing Search Engine Results Pages (SERPs) with SeleniumBase UC Mode (Chromium), proxy rotation, stealth browsing, and intelligent caching.
- SeleniumBase UC Mode: Stealth Chromium browser automation with hex-edited chromedriver (
navigator.webdriver=false) - Multi-layered Stealth: Driver-level, Chromium args, CDP overrides, WebRTC spoofing, JS fingerprint injection, and IP metadata sync
- Google News RSS: Scrape news articles via Google News RSS feeds (httpx-based)
- Google Scholar: Search academic papers from Google Scholar
- Proxy Rotation: DataImpulse, Decodo, and custom proxy support with automatic rotation
- Intelligent Caching: Disk-based caching with configurable TTL
- CAPTCHA Handling: Automatic detection with retry logic and exponential backoff
- Type Safety: Full type annotations with Pydantic validation,
py.typedmarker included - Async/Await: Modern asynchronous API design
- YAML Configuration:
config.yamlfile support (seeconfig.yaml.example) - CLI Tool: Interactive command-line interface for testing
- Dual Output Format: Text (human-readable) and JSON (LLM-friendly) output
- REST API: Optional FastAPI-based REST API with rate limiting and authentication
- Content Compression: Built-in
compress_content()utility for truncating long content — available in both the core library and the REST API
pip install serp-scrapergit clone https://github.com/neuronaline/serp-scraper.git
cd serp-scraper
pip install -e .pip install serp-scraper[dev] # Dev tools (ruff, mypy, build, twine)
pip install serp-scraper[test] # Test dependencies (pytest, pytest-asyncio, etc.)
pip install serp-scraper[api] # REST API (FastAPI + uvicorn)After installation, the interactive CLI is available as a command:
serp-cliOr run directly:
python main.py- Python 3.10 or higher
- Chromium browser — automatically managed by SeleniumBase (downloads chromedriver + browser on first run)
- Virtual display (for non-headless mode, the default):
- Linux/headless servers: Install Xvfb (
sudo apt install xvfb) and useDISPLAY=:99or run withxvfb-run - macOS: No additional setup needed (has built-in display)
- Windows: No additional setup needed (has built-in display)
- Linux/headless servers: Install Xvfb (
import asyncio
from serp import SerpClient
async def main():
async with SerpClient() as client:
results = await client.search("python programming")
for r in results:
print(f"{r.rank}. {r.title}")
print(f" {r.url}")
print(f" {r.description[:100]}...")
asyncio.run(main())By default, the browser runs in non-headless mode (visible window) which requires a display. For headless servers or CI/CD environments, use one of these approaches:
Option 1: Use xvfb-run (recommended for Linux):
xvfb-run -a python your_script.pyOption 2: Set DISPLAY environment variable:
DISPLAY=:99 python your_script.pyOption 3: Run in headless mode:
async with SerpClient(headless=True) as client:
results = await client.search("python programming")Note: The VirtualScreenRequiredError exception is raised when running non-headless without a display.
Scrape news articles using Google News RSS feeds:
import asyncio
from serp import GoogleNewsClient
async def main():
async with GoogleNewsClient(language="en", country="US") as client:
news = await client.get_news("Tesla", max_results=20)
for r in news:
print(f"{r.title}")
print(f" Source: {r.source}")
print(f" URL: {r.url}")
print(f" Date: {r.published}")
asyncio.run(main())Or use the quick function:
from serp import quick_news
news = await quick_news("Tesla", language="en", country="US", max_results=20)Search academic papers from Google Scholar:
import asyncio
from serp import ScholarClient
async def main():
async with ScholarClient() as client:
papers = await client.search_scholar("machine learning", max_results=10)
for p in papers:
print(f"{p.title}")
print(f" Authors: {p.authors}")
print(f" URL: {p.url}")
print(f" Citations: {p.citations}")
asyncio.run(main())Or use the quick function:
from serp import quick_scholar
papers = await quick_scholar("machine learning")import asyncio
from serp import SerpClient
async def main():
async with SerpClient() as client:
# Fetch page content as Markdown using SeleniumBase UC Mode browser
content = await client.fetch("https://example.com")
print(content)
asyncio.run(main())Fetch Strategy:
- All URL fetches use SeleniumBase UC Mode browser for full JavaScript-rendered page content
- This ensures JavaScript-rendered content is never missed and incomplete data is never collected
- The browser waits for the
loadevent plus additional time for JS rendering
Compress long content into head, middle, and tail portions — useful for LLM contexts where you want to retain key information while reducing token count:
from serp import compress_content, CompressionMeta
long_text = "..." # e.g. 20,000+ characters
compressed, meta = compress_content(long_text)
if meta.was_truncated:
print(f"Reduced from {meta.original_length:,} to {meta.compressed_length:,} chars")
print(f"Truncated {meta.truncated_chars:,} characters")Compression can also be enabled directly during fetch:
content = await client.fetch("https://example.com", compress=True)The standalone compress_content() function gives you full control over thresholds and metadata, while the compress=True parameter on fetch() is a convenience shortcut.
For simple use cases without creating a client:
import asyncio
from serp import quick_search, quick_fetch
async def main():
# Search
results = await quick_search("web scraping")
print(f"Found {len(results)} results")
# Fetch URL (with optional compression)
content = await quick_fetch("https://example.com", compress=True)
print(content[:500])
asyncio.run(main())import asyncio
from serp import SerpClient, SerpConfig
# Create configured client directly
config = SerpConfig(
log_level="DEBUG",
max_retries=5,
cache_ttl=3600, # 1 hour
cache_enabled=True,
)
async with SerpClient(config) as client:
results = await client.search("python tutorial")Or load from config.yaml:
config = SerpConfig.from_yaml()
# Or specify path:
config = SerpConfig.from_yaml("/path/to/config.yaml")
client = SerpClient(config)Create a config.yaml file in your project root. Copy from config.yaml.example for all options:
serp:
log_level: INFO
proxy:
provider: dataimpulse
dataimpulse:
gateway: http://gw.dataimpulse.com:823
user: myuser
pass: mypass
protocol: http
country: us
# decodo:
# user: myuser
# pass: mypass
# country: us
# custom:
# - http://user:pass@proxy1.com:8080
# - socks5://proxy2.com:1080
cache:
enabled: true
dir: .cache/serp
ttl: 86400
retry:
max_retries: 3
delay_min: 0.5
delay_max: 2.0
exponential_backoff: false
search:
source: auto # "google", "bing", or "auto"
headless: false
timeout: 30
human_behavior: true
block_images: false| Parameter | Type | Default | Description |
|---|---|---|---|
| Proxy Settings | |||
proxy_provider |
str | "dataimpulse" |
Proxy provider: "dataimpulse", "decodo", "custom", or "none" |
dataimpulse_gateway |
str | None |
DataImpulse gateway URL |
dataimpulse_user |
str | None |
DataImpulse username |
dataimpulse_pass |
str | None |
DataImpulse password |
dataimpulse_protocol |
str | "http" |
DataImpulse proxy protocol ("http" or "socks5") |
dataimpulse_country |
str | "us" |
DataImpulse country code (e.g., "us", "de") |
dataimpulse_sessid |
str | None |
DataImpulse session ID for sticky proxy |
dataimpulse_sessttl |
int | None |
DataImpulse session TTL in minutes (1-120) |
dataimpulse_nocr |
str | None |
Exclude countries (comma-separated ISO codes) |
dataimpulse_noasn |
str | None |
Exclude ASNs (comma-separated) |
dataimpulse_state |
str | None |
US state targeting (e.g., "arizona") |
dataimpulse_city |
str | None |
City targeting (e.g., "berlin") |
dataimpulse_zip |
str | None |
ZIP code targeting (e.g., "10001") |
dataimpulse_asn |
str | None |
Specific ASN targeting |
dataimpulse_anon |
bool | false |
High anonymity filter (elite proxies) |
decodo_user |
str | None |
Decodo username |
decodo_pass |
str | None |
Decodo password |
decodo_country |
str | None |
ISO country code (e.g., "us", "de") |
decodo_city |
str | None |
City name |
decodo_state |
str | None |
State format "us_california" |
decodo_session |
str | None |
Sticky session ID |
decodo_sessionduration |
int | None |
Session TTL in minutes (1-1440) |
decodo_protocol |
str | "http" |
Proxy protocol ("http" or "socks5") |
custom_proxies |
str | "" |
Comma-separated proxy URLs |
| Cache Settings | |||
cache_enabled |
bool | true |
Enable/disable caching |
cache_dir |
str | ".cache/serp" |
Cache directory path |
cache_ttl |
int | 86400 |
Cache TTL in seconds (min 60) |
| Retry Settings | |||
max_retries |
int | 3 |
Maximum retry attempts (1-10) |
retry_delay_min |
float | 5.0 |
Minimum retry delay in seconds |
retry_delay_max |
float | 15.0 |
Maximum retry delay in seconds |
exponential_backoff |
bool | true |
Use exponential backoff |
| Search Settings | |||
default_source |
str | "auto" |
Default search source: "google", "bing", or "auto" |
headless |
bool | false |
Run browser in headless mode |
timeout |
int | 30 |
Request timeout in seconds (5-120) |
user_agent |
str | None |
Custom user agent string (None = natural UA from SeleniumBase UC Mode) |
human_behavior |
bool | true |
Simulate human browsing behavior |
block_images |
bool | false |
Block image loading to save bandwidth |
| Logging | |||
log_level |
str | "WARNING" |
Logging level (DEBUG, INFO, WARNING, ERROR) |
The recommended high-level interface for using the library.
from serp import SerpClient
client = SerpClient(
headless=False, # Run browser in headless mode
use_cache=True, # Enable/disable caching
cache_ttl=86400, # Cache TTL in seconds
source=None, # "google", "bing", or None (auto)
max_retries=3, # Max retry attempts
timeout=30, # Request timeout in seconds
log_level="WARNING", # Logging level
)
# Or with a SerpConfig instance:
config = SerpConfig(log_level="DEBUG", max_retries=5)
client = SerpClient(config)SerpClient supports both async and sync context managers:
# Async (recommended)
async with SerpClient() as client:
results = await client.search("query")
# Sync (backward compatibility)
with SerpClient() as client:
results = client.search("query") # still async, needs awaitSearch for a query and return results.
Note: Google and Bing SERP searches always use SeleniumBase UC Mode browser because search engines require JavaScript to render results.
Parameters:
query(str): Search query stringpage_num(int): Page number (1-based), defaults to 1source(str): Search engine —"google","bing", orNone(auto: google first, bing fallback)use_cache(bool): Whether to use cache.Noneuses client default.
Returns:
list[SearchResult]: List of SearchResult objects
Raises:
ProxyError: All proxies failedCaptchaError: CAPTCHA detected after all retriesPageTimeoutError: Page load timeoutParseError: Failed to parse results
Fetch a URL and return content as Markdown.
Fetch Strategy:
- All fetches use SeleniumBase UC Mode browser for full JavaScript-rendered page content
- Browser waits for
loadevent plus additional time for JS rendering
Parameters:
url(str): Target URLuse_cache(bool): Whether to use cache.Noneuses client default.prefer_browser(bool): Deprecated, ignored (browser is always used).compress(bool): If True and content exceeds ~10K chars, compress by taking head, middle, and tail portions. (Default: False)
Returns:
str: Page content converted to Markdown (optionally compressed)
Typed result object returned by search operations.
from serp import SearchResult
result = SearchResult(
rank=1,
title="Example Title",
url="https://example.com",
description="Example description...",
source="google" # or "bing"
)Attributes:
rank(int): Position in search results (1-based)title(str): Result titleurl(str): Target URLdescription(str): Result snippet/descriptionsource(str): Search engine source ("google"or"bing")
Methods:
to_dict(): Convert to dictionary
Rich parsed SERP page objects with metadata:
from serp import SerpClient
async with SerpClient() as client:
page = await client.search_with_metadata("python tutorial")
# page.results — list of organic results
# page.stats — SearchStats (result count, search time)
# page.pagination_has_next — bool
# page.pagination_next_url — strAdditional rich types (available from serp):
SearchStats— Google search statistics (result count, search time)PeopleAlsoAskItem— People Also Ask question/answer pairVideoResultItem— Video carousel resultKnowledgeGraphInfo— Knowledge Graph panel dataBingSearchStats— Bing search statisticsBingRelatedSearchItem— Bing related search suggestion
Module-level convenience functions using a default client:
from serp import quick_search, quick_fetch
# Quick search
results = await quick_search("query")
# Fetch URL (with optional compression)
content = await quick_fetch("https://example.com", compress=True)Client for scraping Google News via RSS feeds.
from serp import GoogleNewsClient
client = GoogleNewsClient(
language="tr", # Language code (tr, en, etc.)
country="TR", # Country code (TR, US, etc.)
time_range="d", # Time range: "h" (hour), "d" (day), "w" (week), "m" (month)
)Get news articles for a search term.
Parameters:
query(str): Search term to find news formax_results(int): Maximum number of results (default: 50)queries(list[str]): Custom list of queries to use
Returns:
list[NewsResult]: List of NewsResult objects
Convenience function for quick news retrieval.
from serp import quick_news
news = await quick_news("Tesla", max_results=20, language="en", country="US")Typed result object for Google News articles.
from serp import NewsResult
result = NewsResult(
title="Tesla announces new model",
url="https://news.google.com/rss/articles/...",
original_url="https://example.com/article",
published=datetime(2026, 5, 11, 8, 0, 0),
source="BBC",
description="Tesla unveiled...",
query="Tesla"
)Attributes:
title(str): News headlineurl(str): Google News RSS URLoriginal_url(str): Original article URL (extracted from description)published(datetime): Publication datesource(str): News source name (e.g.,"BBC","NTV")description(str): News summary/snippetquery(str): Search query that returned this result
Client for searching Google Scholar academic papers.
from serp import ScholarClient
client = ScholarClient()
papers = await client.search_scholar("machine learning", max_results=10)Methods:
search_scholar(query, max_results=10, page_num=1)— Search for academic papersquick_scholar(query, max_results=10)— Convenience function
Returns: list[ScholarResult] with attributes:
title(str): Paper titleauthors(str): Author namesurl(str): Paper URLsnippet(str): Abstract/excerptcitations(int): Citation countyear(str): Publication year
Structured output formatting for CLI tools and LLM integration.
from serp import OutputFormatter, OUTPUT_TEXT, OUTPUT_JSON
# Format search results
output = OutputFormatter.format_search_results(
results=search_results,
mode=OUTPUT_JSON,
query="python tutorial",
source="google",
)
# Format news results
output = OutputFormatter.format_news(
news_list=news_results,
mode=OUTPUT_JSON,
search_term="Tesla",
language="en",
country="US",
)
# Format fetch results
output = OutputFormatter.format_fetch(
content=page_content,
url="https://example.com",
char_count=len(page_content),
mode=OUTPUT_JSON,
)Constants:
OUTPUT_TEXT: Text output mode (human-readable)OUTPUT_JSON: JSON output mode (machine-parseable)
Set the log level for all serp loggers.
from serp import set_log_level
set_log_level("DEBUG") # Enable debug logging
set_log_level("WARNING") # Only show warnings and errorsCompress long content by extracting head, middle, and tail portions, joined with a truncation marker.
from serp import compress_content, CompressionMeta
compressed, meta = compress_content(
content,
threshold=10000, # Content longer than this gets compressed
head_pct=0.35, # 35% of target for the head
middle_pct=0.15, # 15% of target for the middle
tail_pct=0.50, # 50% of target for the tail
)
if meta.was_truncated:
print(f"Original: {meta.original_length:,} chars")
print(f"Compressed: {meta.compressed_length:,} chars")
print(f"Truncated: {meta.truncated_chars:,} chars")Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
content |
str | - | The content string to compress |
threshold |
int | 10000 |
Char length threshold. Content shorter than this is unchanged. |
head_pct |
float | 0.35 |
Fraction of target length for the head portion |
middle_pct |
float | 0.15 |
Fraction of target length for the middle portion |
tail_pct |
float | 0.50 |
Fraction of target length for the tail portion |
Returns: tuple[str, CompressionMeta] — (compressed_content, metadata)
CompressionMeta attributes:
| Attribute | Type | Description |
|---|---|---|
original_length |
int | Character count before compression |
compressed_length |
int | Character count after compression |
truncated_chars |
int | Number of characters removed |
was_truncated |
bool | Whether content exceeded threshold and was truncated |
| Exception | Description |
|---|---|
ProxyError |
All configured proxies failed or returned errors |
CaptchaError |
Search engine detected automation and presented CAPTCHA |
PageTimeoutError |
Page load timeout |
ParseError |
Page loaded but results could not be parsed |
BrowserCrashError |
Browser process crashed unexpectedly |
VirtualScreenRequiredError |
No virtual display available for non-headless mode |
| Constant | Description |
|---|---|
MAX_RETRIES |
Maximum retry attempts (default: 3) |
TIMEOUT_MS |
Page timeout in milliseconds (default: 30000) |
The package includes an interactive CLI tool for testing:
# Via entry point (after pip install)
serp-cli
# Or run directly
python main.py
python main.py --format json # JSON output for LLM integration
python main.py -f text # Human-readable text output (default)
python main.py --compress # Enable content compression for URL fetch
python main.py --compress -f json # Compress + JSON outputFeatures:
- SERP Search testing (Google & Bing)
- URL Fetch testing (with optional
--compressflag) - Google News RSS testing
- Google Scholar testing
- Dual output format (text/JSON)
- Proxy status checking
The package includes an optional FastAPI-based REST API for programmatic access.
pip install serp-scraper[api]python -m api.main
# Or with uvicorn directly:
uvicorn api.main:app --host 0.0.0.0 --port 8000| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
POST |
/api/v1/search |
Search SERP results |
POST |
/api/v1/fetch |
Fetch URL content (supports compress field) |
POST |
/api/v1/news |
Get Google News articles |
POST |
/api/v1/scholar |
Search Google Scholar articles |
The API uses API key authentication. Set the X-API-Key header in your requests.
The API includes rate limiting middleware to prevent abuse.
| Variable | Description | Default |
|---|---|---|
API_HOST |
API server host | 0.0.0.0 |
API_PORT |
API server port | 8000 |
API_DEBUG |
API debug mode | false |
API_MAX_CONCURRENT_REQUESTS |
Max concurrent requests (pool size) | 15 |
API_REQUEST_TIMEOUT |
Request timeout in seconds | 60 |
API_RATE_LIMIT_SEARCH |
Rate limit for /search (req/min) | 30 |
API_RATE_LIMIT_FETCH |
Rate limit for /fetch (req/min) | 60 |
API_RATE_LIMIT_NEWS |
Rate limit for /news (req/min) | 30 |
API_RATE_LIMIT_SCHOLAR |
Rate limit for /scholar (req/min) | 30 |
API_RATE_LIMIT_DEFAULT |
Default rate limit (req/min) | 100 |
API_KEYS_HASHED |
Comma-separated hashed API keys | - |
API_ALLOW_NO_AUTH |
Allow unauthenticated access | false |
API_LOG_LEVEL |
API logging level | INFO |
API_LOG_DIR |
API log directory | logs |
API_LOG_RETENTION_DAYS |
Log retention days | 7 |
API_CORS_ORIGINS |
CORS allowed origins (comma-separated) | - |
serp-scraper/
├── serp/ # Core library
│ ├── __init__.py # Public API exports, __version__
│ ├── client.py # SerpClient and quick functions
│ ├── sb_browser.py # SeleniumBase UC Mode browser lifecycle
│ ├── ip_metadata.py # IP metadata sync (ipinfo.io → locale/currency)
│ ├── browser_stealth.py # Chromium/CDP stealth JS + human behavior
│ ├── parsers.py # SERP page parsing (Google, Bing)
│ ├── cleaning.py # Regex-based HTML/Markdown cleaning
│ ├── cache.py # Disk-based caching (DiskCache, NullCache)
│ ├── compression.py # Content compression (compress_content, CompressionMeta)
│ ├── config.py # Configuration constants (BING_URL_TEMPLATE)
│ ├── config_pydantic.py # Pydantic-based SerpConfig (from_yaml)
│ ├── proxy_providers.py # DataImpulse, Decodo, Custom proxy providers
│ ├── google_news.py # Google News RSS client
│ ├── google_scholar.py # Google Scholar client
│ ├── output_formatter.py # Text and JSON output formatting
│ ├── types.py # Type definitions (SearchResult, RetryPolicy, etc.)
│ ├── utils.py # Exceptions, helpers, constants
│ └── py.typed # PEP 561 typing marker
├── api/ # REST API (optional)
│ ├── __init__.py
│ ├── main.py # FastAPI application
│ ├── config.py # API configuration (APISettings, RateLimitConfig)
│ ├── deps.py # DI: auth, rate limit, semaphore
│ ├── exceptions.py # Custom exception classes
│ ├── models/ # Pydantic models
│ │ ├── __init__.py
│ │ ├── requests.py # Request schemas
│ │ └── responses.py # Response schemas
│ ├── routers/ # API routes
│ │ ├── __init__.py
│ │ ├── health.py # GET /health
│ │ ├── search.py # POST /api/v1/search
│ │ ├── fetch.py # POST /api/v1/fetch
│ │ ├── news.py # POST /api/v1/news
│ │ └── scholar.py # POST /api/v1/scholar
│ ├── middleware/ # Middleware
│ │ ├── __init__.py
│ │ ├── rate_limit.py # Sliding window rate limiter
│ │ └── logging_middleware.py
│ ├── utils/ # Backward-compat re-exports
│ │ ├── __init__.py
│ │ └── compression.py # Re-exports from serp.compression
│ └── cli/ # API CLI tools
│ ├── __init__.py
│ └── keys.py # API key generation
├── tests/ # Test suite
│ ├── __init__.py
│ ├── conftest.py # Fixtures, global state reset
│ ├── test_serp.py # Core library tests
│ ├── test_cache.py # Cache tests
│ ├── test_google_news.py # Google News tests
│ └── api/ # API tests
│ └── __init__.py
├── main.py # Interactive CLI tool
├── config.yaml # Active configuration
├── config.yaml.example # Configuration template
├── pyproject.toml # Project metadata & build config
├── requirements.txt # Pinned dependencies
└── README.md # This file
- Check cache for existing results (if
use_cache=True) - Load proxy configuration from
config.yamlor programmatic settings - Select proxy via provider abstraction (DataImpulse, Decodo, or Custom)
- Create SeleniumBase UC Mode browser instance with stealth settings
- Apply CDP overrides (timezone, geolocation, locale, hardware concurrency)
- Inject stealth JS (WebRTC sanitization, fingerprint spoof, Intl overrides)
- Navigate to search URL with human-like timing
- Wait for results to load
- Check for CAPTCHA
- Parse organic results (Google/Bing specific parsers)
- Cache results before returning
All fetching uses SeleniumBase UC Mode (Chromium) browser exclusively:
- SERP Search (Google/Bing): Browser required — JavaScript renders results
- URL Fetch: Browser ensures JavaScript-rendered content is captured
- Google Scholar: Browser-based scraping with JS evaluation
This ensures complete, accurate data collection and avoids incomplete results from HTTP-only approaches.
| Layer | Technology | Description |
|---|---|---|
| Driver | SeleniumBase UC Mode | Hex-edited chromedriver, navigator.webdriver=false |
| Chromium args | --disable-blink-features=AutomationControlled |
Various anti-detection flags |
| CDP | Emulation.setTimezoneOverride, setGeolocationOverride |
Emulation overrides |
| WebRTC | JSON prefs injection | disable_non_proxied_udp |
| JS | WebRTC ICE sanitization, fingerprint spoof | Runtime fingerprint masking |
| Monkey-patch | sb.get override |
Auto-inject CDP + JS on navigation |
| IP metadata | ipinfo.io API |
Country → locale/currency/timezone derivation |
SeleniumBase is synchronous. All WebDriver calls run via asyncio.to_thread():
await asyncio.to_thread(sb.get, url)
await asyncio.to_thread(sb.execute_script, script)
await asyncio.to_thread(lambda: sb.get_page_source())The caching system uses a disk-based approach:
- Cache entries stored as JSON files in
.cache/serp/ - Keys are SHA256 hashes of query parameters
- Automatic expiration based on TTL
- Can be disabled via
cache_enabled=False
The library includes a built-in content compression utility for truncating long content while preserving key information:
Algorithm:
- If content length ≤
threshold(default: 10,000 chars), return unchanged - Target compressed length = 45% of threshold (~4,500 chars)
- Extract three portions: head (35%), middle (15%), tail (50%)
- Join with a truncation marker:
\n\n-- X,XXX chars truncated --\n\n
Run the test suite:
pytestRun with coverage:
pytest --cov=serp --cov-report=htmlRun specific test file:
pytest tests/test_serp.py| Package | Version | Purpose |
|---|---|---|
| seleniumbase | >=4.30.0 | Stealth Chromium browser automation (UC Mode) |
| markdownify | >=0.12.0 | HTML to Markdown conversion |
| httpx | >=0.25.0 | Async HTTP client (Google News RSS) |
| pyyaml | >=6.0 | YAML config file parsing |
| pydantic | >=2.0.0 | Configuration validation |
| pydantic-settings | >=2.0.0 | Pydantic settings integration |
Install with pip install serp-scraper[api]:
| Package | Version | Purpose |
|---|---|---|
| fastapi | >=0.100.0 | REST API framework |
| uvicorn[standard] | >=0.23.0 | ASGI server |
| passlib[bcrypt] | >=1.7.4 | Password hashing |
| bcrypt | <4.1.0 | Password hashing |
| Package | Version | Purpose |
|---|---|---|
| pytest | >=7.0.0 | Testing framework |
| pytest-asyncio | >=0.21.0 | Async test support |
| pytest-cov | >=4.0.0 | Coverage reporting |
| pytest-mock | >=3.10.0 | Mocking utilities |
| pytest-httpserver | >=1.0.0 | HTTP server for testing |
| ruff | >=0.1.0 | Linting |
| mypy | >=1.0.0 | Type checking |
| build | >=1.0.0 | Package building |
| twine | >=4.0.0 | Package publishing |
MIT License - see LICENSE file for details.
neuronaline - flashneuron@proton.me
This software is provided for educational and legitimate purposes only. Users are responsible for ensuring their use complies with search engine Terms of Service and applicable laws. The authors assume no liability for misuse of this software.