Skip to main content
Tech Tutorials & Programming

Published Nov 22, 2024 · Updated Oct 2, 2026

How to Use cURL in Python: PycURL, Requests, and subprocess

Use cURL in Python with Requests, PycURL, or subprocess. See tested GET, POST, JSON, headers, auth, file, proxy, timeout, and error examples.

How to Use cURL in Python: PycURL, Requests, and subprocess

Python can make the same HTTP requests as a cURL command in three practical ways: translate the command into the Requests library, call libcurl through PycURL, or execute the installed curl program with subprocess.run(). The best method depends on whether you want a Python-native API, libcurl features, or exact compatibility with an existing cURL command.

Quick Answer

Use Requests for most Python HTTP and API code because its request, response, JSON, authentication, file, timeout, and error interfaces are straightforward. Choose PycURL when you specifically need libcurl behavior or detailed transfer controls. Use subprocess.run() when an existing cURL command must run unchanged—but pass arguments as a list, keep shell=False, enforce timeouts, and check the exit status.

Key Takeaways

  • Requests is usually the clearest choice for a new Python HTTP client.
  • PycURL is a Python interface to libcurl, not a wrapper around the curl command-line program.
  • subprocess.run() launches an external cURL process, so the cURL binary must exist on the system.
  • Pass subprocess arguments as a list and avoid shell=True for ordinary cURL calls.
  • Always set timeouts, check HTTP status or process exit codes, and keep TLS verification enabled.
  • Proxy configuration changes the request route; it does not replace authentication, parsing, retries, or permission to access a target.

Requests vs PycURL vs subprocess

The phrase “use cURL in Python” can mean two different things. You might want to reproduce what a cURL command does using Python code, or you might want Python to launch the cURL executable itself.

MethodWhat actually runsBest forMain advantageMain limitation
RequestsA Python HTTP client libraryNew API clients, scripts, downloads, and integrationsReadable Python interface and broad ecosystemDoes not expose every libcurl capability
PycURLlibcurl through a Python extensionTransfer-heavy code or projects needing libcurl optionsDirect access to libcurl behavior and transfer metricsLower-level API and a compiled dependency
`subprocess.run()`The external `curl` executableReusing a known command or a CLI-based workflowClosest match to a tested shell commandSeparate process, cURL dependency, and more careful secret handling

If the goal is simply “convert this cURL request to Python,” start with Requests. If the application already standardizes on libcurl options, consider PycURL. If you must preserve a command exactly, use subprocess safely.

Install the Python Packages

Create a virtual environment so the tutorial does not change system packages:

bash

On Windows PowerShell, activate it with:

bash

Requests is a Python library. PycURL also depends on libcurl and may require operating-system development packages when a compatible wheel is unavailable. The subprocess example needs the curl program on PATH; verify it with:

bash

Complete Python Requests Example

This is the most useful starting point for a normal JSON endpoint:

python

The timeout tuple means five seconds to establish the connection and 20 seconds between received bytes. It is not a guaranteed limit on the entire download. Requests has no default timeout, so production code should supply one.

raise_for_status() raises an HTTPError for unsuccessful HTTP responses. Network failures, TLS errors, proxy errors, timeouts, and redirect failures appear through the Requests exception hierarchy.

Common Requests Examples

GET parameters and headers

Let Requests encode query parameters instead of manually joining strings:

python

Headers should describe the real client. Changing a User-Agent value does not turn a Python HTTP client into a browser, execute JavaScript, or reproduce a browser fingerprint. The dedicated cURL headers guide covers header syntax in more depth.

POST JSON

Use json= for a JSON request body. Requests serializes the dictionary and sets an appropriate content type:

python

Use data= instead when the endpoint expects form-encoded fields rather than JSON.

Basic authentication

python

Load real credentials from environment variables or a secret manager. Do not paste them into an article, repository, screenshot, or exception message.

Upload a file

python

Opening the file inside a with block ensures that Python closes it after the request.

Download a file without loading it all into memory

python

Streaming matters when a response may be too large to hold comfortably in memory. If the server supports checksums or content lengths, validate them before treating the download as complete.

Complete PycURL Example

PycURL exposes libcurl options through setopt(). It does not create response storage automatically, so this example supplies a BytesIO buffer:

python

The PycURL quick start explains that a transfer normally follows three steps: create a Curl object, set its options, and call perform(). Response metadata must be read with getinfo() before closing the object.

PycURL option names generally mirror libcurl's CURLOPT_* names without the CURLOPT_ prefix. For example, CURLOPT_FOLLOWLOCATION becomes pycurl.FOLLOWLOCATION.

POST JSON with PycURL

python

PycURL offers many lower-level transfer controls. That flexibility is useful only when the project needs it; it is not automatically better than Requests for a normal JSON API.

Safe subprocess.run() Usage

Use subprocess when an existing cURL command is already the source of truth or when the Python program must invoke the command-line tool itself:

python

Important details:

  • Each argument is a separate list element.
  • shell=False is the default and is intentionally retained.
  • check=True turns a non-zero cURL exit status into an exception.
  • --fail-with-body makes HTTP 400+ responses fail while preserving the body for diagnosis.
  • cURL's --max-time and Python's subprocess timeout protect different layers.

Do not build one command string from untrusted input and pass it to shell=True. That would ask a shell to interpret metacharacters, substitutions, redirects, and pipelines. If a legitimate workflow needs a pipeline, construct the process chain explicitly or validate each fixed component.

Convert a cURL Command to Python Requests

Consider this command:

bash

Map each option to a Requests argument:

cURL elementRequests equivalent
`--request POST``requests.post(...)`
URL query string`params={...}`
`--header``headers={...}`
`--user``auth=(username, password)`
JSON `--data``json={...}`
`--max-time``timeout=...` with different semantics

The Python version is:

python

Do not translate mechanically without checking semantics. A cURL config can include cookies, client certificates, multipart parts, redirect rules, retry behavior, proxy settings, and binary bodies that require their own Python equivalents.

Use a Proxy in Python

A proxy changes the network route used by the HTTP request. It does not parse a page, execute browser JavaScript, or authorize collection.

Use non-working placeholders in shared code:

bash

Configure an authenticated HTTP proxy in Requests like this:

python

The https dictionary key means “use this proxy for HTTPS destinations.” The proxy URL can still begin with http:// because an HTTP proxy can carry an HTTPS request through CONNECT. Use the actual scheme supplied by the provider.

For SOCKS, install requests[socks] and follow the library's documented socks5 or socks5h behavior. socks5h requests remote DNS resolution through the proxy, while socks5 can resolve locally.

With a Proxidize access point, replace the placeholders with the generated server, port, username, and password. Test the exit IP and expected location from the same Python client before trusting a collection result. The full cURL proxy guide covers HTTP, HTTPS, SOCKS5, authentication, and CLI diagnostics.

Timeouts, Redirects, HTTP Errors, and TLS

ConcernRequestsPycURLcURL through subprocess
Connection timeout`timeout=(connect, read)``CONNECTTIMEOUT``--connect-timeout`
Overall transfer limitRequires additional application logic; read timeout is not a total limit`TIMEOUT``--max-time`, plus subprocess timeout
RedirectsFollowed for GET by default; control with `allow_redirects`Off by default; enable `FOLLOWLOCATION`Enable with `--location`
HTTP 400+ handlingCall `raise_for_status()`Set `FAILONERROR` or inspect `RESPONSE_CODE`Use `--fail-with-body` and `check=True`
TLS verificationEnabled by defaultSupply a trusted CA bundle when neededEnabled by default; use a trusted CA path if required

Do not “fix” a certificate error by permanently disabling TLS verification. Identify whether the failure comes from an outdated CA store, an intercepting corporate proxy, a hostname mismatch, or an invalid server certificate. The cURL TLS guide explains the limited cases where insecure local testing may be acceptable and why it should not become production configuration.

Retries also need deliberate policy. Retry transient connection failures and selected 429/5xx responses only when the operation is safe to repeat. A POST may have completed even if the client did not receive the response. For bounded retry logic in Requests, use the Python Requests retry guide.

Tested Versions and Results

We tested the primary patterns on October 2, 2026 with:

  • Python 3.11.2.
  • Requests 2.34.2.
  • PycURL 7.48.0 linked to libcurl 8.22.0.
  • cURL 7.88.1 linked to the system libcurl 7.88.1.

Requests and PycURL retrieved and parsed https://httpbin.org/json, and the subprocess example returned the same JSON through the installed cURL executable.

We also ran a controlled local test covering Requests GET, JSON POST, Basic authentication, multipart upload, streamed download, and an authenticated HTTP proxy. A PycURL GET and a list-based subprocess.run() cURL call were checked against the same fixture. All assertions passed.

These checks establish that the displayed patterns worked on the recorded versions. They do not prove compatibility with every API, proxy, TLS environment, or operating system. Validate the exact endpoint and dependency versions used in production.

Troubleshooting

SymptomLikely causeWhat to check
`ModuleNotFoundError: requests`Wrong environment or package not installedActivate the virtual environment and run `python -m pip install requests`
`ImportError` while importing PycURLMissing or incompatible libcurl/SSL buildInstall a compatible wheel or required OS development packages, then rebuild in a clean environment
`FileNotFoundError: curl`cURL is absent from `PATH`Install cURL or call the verified executable path
Subprocess returns empty outputcURL wrote an error to stderr or request returned no bodyInspect `returncode` and `stderr`; use `check=True` and `--show-error`
Request hangsNo timeout or a stalled responseAdd connection/read or process timeouts and log the phase that failed
`HTTPError` after `raise_for_status()`Server returned an unsuccessful statusRecord status, safe response excerpt, request ID, and retry only when appropriate
`ProxyError` or HTTP 407Wrong proxy endpoint or authenticationRecheck host, port, scheme, username, password, account state, and allowed source IP
Public IP does not changeRequest did not use the intended proxy or the provider kept the same sessionInspect effective configuration and compare the exit from the same client
TLS certificate errorCA, hostname, interception, or server-certificate problemFix trust configuration; do not disable verification as a permanent workaround
Redirect loopEndpoint or cookie/auth flow keeps redirectingDisable automatic redirects temporarily and inspect the `Location` chain

Conclusion

Requests is the best default for most Python HTTP code. PycURL is appropriate when libcurl controls matter, and subprocess.run() is useful when an established cURL command must remain the executable interface. Whichever method you choose, make timeouts, status checks, TLS verification, secret handling, and output validation explicit.

For API calls and ordinary downloads, custom code is usually enough. When a project instead needs maintained visual extraction workflows, a platform such as Octoparse may be worth evaluating alongside code-first tools. For proxy routing, start with the complete cURL proxy setup guide or compare cURL and Wget before choosing the command-line layer.

Frequently asked questions

Yes. subprocess.run() can launch the installed curl executable. Pass arguments as a list, retain the default shell=False, set both cURL and subprocess timeouts, capture stderr, and use check=True when a non-zero exit should fail the program.

No. Requests is a Python HTTP library. cURL is a command-line program built on libcurl, and PycURL is a Python binding to libcurl. They can send equivalent requests, but their defaults, timeout semantics, redirect behavior, protocol support, and error interfaces differ.

Use Requests for most new Python API clients and scripts. Choose PycURL when the project specifically benefits from libcurl options, transfer information, protocol behavior, or an existing libcurl-based design.

Map the method, URL, query parameters, headers, authentication, body, files, cookies, proxy, and timeout settings individually. Requests is usually the clearest target. Review redirects, TLS, retries, binary bodies, and multipart data rather than translating only the visible URL and method.

No. A normal cURL invocation should use a list of arguments with shell=False, which is the default. Shell mode is only needed for shell syntax such as pipelines or redirects and introduces additional injection risk when any component is variable.

Generate an access point in the Proxidize dashboard, load its host, port, username, and password from environment variables, build the proxy URL, and pass it in Requests' proxies mapping. Then verify the observed exit IP and location from the same client.

Ready to launch?

Proxies built for real operations.

For teams that depend on stability, not luck.