# Python에서 SERP API 사용하기: requests, httpx, asyncio

> Python에서 SerpCrypto SERP API를 사용하세요: 재시도가 있는 requests 클라이언트, 동시성 제한이 있는 async httpx, 페이지네이션과 CSV 내보내기. 테스트한 코드입니다.

- URL: https://serpcrypto.com/ko/docs/python/
- 마지막 업데이트: 2026-10-22
- 발행자: SerpCrypto (https://serpcrypto.com)

재시도가 있는 재사용 클라이언트, 결과가 끝나면 멈추는 페이지네이션 루프, 동시성 제한 내로 제한된 async 검색, 순위용 일별 CSV. 복사해 붙여넣는 코드로, 문법 검사를 마쳤고 저희 패키지는 설치할 필요가 없습니다.

**빠른 답변 — Python에서 SERP API를 어떻게 호출하나요?**

`https://serpcrypto.com/api/v1/search`에 `requests` 또는 `httpx`로 GET 요청을 보내고, `Authorization: Bearer` 헤더에 키를, `params`에 검색 쿼리를 넣은 뒤 `response.json()`을 읽으세요. **SerpCrypto**은 성공한 검색 1건당 크레딧 1개를 청구하며, US$0.30/1K 검색부터 시작하고, 실패한 호출의 재시도는 무료입니다. 암호화폐로 결제하고 KYC가 없습니다. 아래 코드는 `requests` 또는 `httpx`만 있으면 됩니다.

## Python 설정 한눈에 보기

| 속성 | 값 |
| --- | --- |
| 설치 | `pip install requests` 또는 `pip install httpx` |
| 키 | 환경 변수 `SERPCRYPTO_API_KEY` |
| 헤더 | `Authorization: Bearer <key>` |
| 타임아웃 | 60~90초: 검색이 실시간으로 실행됩니다 |
| 재시도 | 429, 502, 504만 백오프로 재시도하며, 오류는 무료입니다 |
| 병렬 검색 | `GET /credits`부터 최대 `concurrency_limit` |

## 재사용 가능한 Python 클라이언트는 어떻게 설정하나요?

헤더에 키를 넣고 세 가지 일시적 오류에 대한 재시도 정책을 가진 `requests.Session`을 하나 만드세요. 세션은 연결을 재사용하므로, 검색 루프에서 TLS 핸드셰이크에 드는 시간이 줄어듭니다. 파일을 `serp_client.py`로 저장하세요. 다음 예제들이 이를 불러옵니다.

urllib3의 `Retry`는 새 시도 전마다 더 오래 기다리며, `Retry-After` 헤더가 오면 이를 따릅니다. `400`, `401`, `402`은 재시도하지 않으며, 이들은 두 번째 시도가 아니라 수정이 필요합니다.

*serp_client.py*

```python
"""serp_client.py: a small, reusable client for the SerpCrypto SERP API (requests)."""
import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

API_URL = "https://serpcrypto.com/api/v1/search"
CREDITS_URL = "https://serpcrypto.com/api/v1/credits"


def make_session():
    session = requests.Session()
    session.headers["Authorization"] = "Bearer " + os.environ["SERPCRYPTO_API_KEY"]
    retry = Retry(
        total=4,
        backoff_factor=1.0,  # waits about 1 s, 2 s, 4 s between attempts
        status_forcelist=(429, 502, 504),
        allowed_methods=("GET",),
        respect_retry_after_header=True,
    )
    session.mount("https://", HTTPAdapter(max_retries=retry))
    return session


SESSION = make_session()


def search(q, **params):
    """Run one search (1 credit if it succeeds) and return the parsed JSON."""
    r = SESSION.get(API_URL, params={"q": q, **params}, timeout=90)
    r.raise_for_status()
    return r.json()


def credits():
    """Free: credits left, used today and the concurrency limit."""
    r = SESSION.get(CREDITS_URL, timeout=30)
    r.raise_for_status()
    return r.json()


if __name__ == "__main__":
    data = search("project management software", gl="us", hl="en")
    for item in data.get("organic_results", []):
        print(item["position"], item["link"])
    print(credits())
```

## 10개 넘는 결과를 어떻게 수집하나요?

다음 페이지를 하나씩 요청하세요. 페이지당 약 10개의 유기적 결과로, Google이 2025년 9월 num=100을 더 이상 지원하지 않게 된 이후 제공되는 방식입니다. 추가 페이지마다 크레딧이 1개 더 차감됩니다. `pagination.next_page`이 없으면 바로 멈추세요. 더 가져올 것이 없고, 크레딧도 지킬 수 있습니다.

대부분의 순위 추적 작업에는 첫 페이지나 처음 세 페이지면 충분합니다. 클릭이 그쪽에 집중되기 때문입니다. 키워드 하나에 30개 결과는 크레딧 3개가 듭니다.

*페이지네이션: 최대 세 페이지, 조기 중단*

```python
from serp_client import search


def top_results(q, pages=3, **params):
    """Collect organic results over several pages: each page is 1 credit."""
    rows = []
    for page in range(1, pages + 1):
        data = search(q, page=page, **params)
        for item in data.get("organic_results", []):
            rows.append({"page": page, "position": item["position"], "title": item["title"], "link": item["link"]})
        if not data.get("pagination", {}).get("next_page"):
            break  # no further page: stop early and save credits
    return rows


rows = top_results("crm for small business", pages=3, gl="gb", hl="en")
print(len(rows), "results")
```

## 많은 검색을 병렬로 어떻게 실행하나요?

계정 한도에 맞춘 `asyncio.Semaphore`과 함께 `httpx.AsyncClient`을 사용하세요. 스크립트는 무료 크레딧 엔드포인트에서 `concurrency_limit`을 읽으므로, 지원팀이 한도를 올리면 자동으로 적응합니다. 한도를 넘는 검색은 어차피 `429`을 받으므로, 세마포어가 그 시도를 낭비하지 않게 합니다.

각 작업은 일시적 오류를 점점 늘어나는 대기로 재시도하며, 네 번 실패하면 `None`를 반환하므로, 고집스러운 쿼리 하나가 배치 전체를 막지 않습니다.

*asyncio + httpx: 한도 내 병렬 검색*

```python
"""Run many searches in parallel without exceeding the account's concurrency limit."""
import asyncio
import os

import httpx

API_URL = "https://serpcrypto.com/api/v1/search"
CREDITS_URL = "https://serpcrypto.com/api/v1/credits"
HEADERS = {"Authorization": "Bearer " + os.environ["SERPCRYPTO_API_KEY"]}


async def search_one(client, gate, q):
    async with gate:  # at most N searches at the same time
        for attempt in range(4):
            r = await client.get(API_URL, params={"q": q, "gl": "us", "hl": "en"})
            if r.status_code in (429, 502, 504):
                await asyncio.sleep(2 ** attempt)
                continue
            r.raise_for_status()
            return q, r.json()
        return q, None


async def main(queries):
    async with httpx.AsyncClient(headers=HEADERS, timeout=90) as client:
        limits = (await client.get(CREDITS_URL)).json()
        gate = asyncio.Semaphore(limits["concurrency_limit"])
        results = await asyncio.gather(*(search_one(client, gate, q) for q in queries))
    for q, data in results:
        first = (data or {}).get("organic_results", [{}])[0].get("link")
        print(f"{q!r}: {first}")


asyncio.run(main(["best vpn router", "home espresso machine", "4k monitor for coding"]))
```

## 일별 순위를 CSV 파일로 어떻게 저장하나요?

결과마다 한 행을 추가하세요. 날짜, 키워드, 순위, 링크, 그리고 API가 적용한 설정을 담습니다. `gl`과 `device`을 직접 만든 변수가 아니라 `search_parameters`에서 읽으면, 파일에 실제로 사용된 값이 기록됩니다.

cron이나 Windows 작업 스케줄러로 하루에 한 번 스크립트를 실행하세요. 모바일에서 키워드 세 개는 하루 크레딧 3개, 한 달에 약 90개입니다.

*오늘의 순위를 rankings.csv에 추가*

```python
import csv
from datetime import date

from serp_client import search

KEYWORDS = ["standing desk", "ergonomic chair", "monitor arm"]
FIELDS = ["day", "keyword", "position", "title", "link", "gl", "device"]

with open("rankings.csv", "a", newline="", encoding="utf-8") as fh:
    writer = csv.DictWriter(fh, fieldnames=FIELDS)
    if fh.tell() == 0:
        writer.writeheader()
    for kw in KEYWORDS:
        data = search(kw, gl="gb", hl="en", device="mobile")
        applied = data["search_parameters"]
        for item in data.get("organic_results", []):
            writer.writerow({
                "day": date.today().isoformat(),
                "keyword": kw,
                "position": item["position"],
                "title": item["title"],
                "link": item["link"],
                "gl": applied["gl"],
                "device": applied["device"],
            })
```

## 어떤 Python HTTP 클라이언트를 써야 하나요?

스크립트와 cron 작업에는 `requests`을 사용하세요. 간단하고 재시도 어댑터가 성숙해 있습니다. 웹 앱, 에이전트, 수백 개의 키워드 배치처럼 비동기 코드가 필요할 때는 `httpx`을 사용하세요. 둘 다 같은 요청을 보내고 같은 JSON을 받습니다. API는 어느 쪽을 선택하든 상관하지 않습니다.

어느 라이브러리든 타임아웃을 너무 짧게 잡지 마세요. 5초 후에 포기하는 클라이언트는 아직 실행 중이던 검색을 다시 보내 두 번 결제할 수 있습니다.

## 내 스크립트는 비용이 얼마나 들까요?

결과 수가 아니라 검색 횟수를 세세요. 호출 한 번이 성공하면 크레딧 1입니다. 키워드 500개, 각 1페이지, 매일 30일이면 15,000회 검색입니다. 검색 25,000회 팩(US$18.75)에서도 여유가 남고, 선불 검색 크레딧은 만료되지 않습니다: 사용하지 않은 검색은 사용할 때까지 계정에 남아 있습니다. 

잔액은 위의 `credits()` 헬퍼로 코드에서 확인하세요. 비용은 들지 않습니다.

## Python 관련 질문

### PyPI에 SerpCrypto 패키지가 있나요?

없습니다. 예제는 널리 쓰이는 두 라이브러리인 `requests` 또는 `httpx`로 HTTPS endpoint를 호출하므로, 설치하거나 업데이트할 저희 패키지는 없습니다. 위의 클라이언트는 40줄 정도이고 직접 소유하게 됩니다.

### 이 코드는 어떤 Python 버전을 지원하나요?

Python 3.9 이상입니다. 비동기 예제는 Python 3.7부터 제공되는 `asyncio.run`을 사용하고, 나머지는 표준 라이브러리와 `requests` 또는 `httpx`만 사용합니다.

### Jupyter notebook에서 API를 사용할 수 있나요?

네. Jupyter를 시작하기 전에 `SERPCRYPTO_API_KEY`을 설정하거나, notebook 안에서 `getpass`로 키를 읽어 저장된 셀에 남지 않게 하세요. notebook에서는 `asyncio.run(main(...))` 대신 `await main(...)`을 사용하세요.

### 결과를 pandas DataFrame으로 바꾸려면 어떻게 하나요?

결과 딕셔너리 목록을 `pandas.DataFrame(data["organic_results"])`에 넘기세요. 각 field가 column이 되고, `date` 같은 누락된 optional field는 빈 셀이 됩니다.

---

SerpCrypto: SerpCrypto는 개발자와 AI 에이전트를 위한 SERP API로, Google 및 Bing 검색 결과를 JSON으로 반환하며 신분증 업로드 없이 암호화폐로 결제합니다. 선불 검색 크레딧 US$0.30/1K 검색는 만료되지 않습니다. KYC 없이 — 신분증도, 셀피도, 서류도 없습니다. 암호화폐 결제.
