Skip to main content

Selanet Python SDK - HTTP client for browser automation agents

Project description

Selanet Python SDK

Python bindings for the Selanet client SDK. Browse any website and get back structured data through distributed browser agents — no CAPTCHAs, no bot detection.

Installation

pip install selanet-sdk

Quick Start

import asyncio
import json
import os
from selanet_sdk import SelaClient, BrowseOptions

async def main():
    # 1. Create client with your API key (get one at https://app.selanet.ai)
    client = await SelaClient.with_api_key(os.environ["SELA_API_KEY"])

    # 2. Browse a URL
    result = await client.browse(
        url="https://x.com/search?q=ai&f=live",
        options=BrowseOptions(parse_only=True),
    )

    # 3. Extract structured data
    for item in result.page.content:
        fields = json.loads(item.fields_json)
        print(fields["text"])
        print(fields["author_username"], "—", fields.get("like_count"), "likes")

    await client.shutdown()

asyncio.run(main())

Example response:

{
  "page": {
    "page_type": "x_com::search",
    "content": [
      {
        "content_type": "tweet",
        "fields": {
          "text": "AI is transforming how we build software...",
          "author_username": "johndoe",
          "like_count": 142,
          "retweet_count": 38
        }
      }
    ]
  }
}

Note: fields_json and collection_stats are JSON strings in Python. Always use json.loads() to parse them.


Examples

All examples below assume you've created a client:

import os
client = await SelaClient.with_api_key(os.environ["SELA_API_KEY"])

Browse Any Website (Markdown Extraction)

For general websites (not X, Xiaohongshu, Rednote, YouTube, or LinkedIn), use extract_format='markdown' to get readable content:

result = await client.browse(
    url="https://en.wikipedia.org/wiki/Python_(programming_language)",
    options=BrowseOptions(parse_only=True, extract_format="markdown"),
)

print(result.extracted_content)  # Clean markdown text

Important: extract_format='schema' (default) only works for supported platforms (X, Xiaohongshu, Rednote, YouTube, LinkedIn). For all other sites, you must use 'markdown' or 'html' — otherwise you'll get an empty response.

Collect More Items (Infinite Scroll)

Use count to collect multiple items by scrolling the page. The agent will scroll and accumulate items until reaching the target count.

  • Typical range: 10100 items per request
  • Approximate time: count=30 takes ~10–15s, count=100 takes ~30–60s (depends on the platform and network)
import json

# Collect 50 tweets by scrolling
result = await client.browse(
    url="https://x.com/search?q=python&f=live",
    options=BrowseOptions(parse_only=True, count=50),
)

for item in result.page.content:
    fields = json.loads(item.fields_json)
    print(fields["text"][:80])

# Check collection stats
if result.collection_stats:
    stats = json.loads(result.collection_stats)
    print(f"Collected: {stats}")
    # Example: {"total_collected": 50, "scroll_count": 8, "duplicates_removed": 3}

Xiaohongshu

Search Posts

When using platform params, pass url=None and provide the target URL inside the params.

import json
from selanet_sdk import BrowseOptions, XiaohongshuParams

# Search and collect 30 posts
result = await client.browse(
    url=None,
    options=BrowseOptions(
        xiaohongshu_params=XiaohongshuParams(
            feature="search",
            url="https://www.xiaohongshu.com/search_result?keyword=Selanet",
        ),
        count=30,
        parse_only=True,
    ),
)

for item in result.page.content:
    fields = json.loads(item.fields_json)
    print(fields.get("title", ""), "—", fields.get("like_count", ""))

Browse a Note

result = await client.browse(
    url=None,
    options=BrowseOptions(
        xiaohongshu_params=XiaohongshuParams(
            feature="note",
            url="https://www.xiaohongshu.com/explore/682e7c72000000000b03a68a",
        ),
        parse_only=True,
    ),
)

XiaohongshuParams Features

Feature Fields Description
"search" url, query, filters Search posts by keyword
"note" url, note_id Browse a specific note
"profile" url, user_id Browse a user profile
"url" url (required) Browse any Xiaohongshu URL directly

X (Twitter)

Search Tweets

from selanet_sdk import BrowseOptions, XParams

result = await client.browse(
    url=None,
    options=BrowseOptions(
        x_params=XParams(
            feature="search",
            url="https://x.com/search?q=selanet&f=live",
        ),
        count=30,
        parse_only=True,
    ),
)

Browse a Profile

result = await client.browse(
    url=None,
    options=BrowseOptions(
        x_params=XParams(
            feature="profile",
            url="https://x.com/elonmusk",
        ),
        parse_only=True,
    ),
)

Browse a Single Post

result = await client.browse(
    url=None,
    options=BrowseOptions(
        x_params=XParams(
            feature="post",
            url="https://x.com/elonmusk/status/1234567890",
        ),
        parse_only=True,
    ),
)

XParams Features

Feature Fields Description
"search" url, query, search_tab, filters, min_likes, min_retweets, lang, since, until Advanced search with filters
"profile" url, username, tab (posts/replies/media/likes) User profile
"post" url, username, tweet_id Single tweet

Rednote

Rednote is the international version of Xiaohongshu. RednoteParams has the same fields and features as XiaohongshuParams.

Search Posts

from selanet_sdk import BrowseOptions, RednoteParams

result = await client.browse(
    url=None,
    options=BrowseOptions(
        rednote_params=RednoteParams(
            feature="search",
            url="https://www.xiaohongshu.com/search_result?keyword=travel%20tips",
        ),
        count=20,
        parse_only=True,
    ),
)

Browse a Note

result = await client.browse(
    url=None,
    options=BrowseOptions(
        rednote_params=RednoteParams(
            feature="note",
            url="https://www.xiaohongshu.com/explore/682e7c72000000000b03a68a",
        ),
        parse_only=True,
    ),
)

RednoteParams Features

Feature Fields Description
"search" url, query, filters Search posts by keyword
"note" url, note_id Browse a specific note
"profile" url, user_id Browse a user profile
"url" url (required) Browse any Rednote URL directly

YouTube

Search Videos

import json
from selanet_sdk import BrowseOptions, YouTubeParams

result = await client.browse(
    url=None,
    options=BrowseOptions(
        youtube_params=YouTubeParams(
            feature="search",
            url="https://www.youtube.com/results?search_query=rust+programming+tutorial",
        ),
        count=10,
        parse_only=True,
    ),
)

for item in result.page.content:
    fields = json.loads(item.fields_json)
    print(fields.get("title"), "—", fields.get("link"))

Watch a Video

Use feature="watch" with duration (seconds) to watch a video for a specified time:

result = await client.browse(
    url=None,
    options=BrowseOptions(
        youtube_params=YouTubeParams(
            feature="watch",
            url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
            duration=30,  # watch for 30 seconds
        ),
        parse_only=True,
    ),
)

Get Comments

result = await client.browse(
    url=None,
    options=BrowseOptions(
        youtube_params=YouTubeParams(
            feature="comments",
            url="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
        ),
        count=20,
        parse_only=True,
    ),
)

YouTubeParams Features

Feature Fields Description
"search" url, query Search videos by keyword
"watch" url, video_id, duration (seconds) Watch a video for a specified duration
"comments" url, video_id Get comments on a video (use count for more)

LinkedIn

Search

from selanet_sdk import BrowseOptions, LinkedInParams

result = await client.browse(
    url=None,
    options=BrowseOptions(
        linkedin_params=LinkedInParams(
            feature="search_people",
            url="https://www.linkedin.com/search/results/people/?keywords=software%20engineer",
        ),
        parse_only=True,
    ),
)

LinkedInParams Features

Feature Fields Description
"search_all" url Search all content
"search_people" url Search people
"search_posts" url Search posts
"search_companies" url Search companies
"search_jobs" url Search jobs
"in" url Browse a user profile
"company" url Browse a company page
"feed" url Browse the feed

Browse Multiple URLs in Parallel

browse_parallel_collect() distributes URLs across agents for concurrent browsing. Results contain an index field matching the original input order — results may arrive out of order, so use index to map them back.

Search → Parallel Browse Pattern

The typical workflow: search first, extract links, then browse them in parallel:

import json
import os
from selanet_sdk import (
    SelaClient, BrowseOptions,
    XiaohongshuParams, ParallelBrowseItem,
)

async def main():
    client = await SelaClient.with_api_key(os.environ["SELA_API_KEY"])

    # Step 1: Search
    search_result = await client.browse(
        url=None,
        options=BrowseOptions(
            xiaohongshu_params=XiaohongshuParams(
                feature="search",
                url="https://www.xiaohongshu.com/search_result?keyword=Selanet",
            ),
            count=10,
            parse_only=True,
        ),
    )

    # Step 2: Extract note links from search results
    note_links = []
    for item in search_result.page.content:
        fields = json.loads(item.fields_json)
        link = fields.get("link")
        if link and "/explore/" in link:
            note_links.append(link)

    # Step 3: Browse notes in parallel
    items = [
        ParallelBrowseItem(
            url=None,
            options=BrowseOptions(
                xiaohongshu_params=XiaohongshuParams(feature="note", url=link),
                parse_only=True,
            ),
        )
        for link in note_links[:5]  # batch of 5
    ]

    results = await client.browse_parallel_collect(items, max_concurrent_per_agent=5)

    for r in sorted(results, key=lambda r: r.index):
        if r.error:
            print(f"[{r.index}] FAIL: {r.error}")
        else:
            title = r.response.page.metadata.title or "N/A"
            count = len(r.response.page.content)
            print(f"[{r.index}] {title[:40]}{count} items ({r.elapsed_ms}ms)")

    await client.shutdown()

import asyncio
asyncio.run(main())

Batching Large Requests

For many URLs, batch them to avoid overwhelming the network:

batch_size = 5
for i in range(0, len(all_urls), batch_size):
    batch = all_urls[i : i + batch_size]
    items = [
        ParallelBrowseItem(
            url=None,
            options=BrowseOptions(
                xiaohongshu_params=XiaohongshuParams(feature="note", url=url),
                parse_only=True,
            ),
        )
        for url in batch
    ]
    results = await client.browse_parallel_collect(items, max_concurrent_per_agent=5)
    # process results...

ParallelBrowseResult fields:

Field Type Description
index int Original position in the input list (use this to match results to inputs)
url str The URL that was browsed
response SemanticResponse? Result (None if error)
error str? Error message (None if success)
agent_peer_id str Always empty in HTTP mode. Use response.agent_id instead.
elapsed_ms int Time taken in milliseconds

API Reference

SelaClient

Creating a Client

Constructor Description
SelaClient.with_api_key(key) Pass API key directly (recommended)
SelaClient.from_env() Auto-load from SELA_API_KEY or API_KEY env var
SelaClient.create(config) Full control via SelaClientConfig
import os
from selanet_sdk import SelaClient, SelaClientConfig

# Option 1: Pass API key directly (recommended)
client = await SelaClient.with_api_key(os.environ["SELA_API_KEY"])

# Option 2: Auto-load from env var (SELA_API_KEY or API_KEY)
client = await SelaClient.from_env()

# Option 3: Full configuration
config = SelaClientConfig(api_key="sk_live_xxx", api_server_url="https://api.selanet.ai")
client = await SelaClient.create(config)

Methods

Method Description
browse(url, options?) Browse a URL and get semantic content
browse_parallel_collect(items, max_concurrent?) Browse multiple URLs in parallel
shutdown(timeout_ms?) Gracefully shutdown the client
state() Get current client state

Configuration

BrowseOptions

All fields are optional. Only set what you need.

Field Type Description
parse_only bool Recommended. Skip AI planning, extract only
count int Items to collect via infinite scroll (~10–100)
extract_format str "schema" (default), "markdown", "html"
x_params XParams X (Twitter) search/profile/post params
xiaohongshu_params XiaohongshuParams Xiaohongshu search/note/profile/url params
rednote_params RednoteParams Rednote search/note/profile/url params
youtube_params YouTubeParams YouTube search/watch/comments params
linkedin_params LinkedInParams LinkedIn search/profile/company/feed params
wait_for_agent bool Wait for agent availability within timeout
include_html bool Return raw HTML in response
timeout_ms int Request timeout in ms
session_id str Reuse existing browser session
query str Query hint for semantic extraction
api_key str Per-request API key override
agent_id str Target a specific agent by peer ID

Important: extract_format='schema' only works for supported platforms (X, Xiaohongshu, Rednote, YouTube, LinkedIn). For all other sites, use 'markdown' or 'html'.

Tip: All platform params only require feature. Other fields like query, username, url etc. are optional — pass the target URL in the url field of the params.

SelaClientConfig

Most users should use SelaClient.with_api_key(). Use SelaClientConfig only when you need to customize timeouts or API server URL:

from selanet_sdk import SelaClient, SelaClientConfig

config = SelaClientConfig(
    api_key=os.environ["SELA_API_KEY"],
    api_server_url="https://api.selanet.ai",  # Optional, override API server
    connection_timeout_secs=30,     # Optional, default: 30s
    request_timeout_secs=120,       # Optional, default: 120s
)
client = await SelaClient.create(config)
Field Type Default Description
api_key str? None API key (sk_live_xxx)
api_server_url str? None API Server URL override
connection_timeout_secs int? 30 Connection timeout
request_timeout_secs int? 120 Per-request timeout

All fields are optional.


Response Types

SemanticResponse

result = await client.browse(url, options)

result.session_id           # Browser session ID
result.page                 # SemanticPage
result.page.page_type       # e.g., "x_com::search", "generic"
result.page.content         # list[SemanticContent]
result.page.metadata.title  # Page title
result.collection_stats     # JSON string (when count is used) — use json.loads()
result.extracted_content    # Markdown/HTML text (when extract_format is set)
result.extract_format       # "schema", "html", or "markdown"
result.agent_version        # Agent node version (e.g., "0.2.6")
result.agent_id             # Agent peer ID that processed the request
result.request_id           # Unique request identifier
result.action_result        # ActionResult (Success, Failed, Skipped, etc.)

SemanticContent

for item in result.page.content:
    item.content_type       # e.g., "tweet", "note", "user"
    item.content_id         # Unique ID (if available)
    item.fields_json        # JSON string — use json.loads()
    item.actions            # list[AvailableAction]

collection_stats

When using count, collection_stats contains scroll/collection metadata as a JSON string:

if result.collection_stats:
    stats = json.loads(result.collection_stats)
    # {
    #   "total_collected": 30,
    #   "scroll_count": 5,
    #   "duplicates_removed": 2,
    #   "elapsed_ms": 12340
    # }

Error Handling

from selanet_sdk import SelaError

try:
    result = await client.browse(url, options)
except SelaError.ConfigurationError as e:
    print(f"Bad config: {e}")
except SelaError.TimeoutError as e:
    print(f"Request timed out: {e}")
except SelaError.BrowseError as e:
    print(f"Browse failed: {e}")
except SelaError.DiscoveryError as e:
    print(f"No agents available: {e}")
except SelaError as e:
    print(f"Error: {e}")
Error When
ConfigurationError Invalid config (bad API key format, missing fields)
TimeoutError Request timed out
BrowseError Browse operation failed on agent
DiscoveryError No agents found or agent connection failed
ConnectionError Network connection failed
ProtocolError Rate limited, invalid response, serialization error
InternalError Unexpected internal error

Development

Building from Source

Requires: Rust 1.70+, Python 3.9+, maturin

pip install maturin

cd client-sdk/crates/sela-py
maturin develop --release   # Install in dev mode
maturin build --release     # Build wheel

Running the Example

cd client-sdk
python examples/browse_example.py

Running Tests

cargo test -p sela-py

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

selanet_sdk-0.2.5-py3-none-macosx_11_0_arm64.whl (1.3 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

File details

Details for the file selanet_sdk-0.2.5-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for selanet_sdk-0.2.5-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 904291ae045c0b13f946c4c5d3a9fae6a94ee118f4579d1e61d0c63eb14a36b6
MD5 29b6235ce931cea693188f728df4e20b
BLAKE2b-256 22d1d960f5950321f20326faef5a750a44c200cac4584a44c6849466b36b0bee

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page