๐ ์น ์คํฌ๋ํ ๋ด ํ์ง ์ฐํํ๊ธฐ: curl_cffi ๋ง์คํฐ ์ฒญ์ฌ์ง๐ก ์ํฉ ํด๋ ํ์ฌ ์ํ: ์น์ฌ์ดํธ๋ค์ด ์๋ํ ๋๊ตฌ๋ก๋ถํฐ์ ์ ๊ทผ์ ๊ฐ์งํ๋ ๊ธฐ์ ์ ๊ณ ๋ํํ๋ฉด์, ๊ธฐ์กด์ ๊ฐ๋จํ HTTP ์์ฒญ ๋๊ตฌ๋ค(requests, aiohttp)์ด ๊ณ์ ์ฐจ๋จ๋นํ๊ณ ์์ต๋๋ค. 403 Forbidden, 429 Too Many Requests ์ค๋ฅ๊ฐ ์์ฃผ ๋ฐ์ํ๊ณ , Cloudflare ๊ฐ์ ๋ณด์ ์๋น์ค์์ CAPTCHA๋ฅผ ์๊ตฌํ๊ธฐ๋ ํฉ๋๋ค.
ํต์ฌ ์์ :
User-Agent๋ง ๋ฐ๊ฟ์๋ ์ฐํ ๋ถ๊ฐ๋ฅ (์ด๋ฏธ ์น์ฌ์ดํธ๋ค์ด ๊ทธ ์ ๋๋ ๋๋นํจ)
์น์ฌ์ดํธ๊ฐ ์ฌ๋ฌ ๊ฒน์ ์ง๋ฌธ ์ ๋ณด๋ฅผ ์ข ํฉ์ ์ผ๋ก ๊ฒ์ฌ ์ค
Cloudflare ๊ฐ์ ์ ๋ฌธ ๋ณด์ ์๋น์ค๊ฐ ๊ฐ์ ๋๋ฉด์ ๋ฌธ์ ์ฌํ
์๋ฐ์คํฌ๋ฆฝํธ๋ก ๋์ ์์ฑ๋๋ ์ฝํ ์ธ ๋ ์ ๊ทผ ์์ฒด๊ฐ ๋ถ๊ฐ๋ฅ
๊ธฐ์กด ๋๊ตฌ๋ค๋ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํด์ ์๋ฒ ๋ถํ ์ฆ๊ฐ์์ vs ํ์ค: "User-Agent๋ง ์ค์ ํ๋ฉด ๋ ๊ฒ ๊ฐ์๋ฐ" โ ์ค์ ๋ก๋ ์น์ฌ์ดํธ๊ฐ ํจ์ฌ ๋ ์ ๊ตํ ๋ฐฉ์์ผ๋ก ๋ด์ ๊ตฌ๋ถํ๊ณ ์์์ต๋๋ค.
์ํฅ ๋ฒ์: ์ด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์ง ์์ผ๋ฉด AI ์์ด์ ํธ๊ฐ ์ ํํ๊ณ ์ต์ ์ ๋ฐ์ดํฐ๋ฅผ ์์งํ ์ ์์ด์, ์ ๊ณตํ๋ ๋ต๋ณ์ ์ ๋ขฐ๋๊ฐ ๋จ์ด์ง๋๋ค. ํนํ ๋ฐ์ดํฐ ์์ง์ 15์ด๊ฐ ๊ฑธ๋ฆฌ๋ ์์ ์ด 5์ด๋ก ๋จ์ถ๋๋ฉด์ย ์๋น์ค์ ์๋ต ์๋๊ฐ 3๋ฐฐ ๊ฐ์ ๋ฉ๋๋ค.
๐ ์์ธ ํฌ์๊ทผ๋ณธ ์์ธ: ์น์ฌ์ดํธ๋ค์ด ๋จ์ํ ํน์ฑ(User-Agent)์ด ์๋๋ผ **"๋ธ๋ผ์ฐ์ ์ฒ๋ผ ๋ณด์ด๋์ง ์ค์ ๋ก ๊ฒ์ฆ"**ํ๊ธฐ ์์ํ์ต๋๋ค.
์ด๊ฑด ๋ง์น '์ ๋ถ์ฆ ์ฌ๋ณธ'์์ '์ง์ ์ผ๊ตด์ ๋ณธ๋ค'๋ก ๋ฐ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๋ธ๋ผ์ฐ์ ์ ์์ฃผ ์ธ์ธํ ํน์ง๊น์ง ๋ชจ๋ฐฉํด์ผ ํ๋ ์ํฉ์ด ๋ ๊ฒ์ ๋๋ค.
์ฐ๊ฒฐ ๊ณ ๋ฆฌย (์ด๋ป๊ฒ ์ด ์์ธ์ด ํ์ฌ ๋ฌธ์ ๋ฅผ ๋ง๋ค์๋๊ฐ):
์น์ฌ์ดํธ์ ์ ์ฅ: ๋ด์ด ๊ณ์ ๋ค์ด์ค๋๊น ๋ ์ ๊ตํ ๊ฒ์ฌ ํ์ โ ๋ธ๋ผ์ฐ์ ์ง๋ฌธ ๊ธฐ์ ๋์
๊ธฐ์กด ๋๊ตฌ์ ํ๊ณ: requests ๊ฐ์ ๋๊ตฌ๋ "๋จ์ํ HTTP ์์ฒญ์ ๋ณด๋ด๋" ๊ฒ๋ง ํจ โ ๋ธ๋ผ์ฐ์ ์ฒ๋ผ ์ธ์ธํ๊ฒ ํ๋ํ์ง ๋ชปํจ
๊ฒฐ๊ณผ: ์น์ฌ์ดํธ๊ฐ "๋๋ ๋ด์ด์ผ"๋ผ๊ณ ํ๋จ โ 403 ์ค๋ฅ ๋๋ CAPTCHA์ผ์ ๋น์ :
์ ๋ถ์ฆ ํ์ธ ์์ค: "์ด์ ๋ฉดํ์ฆ ์์ผ์ธ์?" โ User-Agent๋ง ๋ฐ๊พธ๋ ๊ฒ
์ผ๊ตด ์์ฒด์ธ์ ์์ค: "๋น์ ์ด ์ ๋ง ๊ทธ ์ฌ๋์ด ๋ง์?" โ ๋ธ๋ผ์ฐ์ ์ง๋ฌธ ๊ฒ์ฆ (์ง๊ธ ์ํฉ)
๋ ๋ค๋ฅธ ๋น์ : ๊ฐ์ง ๊ฒฝ์ฐฐ์ด ๊ฒฝ์ฐฐ๋ณต๋ง ์ ๊ณ ๋ํ๋๋ ๊ฒ vs ์ค์ ๊ฒฝ์ฐฐ์ฒ๋ผ ํ๋ํ๋ ๊ฒ์ ์ฐจ์ด์จ๊ฒจ์ง ์์ย (๋๋ถ๋ถ ๊ฐ๋ฐ์๊ฐ ๋์น๋ ๊ฒ๋ค):
TLS/JA3 ์ง๋ฌธ: ์ํธํ ์ฐ๊ฒฐ์ ๋งบ์ ๋์ "์ ์ ๋ฐฉ์"์ด ๋ธ๋ผ์ฐ์ ๋ง๋ค ๋ค๋ฆ
HTTP/2 ํ๋ ์ ํฌ๊ธฐ์ ์์: ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋์ ๊ตฌ์ฒด์ ์ธ ๋ฐฉ์
ํค๋ ์์์ ์ค์์ฑ: Accept, Accept-Encoding, Accept-Language ๊ฐ์ ํค๋๋ฅผ ๋ณด๋ด๋ ์์๊ฐ ํฌ๋กฌ์ ์ ํด์ ธ ์์ (ํฌ๋กฌ์ Accept, Accept-Encoding, Accept-Language ์์)๐ ๏ธ ํด๊ฒฐ ์ค๊ณ๋1๋จ๊ณ: curl_cffi ์ค์น ๋ฐ ๊ธฐ๋ณธ ๋์ ํ์ธํ๊ธฐํต์ฌ ํ๋: requests์์ curl_cffi๋ก ๊ฐ์ํ๋, ์ฝ๋ ๊ตฌ์กฐ๋ ๊ฑฐ์ ๋ฐ๋์ง ์๊ฒ ํ ์ ์์ต๋๋ค.
์คํ ๊ฐ์ด๋:
# 1. curl_cffi ์ค์น (pip ์ฌ์ฉ) pip install curl_cffi # 2. ๊ธฐ๋ณธ ์ค์น ํ์ธ python -c "import curl_cffi; print(curl_cffi.__version__)"
์์/์ฝ๋:
# ๋ณ๊ฒฝ ์ (requests ์ฌ์ฉ)
import requests
response = requests.get("https://example.com")
print(response.text)
# ๋ณ๊ฒฝ ํ (curl_cffi ์ฌ์ฉ)
import curl_cffi
response = curl_cffi.get(
"https://example.com",
impersonate="chrome" # โ ์ด ํ ์ค์ด ํต์ฌ!
)
print(response.text)
# ํต์ฌ ๋ณํ ์ค๋ช
# requests๋ "๊ทธ๋ฅ HTTP ์์ฒญ์ ๋ณด๋"
# curl_cffi๋ "ํฌ๋กฌ ๋ธ๋ผ์ฐ์ ์ธ ์ฒ ํด์ ์์ฒญ์ ๋ณด๋"
# impersonate="chrome"์ด "๋๋ ํฌ๋กฌ์ด ๋์ด๋ผ"๋ผ๋ ๋ช
๋ น
์ฑ๊ณต ์งํ: ๊ฐ์ URL์์ requests๋ก๋ 403 ์ค๋ฅ๊ฐ ๋์ง๋ง, curl_cffi๋ก๋ ์ ์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์์ผ๋ฉด ์ฑ๊ณต์ ๋๋ค.
์ฃผ์์ฌํญ:
impersonate="chrome"ย ๋์ ๊ตฌ์ฒด์ ์ธ ๋ฒ์ ์ ์ง์ ํ ์ ์์ต๋๋ค (์:ย impersonate="chrome124")
๋๋ฌด ์๋ก์ด ๋ธ๋ผ์ฐ์ ๋ฒ์ ์ ์ง์ ํ๋ฉด ์คํ๋ ค ์๋ ์ ํ ์ ์์ผ๋ ํฌ๋กฌ 99 ์ดํ ์ต์ ๋ฒ์ ์ฌ์ด์์ ์ ํ2๋จ๊ณ: ๋ก๊ทธ์ธ ํ์ํ ์ฌ์ดํธ ํฌ๋กค๋งํ๊ธฐํต์ฌ ํ๋: ๋จ์ ์กฐํ ํ์ด์ง๋ฟ ์๋๋ผ ๋ก๊ทธ์ธ์ด ํ์ํ ์ฌ์ดํธ๋ย ์ธ์ ์ ์ง๋ฅผ ํตํด ์ ๊ทผ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๊ธฐ
์คํ ๊ฐ์ด๋:
import curl_cffi
# 1๋จ๊ณ: ์ธ์
์์ฑ (์ฌ๋ฌ ์์ฒญ์ ์ฐ๊ฒฐํ๊ธฐ ์ํด)
session = curl_cffi.Session()
# 2๋จ๊ณ: ๋ก๊ทธ์ธ ํ์ด์ง ๋จผ์ ์ ๊ทผ (์ฟ ํค ์ด๊ธฐํ)
session.get("https://example.com/login", impersonate="chrome")
# 3๋จ๊ณ: ๋ก๊ทธ์ธ ์์ฒญ ์ ์ก
login_response = session.post(
"https://example.com/login",
data={
"username": "your_username",
"password": "your_password"
},
impersonate="chrome"
)
# 4๋จ๊ณ: ๋ก๊ทธ์ธ๋ ์ํ๋ก ๋ฐ์ดํฐ ์์ง
protected_page = session.get(
"https://example.com/my-data",
impersonate="chrome"
)
print(protected_page.text)
์์/์ฝ๋:
# ๋ณ๊ฒฝ ์ : ๋งค๋ฒ ๋ก๊ทธ์ธ (๋นํจ์จ์ ์ด๊ณ ์๋ฒ์ ๋ถ๋ด)
for url in urls:
response = curl_cffi.post(url_login, data=login_info)
response = curl_cffi.get(url)
# ๋ณ๊ฒฝ ํ: ํ ๋ฒ ๋ก๊ทธ์ธ, ๊ณ์ ์ฌ์ฉ (ํจ์จ์ )
session = curl_cffi.Session()
session.post(login_url, data=login_info, impersonate="chrome")
for url in urls:
response = session.get(url, impersonate="chrome")
# ์ฟ ํค๊ฐ ์๋์ผ๋ก ์ ์ง๋์ด ๋งค๋ฒ ๋ก๊ทธ์ธ ๋ถํ์
# ํต์ฌ ๋ณํ ์ค๋ช
# session ๊ฐ์ฒด๊ฐ "์ธ์
๊ธฐ์ต"์ ๋ด๋น
# ๋ก๊ทธ์ธํ์ ๋์ ์ฟ ํค๋ฅผ ๊ณ์ ๋ค๊ณ ๋ค๋
์ฑ๊ณต ์งํ: ๋ก๊ทธ์ธ์ด ํ์ํ ํ์ด์ง์์ ์ค์ ์ฌ์ฉ์์ฒ๋ผ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์์ผ๋ฉด ์ฑ๊ณต์ ๋๋ค.
์ฃผ์์ฌํญ:
์ฟ ํค๋ ์ธ์ ๋ด์์๋ง ์ ์ง๋ฉ๋๋ค. ํ๋ก๊ทธ๋จ์ ์ฌ์์ํ๋ฉด ์ฟ ํค๊ฐ ์ฌ๋ผ์ง๋๋ค
๋ง์ฝ ์ฟ ํค๋ฅผ ์ ์ฅํ๋ค๊ฐ ๋์ค์ ์ฌ์ฌ์ฉํด์ผ ํ๋ค๋ฉด, ์ฟ ํค ํ์ผ์ ๋ฐ๋ก ์ ์ฅํ ํ์๊ฐ ์์ต๋๋ค3๋จ๊ณ: ๋์์ ์ฌ๋ฌ ์์ฒญ ์ฒ๋ฆฌํ๊ธฐ (๋น๋๊ธฐ)ํต์ฌ ํ๋: 10๊ฐ ์ฌ์ดํธ ํฌ๋กค๋ง์ด 15์ด ๊ฑธ๋ฆฌ๋ ๊ฒ์ 5์ด๋ก ๋จ์ถ () -ย ๋์ ์ฒ๋ฆฌ ๋์
์คํ ๊ฐ์ด๋:
๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ํ ์์ฒญ์ด ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ ๋ค๋ฅธ ์์ฒญ์ ๋ณด๋ผ ์ ์์ต๋๋ค.
import asyncio
from curl_cffi import AsyncSession
# ์ฌ๋ฌ URL์ ๋์์ ํฌ๋กค๋ง
urls = [
"https://site1.com",
"https://site2.com",
"https://site3.com",
# ... ๋ ๋ง์ URL
]
async def fetch_url(session, url):
"""ํ ๊ฐ์ URL์ ๊ฐ์ ธ์ค๋ ํจ์"""
response = await session.get(url, impersonate="chrome")
return response.text
async def fetch_all():
"""๋ชจ๋ URL์ ๋์์ ์ฒ๋ฆฌ"""
async with AsyncSession() as session:
# asyncio.gather๊ฐ ๋ชจ๋ ์์ฒญ์ ๋์์ ์คํ
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# ์คํ
results = asyncio.run(fetch_all())
print(f"์์งํ ๋ฐ์ดํฐ: {len(results)}๊ฐ")
์์/์ฝ๋:
# ๋ณ๊ฒฝ ์ : ์์ฐจ ์ฒ๋ฆฌ (๋๋ฆผ)
# site1 โ ๊ธฐ๋ค๋ฆผ โ site2 โ ๊ธฐ๋ค๋ฆผ โ site3
# ์ด 3์ด + 3์ด + 3์ด = 9์ด
for url in urls:
response = curl_cffi.get(url, impersonate="chrome")
process(response)
# ๋ณ๊ฒฝ ํ: ๋์ ์ฒ๋ฆฌ (๋น ๋ฆ)
# site1 | site2 | site3
# ๋์ ์งํ โ 3์ด
async def fetch_all():
async with AsyncSession() as session:
tasks = [session.get(url, impersonate="chrome") for url in urls]
responses = await asyncio.gather(*tasks)
return responses
# ํต์ฌ ๋ณํ ์ค๋ช
# "ํ ๋ช
์ ์ฌ๋์ด ์์๋๋ก" โ "์ฌ๋ฌ ๋ช
์ด ๋์์"
์ฑ๊ณต ์งํ: ํฌ๋กค๋ง ์๊ฐ์ด ์ ๋ฐ ์ด์ ์ค์ด๋ค๋ฉด ์ฑ๊ณต์ ๋๋ค.
์ฃผ์์ฌํญ:
๋์์ ๋๋ฌด ๋ง์ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ์ ๋ถ๋ด์ ์ฃผ๋ ์ฃผ์ ( - ์ด์ฉ์ฝ๊ด ํ์ธ ํ์)
์ผ๋ฐ์ ์ผ๋ก 3~10๊ฐ ์ ๋ ๋์ ์ฒ๋ฆฌ๊ฐ ์ ๋นํฉ๋๋ค4๋จ๊ณ: ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ก ์๋ฒ ๋น์ฉ ์ ๊ฐํ๊ธฐํต์ฌ ํ๋: Selenium ๋๋นย ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ 90% ๊ฐ์ย - ๊ฐ์ ์์ ์ ๋ ์์ ์๋ฒ์์ ์ฒ๋ฆฌ
์คํ ๊ฐ์ด๋:
| ๋ผ์ด๋ธ๋ฌ๋ฆฌ | ํผํฌ ๋ฉ๋ชจ๋ฆฌ | ๋ฉ๋ชจ๋ฆฌ ์ฆ๊ฐ | ํน์ง |
|---|---|---|---|
| curl_cffi | 110.55 MB | 38.00 MB | ๊ฒฝ๋ (์ด์์ ) |
| requests | 116.39 MB | 54.75 MB | ์ค๊ฐ ์์ค |
| Selenium | 812.86 MB | 723.14 MB | ๋ฌด๊ฑฐ์ (ํผํด์ผ ํจ) |
์ฑ๊ณต ์งํ: ๋ชจ๋ํฐ๋ง ๋๊ตฌ์์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋ช ํํ ์ค์ด๋ ๊ฒ์ ํ์ธํ๋ฉด ์ฑ๊ณต์ ๋๋ค.
์ฃผ์์ฌํญ:
๊ทธ๋๋ 1,000๊ฐ ์ด์ ๋์ ํฌ๋กค๋งํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋ถํ๊ฐ ์๊ธธ ์ ์์ต๋๋ค
ํ์ํ๋ฉด ๋ฐฐ์น ์ฒ๋ฆฌ (100๊ฐ์ฉ ๋์ด์ ์ฒ๋ฆฌ)๋ฅผ ๋์ ํฉ๋๋ค๐ง ํต์ฌ ๊ฐ๋ ํด๋ถ1. ๋ธ๋ผ์ฐ์ ์ง๋ฌธ(Browser Fingerprinting): ๋น์ ์ ๊ตฌ๋ถํ๋ "์ฌ๊ถ ์ ๋ณด"5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด:
"๋น์ ์ด ํธ์ง๋ฅผ ๋ณด๋ผ ๋, ํธ์ง์๋ '๋๊ฐ ๋ณด๋ธ์ง' ๋ํ๋๋ ๊ฒ๋ค์ด ์์ด์. ์๊ธ์จ, ํ์ ์๊น, ์ฐํ ๋ถ์ด๋ ๋ฐฉ์ ๊ฐ์ ๊ฒ๋ค์ด์. ์ปดํจํฐ๋ ๋ง์ฐฌ๊ฐ์ง์์. ์ด๋ค ๋ฐฉ์์ผ๋ก ์ธํฐ๋ท์ ์ ์ํ๋์ง ๋ณด๋ฉด, ๊ทธ๊ฒ ์ฌ๋์ธ์ง ๋ก๋ด์ธ์ง ์ ์ ์์ด์."
์ค์ํ ์์:
๊ณตํญ ๋ณด์ ๊ฒ์ฌ์์ ์ฌ๊ถ๋ง ์ ์ํ๋ ๊ฒ vs ์ง๋ฌธ, ํ์ฑ, ์ผ๊ตด์ ๋ชจ๋ ํ์ธํ๋ ์์ฒด์ธ์ ๊ฐ์ ๊ด๊ณ
ํ๋ฐฐ ๊ธฐ์ฌ๊ฐ "์ด ์ฌ๋์ด ์ ๋ง ๋ฐฐ์ก๋ฐ์ ์ฌ๋์ด ๋ง์?"๋ฅผ ํ์ธํ๋ ๊ณผ์ ์จ๊ฒจ์ง ์ค์์ฑ:
"์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ๊ธฐ์ ์ด ์๋ฌด๋ฆฌ ๋ฐ์ ํด๋ ์น์ฌ์ดํธ ์ ๊ทผ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค. User-Agent๋ง์ผ๋ก๋ ์ ๋ ์ฐํ ๋ถ๊ฐ๋ฅํ๋ค๋ ๋ป์ ๋๋ค."
์คํด์ ์ง์ค:
์คํด: "User-Agent ๋ฐ๊พธ๋ฉด ๋๋ค"
์ง์ค: "User-Agent๋ ์ง๋ฌธ ์ ๋ณด์ 1% ์ ๋์ผ ๋ฟ. ๋๋จธ์ง 99%๋ TLS, HTTP/2, ํค๋ ์์ ๋ฑ์ผ๋ก ํ๋จํฉ๋๋ค"2. TLS/JA3 ์ง๋ฌธ: "์ ์ํ๋ ๋ฐฉ์"์ด ๋ค๋ฅด๋ค5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด:
"๋ ์ฌ๋์ด ๋ง๋ ๋ ์ ์ํ๋ ๋ฐฉ์์ด ์์ด์. ์ด๋ค ์ฌ๋์ ์์ ํผ์ณ์, ์ด๋ค ์ฌ๋์ ํด์ง ์๊ณ ... ์ด๋ฐ ํน์ง๋ค๋ก '์, ์ด๊ฑด ํฌ๋กฌ์ด๊ตฌ๋', '์ด๊ฑด ํ์ด์ดํญ์ค๊ตฌ๋' ์ ์ ์์ด์."
์ค์ํ ์์:
๊ฐ ๋๋ผ ๋นํ๊ธฐ๊ฐ ๊ณตํญ์ ์ฐฉ๋ฅํ ๋ ํต์ ํ๋กํ ์ฝ์ด ๋ค๋ฅธ ๊ฒ์ฒ๋ผ, ๋ธ๋ผ์ฐ์ ๋ ์ํธํ ์ฐ๊ฒฐ์ ๋งบ์ ๋ "์ธ์ฌ ๋ฐฉ์"์ด ๋ค๋ฆ ๋๋ค
ํฌ๋กฌ์ "A ์ํธํ ๋ฐฉ์, B ๋ณด์ ๊ธฐ๋ฅ" ์์๋ก, ํ์ด์ดํญ์ค๋ ๋ค๋ฅธ ์์๋ก ์ ์ํฉ๋๋ค์จ๊ฒจ์ง ์ค์์ฑ:
"์ด๊ฒ ์ ์ผ ์๋ณํ๊ธฐ ์ด๋ ต๊ณ , curl_cffi์ ๊ฐ์ฅ ํฐ ๊ฐ์ ์ด ๋ฐ๋ก ์ฌ๊ธฐ์ ๋๋ค. curl_cffi๊ฐ ์์๋ค๋ฉด ์ด ๋ถ๋ถ ์ฐํ๋ ๋ถ๊ฐ๋ฅํ์ ๊ฒ์ ๋๋ค."
์คํด์ ์ง์ค:
์คํด: "User-Agent ํค๋๋ง ์์ผ๋ฉด ๋ธ๋ผ์ฐ์ ์ฒ๋ผ ๋ณด์ธ๋ค"
์ง์ค: "TLS/JA3 ์ง๋ฌธ๊น์ง ๋ง์ถฐ์ผ ์ง์ง ๋ธ๋ผ์ฐ์ ๋ก ์ธ์๋ฉ๋๋ค"3. HTTP/2 ํ๋ ์ ์์: "๋ฐ์ดํฐ ๋ณด๋ด๋ ์์๋ ์ค์ํ๋ค"5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด:
"๊ธ์จ๋ฅผ ์ธ ๋, ๋๊ตฌ๋ ์ผ์ชฝ๋ถํฐ ์์ํด์ ์ค๋ฅธ์ชฝ์ผ๋ก, ๋๊ตฌ๋ ์๋ถํฐ ์๋๋ก ์จ์. ๊ทธ ๋ฐฉ์์ผ๋ก๋ '์, ์ด ์ฌ๋'์ด๋ผ๊ณ ์ ์ ์์ด์."
์ค์ํ ์์:
ํ๊ตญ ์ฌ๋์ ํ๊ธ๋ก ์ฐ๊ณ , ์ผ๋ณธ ์ฌ๋์ ์ผ๋ณธ์ด๋ก ์ฐ๋ฏ์ด, ๋ธ๋ผ์ฐ์ ๋ง๋ค ๋ฐ์ดํฐ ์ ์ก ๋ฐฉ์์ด ๋ฌ๋ผ์
ํฌ๋กฌ์ ์ฐฝ ํฌ๊ธฐ๋ฅผ 1000์ผ๋ก, ํ์ด์ดํญ์ค๋ 500์ผ๋ก ์ค์ ํ๋ ์์ ์ฐจ์ด์จ๊ฒจ์ง ์ค์์ฑ:
"requests๋ aiohttp๋ ์ด ๋ถ๋ถ์ ๊ณ ๋ คํ์ง ์๊ธฐ ๋๋ฌธ์, Cloudflare ๊ฐ์ ๊ณ ๊ธ ๋ณด์ ์๋น์ค์ ์ฝ๊ฒ ๊ฑธ๋ฆฝ๋๋ค"
์คํด์ ์ง์ค:
์คํด: "ํค๋์ Content-Type๋ง ๋ง์ถ๋ฉด ๋๋ค"
์ง์ค: "ํ๋ ์ ํฌ๊ธฐ, ์ฐ์ ์์, ์์ถ ๋ฐฉ์๊น์ง ๋ชจ๋ ๋ง์ถฐ์ผ ํฉ๋๋ค"4. ์ธ์ ์ ์ง(Session Persistence): "๋ก๊ทธ์ธ ์ํ ๊ธฐ์ตํ๊ธฐ"5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด:
"๋์๊ด์ ๊ฐ์ ํ์์นด๋ ๋ณด์ฌ์ฃผ๊ณ ์ฑ ์ ๋น๋ฆฌ๋๋ฐ, ๋งค๋ฒ ๋์๊ด ์ง์์ด '์ ๋ง ํ์์ธ์ง ํ์ธ ์ ํด๋ ๋์ฃ ?'๋ผ๊ณ ์์์ฃผ๋ ๊ฑฐ์ฒ๋ผ, ์น์ฌ์ดํธ๋ ํ ๋ฒ ๋ก๊ทธ์ธํ๋ฉด ๊ณ์ ๊ธฐ์ตํด์ค๋๋ค."
์ค์ํ ์์:
ํ์ด์ค๋ถ์ ๋ก๊ทธ์ธํ ํ ์ฌ๋ฌ ํ์ด์ง๋ฅผ ๋์๋ค๋๋๋ฐ ์๋์ผ๋ก ๋ก๊ทธ์ธ ์ํ๊ฐ ์ ์ง๋๋ ๊ฒ
๊ฐ ํ์ด์ง๋ง๋ค ๋ค์ ๋ก๊ทธ์ธํ ํ์ ์๋ ์ด์ ์จ๊ฒจ์ง ์ค์์ฑ:
"์ด๊ฑธ ๊ตฌํ ๋ชปํ๋ฉด, ๋ก๊ทธ์ธ์ด ํ์ํ ์ฌ์ดํธ์์ ๋งค๋ฒ ๋ก๊ทธ์ธํด์ผ ํ๋๊น ์๋๊ฐ 3๋ฐฐ ๋๋ ค์ง๋๋ค"
์คํด์ ์ง์ค:
์คํด: "๋งค๋ฒ ๋ก๊ทธ์ธ ์์ฒญ์ ์๋ก ํ๋ฉด ๋๋ค"
์ง์ค: "์ฟ ํค๋ฅผ ์ ์งํด์ ์๋ฒ์ ๋ถ๋ด ์ฃผ์ง ์๊ณ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌํฉ๋๋ค"5. ๋น๋๊ธฐ ์ฒ๋ฆฌ(Asynchronous Processing): "๋์์ ์ฌ๋ฌ ์ผ ํ๊ธฐ"5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด:
"์๋ง๊ฐ ๋ฐฅ์ ์ง์ผ๋ฉด์ ๋์์ ๋นจ๋๋ฅผ ํ๋ ๊ฒ์ฒ๋ผ, ์ปดํจํฐ๋ ํ ์์ฒญ์ด ๋๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ๋ง๊ณ ๋ค๋ฅธ ์์ฒญ์ ๋์์ ๋ณด๋ผ ์ ์์ด์."
์ค์ํ ์์:
์๋น์์ ์ ์ 1๋ช ์ด ์๋์ ์ฐจ๋ก๋ก ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ vs ์ฌ๋ฌ ์ ์์ด ๋์์ ์ฌ๋ฌ ์๋์ ๋ํ๋ ๊ฒ์ ์ฐจ์ด
10๊ฐ ์ฌ์ดํธ์์ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋๋ฐ, ์์ฐจ์ ์ผ๋ก ํ๋ฉด 30์ด, ๋์์ ํ๋ฉด 3์ด์จ๊ฒจ์ง ์ค์์ฑ:
"์ด๊ฒ๋ง์ผ๋ก๋ ์๋๊ฐ 10๋ฐฐ๊น์ง ๋นจ๋ผ์ง ์ ์์ต๋๋ค. curl_cffi์ AsyncSession์ด ๋ฐ๋ก ์ด๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค."
์คํด์ ์ง์ค:
์คํด: "์ฌ๋ฌ ์์ฒญ์ ๋์์ ํ๋ฉด ์๋ฒ๊ฐ ์ฃฝ์ ๊ฒ ๊ฐ๋ค"
์ง์ค: "3~10๊ฐ ์ ๋๋ ๋๋ถ๋ถ ์๋ฒ๊ฐ ์ถฉ๋ถํ ์ฒ๋ฆฌ ๊ฐ๋ฅํฉ๋๋ค. ์ด์ฉ์ฝ๊ด๋ง ํ์ธํ๋ฉด ๋ฉ๋๋ค"๐ฎ ๋ฏธ๋ ์ ๋ต ๋ฐ ์งํ์๋ฐฉ ์ ๋ตย (์ ์ฌ ๋ฌธ์ ๊ฐ ์ฌ๋ฐํ์ง ์์ผ๋ ค๋ฉด):
์ฃผ๊ธฐ์ ์ธ ์ง๋ฌธ ์ ๋ฐ์ดํธ: curl_cffi๋ ์๋ก์ด ๋ธ๋ผ์ฐ์ ๋ฒ์ ์ด ๋์ค๋ฉด ์ ๋ฐ์ดํธ๋ฉ๋๋ค. ์ต์ ์ 1ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋ฐ์ดํธ ํ์ธ
ย
pip install --upgrade curl_cffi
ย
ย
๋ค์ํ User-Agent ๋กํ ์ด์ : ๊ฐ์ ์ง๋ฌธ๋ง ๊ณ์ ์ฐ๋ฉด ๋์ค์ ํจํด์ด ๊ฐ์ง๋ ์ ์์ผ๋, ๊ฐ๋์ ๋ค๋ฅธ ๋ธ๋ผ์ฐ์ ๋ก ์ ํ
ย
impersonate_options = ["chrome", "firefox", "safari"]
for url in urls:
response = curl_cffi.get(url, impersonate=random.choice(impersonate_options))
ย
ย
์ด์ฉ์ฝ๊ด ๋จผ์ ํ์ธ ๋ฌธํ: ๋ฒ์ ๋ฌธ์ ๋ฅผ ํผํ๊ธฐ ์ํด, ํฌ๋กค๋ง ์ ์ ํญ์ ์น์ฌ์ดํธ์ robots.txt์ ์ด์ฉ์ฝ๊ด์ ํ์ธ์ฅ๊ธฐ์ ๊ณ ๋ ค์ฌํญ:
์น์ฌ์ดํธ๋ค์ ๋ณด์ ๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํฉ๋๋ค. ์ง๊ธ์ curl_cffi๋ก ์ถฉ๋ถํ์ง๋ง, 3๋ ๋ค์๋ ๋ ๋ค๋ฅธ ๊ธฐ์ ์ด ํ์ํ ์ ์์ต๋๋ค. ๊ทธ๋์ย "์ด๋ป๊ฒ ์ฐํํ๋๊ฐ"๋ณด๋ค "์น์ฌ์ดํธ ์ ์ฅ์์ ์ ๋ณดํธํ๋๊ฐ"๋ฅผ ์ดํดํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๊ทธ๋์ผ ์๋ก์ด ๊ธฐ์ ์ ๋์ํ ์ ์์ต๋๋ค.
์ ๋ฌธ๊ฐ ์ฌ๊ณ ๋ฐฉ์:
"์คํฌ๋ํ๊ณผ ์น์ฌ์ดํธ๋ ๊ฒฝ์ ๊ด๊ณ๊ฐ ์๋๋ผ ๊ณต์กด ๊ด๊ณ์ ๋๋ค. ๋๋ฌด ๊ณต๊ฒฉ์ ์ผ๋ก ํฌ๋กค๋งํ๋ฉด, ์น์ฌ์ดํธ๋ ๋ ๊ฐํ ๋ณด์์ ๋์ ํ๊ณ , ๊ฒฐ๊ตญ ๋ชจ๋์๊ฒ ์ ์ข์์ง๋๋ค. ๋ฐ๋ผ์ย ์ต์ํ์ ์์ฒญ์ผ๋ก ํ์ํ ๋ฐ์ดํฐ๋ง ์์งํ๋ ๊ฒ์ด ์ฅ๊ธฐ์ ์ผ๋ก ์ง์ ๊ฐ๋ฅํฉ๋๋ค."
ํ์ต ๋ก๋๋งต:
์ ๋ฌธ (1์ฃผ): curl_cffi์ ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ ์๋ฌ - ๋ก๊ทธ์ธ ์๋ ์ฌ์ดํธ๋ถํฐ ์์
์ค๊ธ (2์ฃผ): ๋ก๊ทธ์ธ ํ์ํ ์ฌ์ดํธ ํฌ๋กค๋ง, ์ธ์ ๊ด๋ฆฌ
๊ณ ๊ธ (3์ฃผ): ๋น๋๊ธฐ ์ฒ๋ฆฌ, ๋์ ์์ฒญ ์ ์ต์ ํ
์ฌํ (4์ฃผ): HTTP/2, TLS ์ง๋ฌธ์ ์๋ฆฌ ์ดํด (ํ์๋ ์๋์ง๋ง ๊น์ด ์๋ ์ดํด)
๋ง์คํฐ (์งํํ): API ๋ถ์, ์์ฒญ ํจํด ์ต์ ํ, ์๋ก์ด ๋ณด์ ๊ธฐ์ ์ ๋ํ ๋น ๋ฅธ ์ ์๐ ์ค์ ์ ์ฉ ์ฒญ์ฌ์ง์ฆ์ ์ ์ฉ (์ค๋๋ถํฐ):
10๋ถ ์ฒดํฌ: curl_cffi ์ค์น ํ ๊ธฐ์กด requests ์ฝ๋ 3์ค๋ง ๋ฐ๊ฟ๋ณด๊ธฐ
ย
# requests ์ฝ๋
response = requests.get(url)
ย
# curl_cffi๋ก ๋ณ๊ฒฝ
response = curl_cffi.get(url, impersonate="chrome")
ย
ย
1์๊ฐ ํ ์คํธ: ์ง๊ธ๊น์ง "403์ด ๋ ์ ์คํจํ๋" URL 3๊ฐ ์๋ํด๋ณด๊ธฐ - ์๋ง ์๋ํ ๊ฒ
ย
2์๊ฐ ๋ก๊ทธ์ธ: ๋ก๊ทธ์ธ ํ์ํ ์ฌ์ดํธ ํ๋ ๊ณจ๋ผ์ Session ๋ฐฉ์์ผ๋ก ๊ตฌํํด๋ณด๊ธฐ์ค๊ธฐ ํ๋ก์ ํธ (1-4์ฃผ):
1์ฃผ์ฐจ: ๊ธฐ์กด ํฌ๋กค๋ง ์คํฌ๋ฆฝํธ ์ ๋ถ curl_cffi๋ก ๋ง์ด๊ทธ๋ ์ด์ , ์๋ ์ธก์
2์ฃผ์ฐจ: 10๊ฐ ์ด์์ URL์ AsyncSession์ผ๋ก ๋์ ์ฒ๋ฆฌ ๊ตฌํ
3์ฃผ์ฐจ: ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง ๋๊ตฌ(์: memory_profiler) ๋์ ํด์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ 90% ๊ฐ์ ํ์ธ
4์ฃผ์ฐจ: ์ค์ ํ๋ก๋์ ์๋ฒ์ ๋ฐฐํฌ, ์์ ์ฑ ๋ชจ๋ํฐ๋ง์๋ จ๋ ์ ๊ฒ:
์ฒดํฌ๋ฆฌ์คํธ:
ย curl_cffi์ ๊ธฐ๋ณธ GET ์์ฒญ์ impersonate ์ต์ ๊ณผ ํจ๊ป ์ฌ์ฉํ ์ ์๋๊ฐ?
ย 403 ์ค๋ฅ๊ฐ ๋๋ ์ฌ์ดํธ์์ curl_cffi๋ก ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋๊ฐ?
ย Session์ ์ฌ์ฉํด์ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ ์งํ๋ฉฐ ์ฐ์ ์์ฒญ์ด ๊ฐ๋ฅํ๊ฐ?
ย AsyncSession์ผ๋ก 3๊ฐ ์ด์์ ์์ฒญ์ ๋์์ ์ฒ๋ฆฌํ ์ ์๋๊ฐ?
ย ๋ฉ๋ชจ๋ฆฌ ๋ชจ๋ํฐ๋ง์ผ๋ก requests ๋๋น ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๊ฐ์๋ฅผ ํ์ธํ๋๊ฐ?๋ชจ๋ ์ฒดํฌํ์ผ๋ฉดย ๋ง์คํฐ ์์ค์ ๋๋ค.
์ถ๊ฐ ๋ฆฌ์์คย (๋์ด๋ ์):
| ๋์ด๋ | ์๋ฃ | ํ์ฉ ์๊ธฐ |
|---|---|---|
| ์ ๋ฌธ | curl_cffi ๊ณต์ ๋ฌธ์ ์์ | ์ฒ์ ๋ฐฐ์ธ ๋ |
| ์ด๊ธ | ์น ์คํฌ๋ํ ํํ ๋ฆฌ์ผ (requests โ curl_cffi ๋น๊ต) | ๊ธฐ๋ณธ ๊ฐ๋ ์ดํด ํ |
| ์ค๊ธ | ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ (asyncio ๊ณต์ ๋ฌธ์) | AsyncSession ๋ฐฐ์ธ ๋ |
| ๊ณ ๊ธ | TLS ํธ๋์ ฐ์ดํฌ, JA3 ์ง๋ฌธ ์๋ฆฌ | ๊น์ด ์๋ ์ดํด ์ํ ๋ |
| ์ฌํ | Cloudflare ๋ณด์ ๊ธฐ์ ๋ถ์ ๋ธ๋ก๊ทธ | ๊ณ ๊ธ ์ฐํ ๊ธฐ๋ฒ ํ์ํ ๋ |
๐ ์ง์ ์์ถ ์์ฝ"์ curl_cffi๋ฅผ ์จ์ผ ํ๋?"๋ฅผ ํ ๋ฌธ์ฅ์ผ๋ก ์์ถํ๋ฉด:
์น์ฌ์ดํธ๋ ๋ ์ด์ User-Agent๋ง์ผ๋ก๋ ๋ด์ ๊ตฌ๋ถํ์ง ์๊ณ , ๋ธ๋ผ์ฐ์ ๊ฐ "์ค์ ๋ก" ์ด๋ป๊ฒ ํ๋ํ๋์ง๊น์ง ๊ฒ์ฆํฉ๋๋ค. curl_cffi๋ ์ด๋ฐ ์ธ์ธํ ํ๋๊น์ง ๋ชจ๋ฐฉํด์, ์ ๊ทผ ๋ถ๊ฐ๋ฅํ๋ ์ฌ์ดํธ์์๋ ๋ฐ์ดํฐ๋ฅผ ์์ ์ ์ผ๋ก ์์งํ ์ ์๊ฒ ๋ง๋ญ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋๋ Selenium์ 90%๋ฅผ ์ค์ฌ์ค๋๋ค.ํต์ฌ 3๊ฐ์ง ๊ธฐ์ตํ๊ธฐ:
๊ธฐ์ ์ ๊ธฐ๋ฐ: TLS/JA3 ์ง๋ฌธ + HTTP/2 ํ๋ ์ ํจํด + ํค๋ ์์ = curl_cffi์ ๊ฐ์
์ฑ๋ฅ ๊ฐ์ : ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ์๋ 3๋ฐฐโ, ๋ฉ๋ชจ๋ฆฌ 90% ๊ฐ์๋ก ์๋ฒ ๋น์ฉ ๋ํญ ์ ๊ฐ
์ค๋ฆฌ์ ์ฑ ์: ๊ฐ๋ ฅํ ์ฐํ ๊ธฐ์ ์ ๊ฐ์ก์ผ๋๋งํผ, ์ด์ฉ์ฝ๊ด ํ์ธ๊ณผ ๊ณผ๋ํ ์์ฒญ ๋ฐฉ์ง๋ ํ์์ด์ ์ค๋น๊ฐ ๋์์ต๋๋ค. curl_cffi๊ฐ "๋จ์ํ ์ ๊ทธ๋ ์ด๋"๊ฐ ์๋๋ผย ์น ๋ฐ์ดํฐ ์์ง์ ํจ๋ฌ๋ค์ ๋ณํ๋ผ๋ ๊ฒ์ ์ดํดํ๊ณ ์๋ค๋ฉด, ์ด๋ค ๋ณด์ ๊ธฐ์ ์ด ๋์๋ ๋น ๋ฅด๊ฒ ์ ์ํ ์ ์์ ๊ฒ๋๋ค.
๋๊ธ
๋๊ธ ๋ก๋ฉ ์ค...