Automate LinkedIn people search and send connection requests on a daily cap. New contacts are appended to a CSV for outreach tracking. Runs headless on a schedule (macOS) or interactively via CLI or GUI.
Built with Python, Selenium, and Brave (or another Chromium-based browser). Optional OpenTelemetry export to a local SigNoz stack.
- Logs into LinkedIn with credentials from
.env - Searches people by semicolon-separated keywords (e.g.
TCS;Accenture;Wipro) with optional location filters - Sends connection requests up to a per-run limit, skipping profiles already in the CSV
- Falls back to My Network → Grow when search results are exhausted
- Writes results to
OutputFolder/dataset.csvand can resume across runs viaOutputFolder/run_state.json
- CLI or GUI —
run_bot.pyfor unattended runs;Run_me.pyfor a desktop launcher - Brave-first — detects and uses your system default browser (Brave, Chrome, Edge, Firefox, Safari)
- Profile reuse — optionally copies cookies from your existing browser profile so you may already be logged in
- Connection limits — cap invites per run; daily scheduling sends at most once per calendar day
- Grow fallback — continues from My Network when people search runs out of Connect buttons
- CSV deduplication — appends to
OutputFolder/dataset.csvand skips known profiles - ChromeDriver auto-fix — detects version mismatches, downloads a matching driver, and retries
- SigNoz observability — optional traces, metrics, and correlated logs (see below)
| Requirement | Notes |
|---|---|
| Python 3.10+ | Tested on macOS |
| Brave Browser (recommended) | Or Chrome / Edge (Chromium-based). Firefox and Safari are supported with limitations. |
| ChromeDriver | Handled automatically via webdriver-manager. Manual install is not required. |
| LinkedIn account | Credentials go in .env (never commit this file). |
| Docker Desktop (optional) | Only needed for the local SigNoz stack. |
git clone https://github.com/Sumit884-byte/linkedin_connection_bot.git
cd linkedin_connection_bot
python3 -m venv venv
source venv/bin/activate # macOS / Linux
# venv\Scripts\activate # Windows
pip install -r requirements.txtcp .env.example .envEdit .env with your credentials and search settings:
LINKEDIN_USERNAME=your_email@example.com
LINKEDIN_PASSWORD=your_password
LINKEDIN_FIELDS=accenture;software engineer
LINKEDIN_LOCATION=
LINKEDIN_CONNECTIONS=10
LINKEDIN_BROWSER=braveSee Configuration for all variables.
CLI (recommended for scheduled / headless runs):
python run_bot.pyGUI:
python Run_me.pyFill in the fields (or rely on values from .env) and click Start The Automation!!
The bot can run once per calendar day while your Mac is on — no fixed clock time.
- Runs when you log in after turning the Mac on
- Re-checks every hour while the Mac stays on (skips if it already ran successfully today)
- Uses a lock file (
OutputFolder/.daily_run.lock) to avoid overlapping runs - Retries once with ChromeDriver auto-fix if the first attempt fails
BROWSER_HEADLESS=true
USE_BROWSER_PROFILE=true
LINKEDIN_BROWSER=brave
USE_NETWORK_GROW=trueLINKEDIN_START_WITH_GROW=true is useful if the previous run already exhausted people search and you want to skip straight to Grow.
chmod +x schedule/install_daily_schedule.sh schedule/daily_run.sh
./schedule/install_daily_schedule.sh installOptional: change the hourly check interval (seconds, minimum 300):
DAILY_CHECK_INTERVAL_SECONDS=1800 ./schedule/install_daily_schedule.sh install./schedule/daily_run.shLogs: OutputFolder/logs/daily-YYYY-MM-DD.log
Launchd stdout/stderr: OutputFolder/logs/launchd.out.log, launchd.err.log
Force another run the same day:
FORCE_DAILY_RUN=1 ./schedule/daily_run.sh./schedule/install_daily_schedule.sh status
./schedule/install_daily_schedule.sh uninstallAll settings can live in .env. Defaults below are what the code uses when a variable is unset (your .env.example may differ).
| Variable | Description | Default |
|---|---|---|
LINKEDIN_USERNAME |
LinkedIn email | — |
LINKEDIN_PASSWORD |
LinkedIn password | — |
LINKEDIN_FIELDS |
Search keywords, semicolon-separated | — |
LINKEDIN_LOCATION |
Location filters, semicolon-separated. Empty = no filter. | — |
LINKEDIN_CONNECTIONS |
Max invites per run (0 = no limit) |
10 |
LINKEDIN_PAGES |
Search result pages per keyword | 1 |
LINKEDIN_BROWSER |
auto, brave, chrome, edge, firefox, safari |
auto |
USE_BROWSER_PROFILE |
Copy cookies from your real browser profile | true |
BROWSER_HEADLESS |
Run without a visible browser window | false |
USE_NETWORK_GROW |
After search is exhausted, connect from Grow | true |
LINKEDIN_START_WITH_GROW |
Skip people search; connect only from Grow | false |
LINKEDIN_RESUME |
On re-run, continue from Grow if search was already exhausted | true |
AUTO_ACCEPT_INVITATIONS |
Accept pending incoming invites on Grow before sending new requests | true |
LINKEDIN_IMPLICIT_WAIT |
Element lookup timeout (seconds) | 2 |
LINKEDIN_CARD_DELAY |
Pause after each connection (seconds) | 0.8 |
LINKEDIN_ACTION_DELAY |
Pause between small UI actions (seconds) | 0.3 |
LINKEDIN_DRIVER_AUTO_FIX_ATTEMPTS |
Max ChromeDriver auto-fix retries per launch | 2 |
SIGNOZ_ENABLED |
Export traces, metrics, and logs to SigNoz | false |
OTEL_EXPORTER_OTLP_ENDPOINT |
SigNoz OTLP gRPC endpoint | http://localhost:4317 |
OTEL_SERVICE_NAME |
Service name in SigNoz | linkedin-connection-bot |
OTEL_DEPLOYMENT_ENVIRONMENT |
Environment label (local, prod, …) |
local |
GUI fields map to the same settings: Keywords → LINKEDIN_FIELDS, Pages → LINKEDIN_PAGES, Location → LINKEDIN_LOCATION.
Connections are written to:
OutputFolder/dataset.csv
Copy the example header if the file does not exist yet:
cp OutputFolder/dataset.csv.example OutputFolder/dataset.csvColumns: Name, Headline, Location, Profile URL
The file is appended on each run. Profiles already present are skipped automatically. Resume state between runs is stored in OutputFolder/run_state.json (gitignored).
The bot exports OpenTelemetry traces, metrics, and correlated logs to a local SigNoz stack when enabled.
Prerequisite: Docker Desktop must be running.
open -a Docker # macOS — if `docker info` fails
docker compose -f pours/deployment/compose.yaml up -dWait ~30–60s, then verify:
docker compose -f pours/deployment/compose.yaml ps
curl -s http://localhost:8080/api/v1/health # expect {"status":"ok"}| Endpoint | URL |
|---|---|
| SigNoz UI | http://localhost:8080 |
| OTLP gRPC ingest | localhost:4317 |
# Stop (keeps data volumes)
docker compose -f pours/deployment/compose.yaml down
# Restart after Docker Desktop reboot
docker compose -f pours/deployment/compose.yaml up -dSIGNOZ_ENABLED=true
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_SERVICE_NAME=linkedin-connection-bot
OTEL_DEPLOYMENT_ENVIRONMENT=localIf SIGNOZ_ENABLED=false, telemetry is a no-op and the bot runs normally.
python run_bot.pyIn SigNoz:
- Services →
linkedin-connection-bot - Traces →
bot.run,bot.login,bot.connect_from_grow, … - Metrics →
bot.connections_sent,bot.run_duration_seconds,bot.driver_errors - Logs → lines with
run_id=andphase=
Dashboard queries and alert ideas: docs/signoz-dashboard.md.
Write-up of the observability setup: docs/blog-signoz-linkedin-bot.md.
After a Brave or Chrome update you may see errors like "This version of ChromeDriver only supports Chrome version X".
The bot tries to fix this automatically: it detects the mismatch, downloads a matching driver via webdriver-manager, kills stale bot browser processes, and retries (up to LINKEDIN_DRIVER_AUTO_FIX_ATTEMPTS times). The daily scheduler also retries once after auto-fix.
If it still fails, confirm LINKEDIN_BROWSER matches the browser you have installed (e.g. brave) and restart Docker/your terminal so no old chromedriver processes linger.
LinkedIn may show CAPTCHA or security challenges. In headless mode there is no window to complete them.
- Set
BROWSER_HEADLESS=false, run once, complete the checkpoint manually, then re-enable headless. - With
USE_BROWSER_PROFILE=true, a successful interactive login is copied into the bot's isolated profile for later headless runs.
If a run is killed mid-flight, you may see "Another bot run is already in progress" or orphaned Brave windows titled with LinkedInBot.
- The daily script removes a stale lock when the PID is no longer running.
- Manually: delete
OutputFolder/.daily_run.lockand quit anyBrave-Browser-LinkedInBot(or similar) processes in Activity Monitor. ./schedule/daily_run.shcallscleanup_stale_bot_processes()before each run.
Raise delays in .env:
LINKEDIN_CARD_DELAY=1.5
LINKEDIN_IMPLICIT_WAIT=3One search page may not have enough Connect buttons. Increase LINKEDIN_PAGES or rely on USE_NETWORK_GROW=true to continue from Grow after search is exhausted.
| Symptom | Fix |
|---|---|
connect: no such file or directory on docker.sock |
Start Docker Desktop (open -a Docker) |
| Nothing on port 8080 | docker compose -f pours/deployment/compose.yaml up -d |
| UI not ready | Wait until signoz-signoz-0 is (healthy) in docker compose ps |
| Bot runs but no telemetry | Confirm SIGNOZ_ENABLED=true and OTLP endpoint http://localhost:4317 |
linkedin_connection_bot/
├── run_bot.py # CLI entry point (.env → automation)
├── Run_me.py # GUI entry point
├── Scripts/
│ ├── selenium_Funct.py # Core automation logic
│ ├── browser_utils.py # Browser detection, launch, ChromeDriver auto-fix
│ ├── telemetry.py # OpenTelemetry / SigNoz export
│ ├── graphical_script.py # Tkinter GUI
│ └── constants.py # Legacy defaults (overridden by .env)
├── schedule/
│ ├── daily_run.sh # Once-per-day runner (used by launchd)
│ └── install_daily_schedule.sh
├── pours/deployment/ # Local SigNoz stack (docker compose)
├── docs/
│ ├── signoz-dashboard.md # Example queries and alerts
│ └── blog-signoz-linkedin-bot.md
├── OutputFolder/
│ ├── dataset.csv.example # CSV header template
│ ├── dataset.csv # Exported contacts (runtime, gitignored)
│ └── logs/ # Daily and launchd logs (gitignored)
├── .env.example
└── requirements.txt
This tool automates actions on LinkedIn. Use it responsibly and in line with LinkedIn's Terms of Service and applicable laws. Automated connection requests can trigger account restrictions. The authors are not responsible for misuse or resulting account actions.
Originally created by Prabal Jain. Fork maintained by Sumit884-byte.
See LICENSE.