통합

Shifter와 함께 사용 Beautiful Soup

Shifter의 레지덴셜 및 ISP 프록시를 Beautiful Soup과 페어링하여 깔끔하고 표현력 있는 Python 스크래핑을 구현하세요. Beautiful Soup은 HTML 파싱을 처리하고 Shifter는 레지덴셜 IP를 처리하므로, 헤드리스 브라우저가 필요하지 않습니다.

빠른 시작

설치

pip install beautifulsoup4 requests lxml

기본 사용법

import requests
from bs4 import BeautifulSoup

proxy_url = "customer-USERNAME-country-us-sid-123ABC:PASSWORD@p.shifter.io:443"
proxies   = {"http": proxy_url, "https": proxy_url}

response = requests.get("https://example.com", proxies=proxies, timeout=30)
soup = BeautifulSoup(response.text, "lxml")

print(soup.title.string)
for article in soup.select("article.post"):
    print(article.h2.text.strip(), "->", article.a["href"])

기능

requests, httpx, aiohttp 및 proxies 딕셔너리를 지원하는 모든 Python HTTP 클라이언트와 깔끔하게 호환됩니다
기본값은 요청별 로테이션이며, 스티키 세션에는 `sid`를, N초 동안의 시간제 고정에는 `ttl-N`을 사용합니다
bs4 4.x 및 Python 3.7+와 호환 — lxml 및 html.parser 백엔드 모두에서 작동
사용자 이름 매개변수를 통한 195개국 이상 Geo 타겟팅 - country, region, city, ASN
정적 또는 JS 부담이 적은 대상에 대해 헤드리스 브라우저 스크래핑보다 자릿수 단위로 더 빠름
Scrapy, FastAPI 스크레이퍼, Airflow 작업, AWS Lambda 및 모든 Python 데이터 파이프라인에 그대로 적용

예시

스티키 세션 + 다중 페이지 크롤링

프록시 사용자 이름에 `sid-XXX`를 추가하여 전체 페이지네이션 크롤링 동안 레지덴셜 IP 하나를 고정하세요. 지역 타겟팅을 위해 `country-uk`와 `city-london`을 추가하세요.

import requests
import secrets
from bs4 import BeautifulSoup
from urllib.parse import urljoin

sid = secrets.token_hex(4)

proxy_url = (
    f"customer-USERNAME-country-uk-city-london-sid-{sid}-ttl-300:"
    f"PASSWORD@p.shifter.io:443"
)

# Use a session so connection pooling and cookies persist across requests.
session = requests.Session()
session.proxies = {"http": proxy_url, "https": proxy_url}
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
    "Accept-Language": "en-GB,en;q=0.9",
})

products  = []
url       = "https://example.co.uk/products"

while url:
    response = session.get(url, timeout=30)
    soup     = BeautifulSoup(response.text, "lxml")

    for card in soup.select(".product-card"):
        products.append({
            "title": card.select_one("h2").text.strip(),
            "price": card.select_one(".price").text.strip(),
            "url":   urljoin(url, card.select_one("a")["href"]),
        })

    next_link = soup.select_one("a.next-page")
    url       = urljoin(url, next_link["href"]) if next_link else None

print(f"Scraped {len(products)} products")

concurrent.futures를 사용한 병렬 스크래핑

요청별 로테이션을 위해 sid를 제거하세요. ThreadPoolExecutor + requests + Shifter는 IP당 속도 제한에 걸리지 않고 수십 개의 동시 요청으로 확장됩니다.

import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, as_completed

# No sid -> every request gets a different residential IP.
PROXY_URL = "customer-USERNAME-country-us:PASSWORD@p.shifter.io:443"

def scrape(url: str) -> dict:
    response = requests.get(
        url,
        proxies={"http": PROXY_URL, "https": PROXY_URL},
        headers={"User-Agent": "Mozilla/5.0 AppleWebKit/537.36"},
        timeout=30,
    )
    soup = BeautifulSoup(response.text, "lxml")

    return {
        "url":   url,
        "title": (soup.title.string or "").strip(),
        "h1":    [h.text.strip() for h in soup.select("h1")],
        "links": [a["href"] for a in soup.select("a[href]")[:20]],
    }

urls = [
    "https://example.com/category/laptops",
    "https://example.com/category/phones",
    "https://example.com/category/tablets",
    "https://example.com/category/wearables",
    # ... hundreds more
]

with ThreadPoolExecutor(max_workers=16) as pool:
    futures = {pool.submit(scrape, u): u for u in urls}
    for f in as_completed(futures):
        try:
            result = f.result()
            print(result["url"], "->", result["title"])
        except Exception as exc:
            print("error:", futures[f], exc)

재시도 + 백오프를 포함한 견고한 크롤링

프로덕션 스크래핑에는 5xx 오류와 연결 오류에 대한 재시도가 필요합니다. urllib3 Retry를 Shifter와 결합하고, 시도마다 새로운 sid를 사용하여 일시적인 차단을 무력화하세요.

import requests
import secrets
from bs4 import BeautifulSoup
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

class ShifterClient:
    """requests.Session that rotates the residential IP on retry."""

    def __init__(self, country="us"):
        self.country = country
        self._session = requests.Session()

        retry = Retry(
            total=5,
            backoff_factor=1.5,
            status_forcelist=[429, 500, 502, 503, 504],
            allowed_methods=["GET", "POST", "HEAD"],
        )
        adapter = HTTPAdapter(max_retries=retry, pool_connections=20)
        self._session.mount("http://",  adapter)
        self._session.mount("https://", adapter)

    def _proxy(self) -> str:
        sid = secrets.token_hex(4)
        return (
            f"customer-USERNAME-country-{self.country}-sid-{sid}:"
            f"PASSWORD@p.shifter.io:443"
        )

    def get(self, url: str, **kwargs) -> requests.Response:
        return self._session.get(
            url,
            proxies={"http": self._proxy(), "https": self._proxy()},
            timeout=kwargs.pop("timeout", 30),
            **kwargs,
        )

client   = ShifterClient(country="de")
response = client.get("https://example.de/products")
soup     = BeautifulSoup(response.text, "lxml")

for product in soup.select(".product"):
    print(product.h2.text.strip(), product.select_one(".price").text.strip())

httpx (async) + Beautiful Soup

수천 개의 페이지에 대한 비동기 팬아웃이 필요하다면 requests 대신 httpx를 사용하세요. 동일한 Shifter URL, 네이티브 async/await, 완전한 Beautiful Soup 호환성.

# pip install httpx beautifulsoup4 lxml
import asyncio
import httpx
from bs4 import BeautifulSoup

PROXY = "customer-USERNAME-country-fr-sid-789GHI:PASSWORD@p.shifter.io:443"

async def fetch(client: httpx.AsyncClient, url: str) -> dict:
    resp = await client.get(url, timeout=30)
    soup = BeautifulSoup(resp.text, "lxml")
    return {
        "url":      url,
        "title":    (soup.title.string or "").strip(),
        "headings": [h.text.strip() for h in soup.select("h2")],
    }

async def main():
    async with httpx.AsyncClient(proxy=PROXY) as client:
        urls = [
            f"https://example.fr/products?page={i}" for i in range(1, 51)
        ]
        results = await asyncio.gather(*[fetch(client, u) for u in urls])

    for r in results:
        print(r["url"], "->", r["title"])

asyncio.run(main())
자주 묻는 질문

자주 묻는 질문

Beautiful Soup와 Shifter 사용에 관한 일반적인 질문.

아니요. Beautiful Soup은 파서이며 HTTP 요청을 직접 만들지 않습니다. 프록시는 bs4와 함께 사용하는 HTTP 클라이언트(requests, httpx, aiohttp, urllib)에 설정합니다. Shifter를 통해 HTML을 가져온 후에는 평소처럼 BeautifulSoup()에 전달하면 됩니다.

requests.get()에 proxies 딕셔너리를 전달하세요: `{"http": "http://USER:PASS@p.shifter.io:443", "https": "..."}`. response.text를 BeautifulSoup()의 첫 번째 인자로 사용하세요. 여러 페이지를 크롤링할 경우, requests.Session을 재사용하여 쿠키와 연결을 유지하세요.

일회성 스크립트, 노트북, 소규모에서 중간 규모 스크래핑에는 Beautiful Soup를 사용하세요. 더 가볍고 읽기 쉽습니다. 내장 큐잉, 재시도, 지속성, 대규모 동시성이 필요할 때는 Scrapy를 사용하세요. 둘 다 Shifter와 완벽하게 호환됩니다.

프록시 사용자 이름에 세션 ID를 추가하세요(예: `customer-USERNAME-country-us-sid-123ABC`). 모든 fetch 작업에서 단일 requests.Session을 재사용하면 Shifter가 동일한 레지덴셜 IP를 반환합니다. `ttl-N`을 추가하면 IP 수명을 연장할 수 있습니다.

예. bs4를 httpx(비동기) 또는 aiohttp와 함께 사용하세요. 반환되는 HTML은 동일하므로 BeautifulSoup()에 동일한 방식으로 전달하면 됩니다. 수천 개의 페이지의 경우, 비동기 팬아웃이 requests를 사용하는 ThreadPoolExecutor보다 상당히 빠릅니다.

예. Shifter는 SDK가 아니라 단순한 HTTP / SOCKS5 게이트웨이이므로, 서버리스와 호환되지 않는 부분이 전혀 없습니다. Lambda 레이어에 requests + bs4 + lxml을 번들로 포함하고, 환경 변수로 프록시 URL을 설정한 다음 평소처럼 호출하세요.

시작하기

Shifter 사용을 함께 시작하기 Beautiful Soup

Shifter의 205M+ 레지덴셜 및 ISP 프록시를 Beautiful Soup과 페어링하여 깔끔하고 표현력 있는 Python 스크래핑을 구현하세요. 요청별 로테이션, 스티키 세션, httpx를 통한 완전한 비동기 지원까지 제공합니다.

Shifter 무료로 체험하기몇 분 만에 설정. 언제든지 취소 가능.