ํฌ๋กค๋ง

ํฌ๋กค๋ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ ์„œ๋น„์Šค๋“ค์ด ํฌ๋กค๋ง์„ ๊ฐ์ง€ํ•˜๊ณ  ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ์–ด์ฑ…๋“ค, ๊ทธ ๊ธฐ๋‚˜๊ธด ์‹ธ์›€๊ณผ ๊ฒฝํ–ฅ์„ฑ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•ด๋ณธ๋‹ค.
ํฌ๋กค๋ง ๋Œ€์ƒ ์‹œ์Šคํ…œ์€ HTTP ๊ธฐ๋ฐ˜ ์›น ์‹œ์Šคํ…œ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค.




์ •์  ํฌ๋กค๋ง๊ณผ ๋™์  ํฌ๋กค๋ง

ํฌ๋กค๋ง์€ ํฌ๊ฒŒ 2๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋‚˜๋‰œ๋‹ค.



์ •์  ํฌ๋กค๋ง

์ •์  ํฌ๋กค๋ง์€ ์Šคํฌ๋ž˜ํ•‘(scrapping)์ด๋ผ๊ณ ๋„ ํ•œ๋‹ค.
์–ด์ฐŒ๋˜์—ˆ๋“  ๋ชจ๋“  ์‚ฌ์ดํŠธ๋Š” API๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์ค€๋‹ค.

์ €๊ธฐ์„œ ํ•„์š”ํ•œ API๋งŒ API ํด๋ผ์ด์–ธํŠธ๋กœ ํ˜ธ์ถœํ•˜๊ณ , ๊ทธ ์‘๋‹ต๊ฐ’์„ ํŒŒ์‹ฑํ•ด์„œ ์ ์ ˆํžˆ ์ˆ˜์ง‘ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์ด๋Ÿฐ ์ •์  ํฌ๋กค๋ง์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์ตœ์†Œํ•œ์œผ๋กœ ์†Œ๋ชจํ•˜๊ณ  ๋งค์šฐ ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ, ์‚ฌ์ดํŠธ์— ๋”ฐ๋ผ์„œ๋Š” ๊ตฌํ˜„ํ•˜๊ธฐ๊ฐ€ ๊ต‰์žฅํžˆ ๋ฒˆ๊ฑฐ๋กญ๊ฑฐ๋‚˜, ํ˜น์€ ์ฐจ๋‹จ์„ ํšŒํ”ผํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค์–ด์ ธ์žˆ์„ ์ˆ˜๋„ ์žˆ๋‹ค.



B. ๋™์  ํฌ๋กค๋ง

๋™์  ํฌ๋กค๋ง์€ ๊ทธ์— ๋Œ€ํ•œ ๋Œ€์•ˆ์œผ๋กœ, ์ง„์งœ ๋ธŒ๋ผ์šฐ์ €๋‚˜ ๊ฐ€๋ฒผ์šด ๋ธŒ๋ผ์šฐ์ € ๊ตฌํ˜„์„ ์‚ฌ์šฉํ•ด์„œ ์‚ฌ์ดํŠธ๋ฅผ ์ง์ ‘ ์—ด๊ณ , ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ๋งํ•œ๋‹ค. ๋Œ€ํ‘œ์ ์ธ ๊ตฌํ˜„์ฒด๋กœ๋Š” ์…€๋ ˆ๋‹ˆ์›€, puppeteer, playwright ๋“ฑ์ด ์žˆ๋‹ค.

์…€๋ ˆ๋‹ˆ์›€์€ ๋Œ€๋ถ€๋ถ„์˜ ์–ธ์–ด์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, puppeteer๋ฅผ ๋น„๋กฏํ•œ ์—ฌํƒ€ ์—”์ง„๋“ค์€ node.js ํ™˜๊ฒฝ์—์„œ ์ œ๊ณต๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์‚ฌ์‹ค ํฌ๋กค๋ง์„ ํ•œ๋‹ค๊ณ  ํ•˜๋ฉด nodejs ํ™˜๊ฒฝ์„ ์„ ํƒํ•˜๋Š”๊ฒŒ ๋Œ€์•ˆ์ด ๋งŽ์€ ํŽธ์ด๋‹ค.
๊ฐ๊ฐ์ด ๋™์ž‘ ๋ฐฉ์‹์ด ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด๊ณ , ๊ฒฝ์šฐ์— ๋”ฐ๋ผ์„œ ํฌ๋กค๋Ÿฌ ์ฐจ๋‹จ ํŒจํ„ด์„ ํšŒํ”ผํ•˜๊ธฐ์— ์šฉ์ดํ•œ ์ด์ ์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ๋‹ค.

headless ๋ชจ๋“œ
๊ทธ๋Ÿฐ๋ฐ ๋˜, ์‚ฌ์ดํŠธ๋ฅผ ์ง์ ‘ ํ™”๋ฉด์œผ๋กœ ์—ด๊ณ  ๋ Œ๋”๋งํ•˜๋Š”๊ฑด ๋ฆฌ์†Œ์Šค ์†Œ๋ชจ๊ฐ€ ๊ทน์‹ฌํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ํฌ๋กค๋ง ์—”์ง„๋“ค์€ headless ๋ชจ๋“œ๋ž€๊ฑธ ํ†ตํ•ด์„œ ํ™”๋ฉด ์ฒ˜๋ฆฌ๋ฅผ ์ƒ๋žตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค. ์ด๊ฑธ ์จ์•ผ ๊ทธ๋‚˜๋งˆ ์กฐ๊ธˆ ๋นจ๋ผ์ง€๊ณ  ๋ฆฌ์†Œ์Šค ์†Œ๋ชจ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.


์ž ๊ทธ๋Ÿผ, ์ด์ œ๋ถ€ํ„ฐ๋Š” ์„œ๋น„์Šค๋“ค์ด ํฌ๋กค๋Ÿฌ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ฐจ๋‹จํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก ๋“ค์„ ํ•˜๋‚˜์”ฉ ๋‹ค๋ค„๋ณด๊ฒ ๋‹ค.




์„œ๋ฒ„ ํ™˜๊ฒฝ ๊ตฌ์„ฑ (with Rust hyper)

์ผ๋‹จ ๋ฐฉ์–ด์ž์˜ ์ž…์žฅ์—์„œ๋„ ํ™•์ธ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ, ํฌ๋กค๋ง ๋Œ€์ƒ์ด ๋˜๋Š” ๋ชจํ˜• ์„œ๋ฒ„๋„ ํ•˜๋‚˜ ๋„์šฐ๊ฒ ๋‹ค.
์–ธ์–ด๋Š” Rust๋กœ ์„ ํƒํ–ˆ๋‹ค. ํŠน๋ณ„ํ•œ ์ด์œ ๋Š” ์—†๋‹ค.

[package]
name = "just_test"
version = "0.1.0"
edition = "2021"

[dependencies]
hyper = { version = "1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http-body-util = "0.1"
hyper-util = { version = "0.1", features = ["full"] }
flate2 = "1.0"
use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;

async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    let response = Response::builder()
        .status(StatusCode::OK)
        .body(Full::new(Bytes::from(
            "<html><body><h1>OK</h1></body></html>",
        )))
        .unwrap();

    Ok(response)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([0, 0, 0, 0], 3333));

    let listener = TcpListener::bind(addr).await?;

    loop {
        let (stream, _) = listener.accept().await?;

        let io = TokioIo::new(stream);

        tokio::task::spawn(async move {
            if let Err(err) = http1::Builder::new()
                .serve_connection(io, service_fn(hello))
                .await
            {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}

๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰ํ•˜๋ฉด

์ผ๋‹จ ์ž˜ ๋ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ณต๊ฒฉ์ž, ํฌ๋กค๋Ÿฌ๋Š” nodejs ํ™˜๊ฒฝ์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค.

์ด๊ฒƒ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ž˜ ๊ธ์–ด์˜ฌ ๊ฒƒ์ด๋‹ค.

์ด์ œ ํฌ๋กค๋Ÿฌ๋ฅผ ๋ง‰์•„๋ณด์ž.




User-Agent ๊ธฐ๋ฐ˜ ์ฐจ๋‹จ

์˜ˆ์ „๋ถ€ํ„ฐ ๊ต‰์žฅํžˆ ๋งŽ์ด ์‚ฌ์šฉํ–ˆ๋˜ ๋ฐฉ๋ฒ•์ด๋‹ค.
์š”์ฆ˜์—๋Š” ์ด๊ฑธ ์ค‘์ ์œผ๋กœ ๋ง‰์ง€๋Š” ์•Š์ง€๋งŒ, ์—ฌ์ „ํžˆ ์•„๋ฌดํŠผ ์—†์œผ๋ฉด ๋ง‰ํžˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์ดํŠธ์— ์ง„์ž…ํ•œ๋‹ค๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ๋˜์ง€๋Š” ์š”์ฒญ ํ—ค๋”๋“ค์ด ์žˆ๋‹ค.
๊ทธ ์ค‘ ๋Œ€ํ‘œ์ ์ธ ๊ฒƒ์ด ๋ธŒ๋ผ์šฐ์ € ์†์„ฑ์„ ๋‚˜ํƒ€๋‚ด๋Š” User-Agent๋‹ค.

๋ธŒ๋ผ์šฐ์ €๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋™์  ํฌ๋กค๋ง์„ ํ•  ๊ฒฝ์šฐ์—๋Š” ์ด๊ฒƒ๋„ ๋ธŒ๋ผ์šฐ์ €์™€ ๋™๋“ฑํ•˜๊ฒŒ ๋„ฃ์–ด์ค˜์„œ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ, HTTP client๋ฅผ ์จ์„œ ์ฐŒ๋ฅธ๋‹ค๋ฉด ์ด์ƒํ•œ ๊ฐ’์ด ํฌํ•จ๋˜๊ฑฐ๋‚˜ ์•„์˜ˆ ์•ˆ๊ฐˆ ์ˆ˜๋„ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ axios์˜ ๊ฒฝ์šฐ์—๋Š” User-Agent ํ—ค๋”๋กœ ์ž๊ธฐ์ฃผ์žฅ์„ ํ•œ๋‹ค.
ํ—ค๋”๋ฅผ ๋ช…์‹œํ•˜์ง€ ์•Š์•„๋„ ์•Œ์•„์„œ ์ €๋ ‡๊ฒŒ ์•”์‹œ์ ์œผ๋กœ ๋ณด๋‚ด๋Š” ์ˆจ์€ ๋™์ž‘๋“ค์ด ์ˆจ์–ด์žˆ๋‹ค.

ํŠนํžˆ User-Agent์— axios/1.7.9์ฒ˜๋Ÿผ ๋ฒ„์ „์ด ๋ฐ•ํ˜€์žˆ๋Š”๊ฑด ๋„ˆ๋ฌด ๋ป”ํ•˜๋‹ค.

์ด๊ฑด nodejs/axios๋งŒ ๊ทธ๋Ÿฐ๊ฑด ์•„๋‹ˆ๊ณ , ๋Œ€๋ถ€๋ถ„ ์–ธ์–ด๋“ค์˜ HTTP client ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ๋‹ค ์ €๋Ÿฐ ๋™์ž‘์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด User-Agent ํ—ค๋”๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ null๋กœ ๋ณด๋‚ด์„œ ์•„์˜ˆ ์‚ฌ๋ผ์ง€๊ฒŒ ํ•˜๊ฑฐ๋‚˜, ์•„๋‹ˆ๋ฉด ์ง„์งœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณด๋‚ด๋Š” User-Agent๋ฅผ ๋˜‘๊ฐ™์ด ๋ณด๋‚ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.
๋ณดํ†ต์€ ํ›„์ž๊ฐ€ ๋” ์ข‹์€ ์„ ํƒ์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์˜ˆ์ „์—๋Š” ๊ฐ™์€ User-Agent๋กœ ๊ณผ๋„ํ•œ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด ํ•ด๋‹น User-Agent์™€ IP ์Œ์œผ๋กœ ์ฐจ๋‹จ์„ ๊ฑฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” User-Agent๋ฅผ ๋žœ๋ค์œผ๋กœ ํ• ๋‹นํ•ด์„œ ํšŒํ”ผํ•˜๋ฉด ํšŒํ”ผ๊ฐ€ ๋๋Š”๋ฐ, ์š”์ฆ˜์€ ๊ทธ๋Ÿฐ ๊ณณ์ด ๊ฑฐ์˜ ์—†์„ ๊ฒƒ์ด๋‹ค.




IP ๊ธฐ๋ฐ˜ ์ฐจ๋‹จ

์ด๋ž˜๋‚˜์ €๋ž˜๋‚˜ ํฌ๋กค๋Ÿฌ๋ฅผ ๋ง‰๋Š” ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š” IP ๊ธฐ๋ฐ˜์œผ๋กœ ํ†ต์ œ๋ฅผ ๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์„œ๋ฒ„๋Š” ๋‹น์—ฐํžˆ ํด๋ผ์ด์–ธํŠธ์˜ ๊ณต์ธ IP๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ณ , ํŠน์ • IP์—์„œ ๊ณผ๋„ํ•œ ์š”์ฒญ์ด๋‚˜ ์˜์‹ฌ์Šค๋Ÿฌ์šด ์š”์ฒญ ํŒจํ„ด์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ์ฐจ๋‹จ์„ ๋จน์ผ ์ˆ˜ ์žˆ๋‹ค.
๊ณต์ธ IP๋Š” ๋ฐ”๊ฟ”๊ฐ€๋ฉด์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€๋ถ€๋ถ„์˜ ์ƒํ™ฉ์—์„œ ์œ ํšจํ•œ ๋ฐฉ๋ฒ•์ด๊ณ , ๋˜ ๋Œ€๋ถ€๋ถ„์˜ ํฌ๋กค๋Ÿฌ ํƒ์ง€ ์‹œ์Šคํ…œ๋“ค์€ IP์— ๊ธฐ๋ฐ˜ํ•œ ์ฐจ๋‹จ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.

๋ฌผ๋ก  ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด์„œ ํšŒํ”ผ ๋ฐฉ๋ฒ•์ด ์—†๋Š”๊ฑด ๋˜ ์•„๋‹ˆ๋‹ค.
๊ณต์ธ IP๋ฅผ ์—ฌ๊ธฐ์ €๊ธฐ์„œ ์ž”๋œฉ ๊ธ์–ด๋ชจ์•„์„œ ๋Œ์•„๊ฐ€๋ฉด์„œ ์˜๋ฉด ๋˜ ๋˜๋Š”๊ฑฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

AWS, GCP ๊ฐ™์€ ํผ๋ธ”๋ฆญ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋ฅผ ์“ด๋‹ค๋ฉด, ์ธ์Šคํ„ด์Šค๋ฅผ ๋„์šธ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๊ณต์ธ IP๋ฅผ ๋ฐœ๊ธ‰๋ฐ›๋Š”๋‹ค. ์ฐจ๋‹จ๋‹นํ•˜๋ฉด ์ƒˆ๋กœ ์žฌ์‹œ์ž‘ํ•ด์„œ ์ฐŒ๋ฅด๋ฉด ๋˜ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
๊ฒŒ๋‹ค๊ฐ€ AWS Lambda, GCP Function ๊ฐ™์€ ์„œ๋ฒ„๋ฆฌ์Šค ์„œ๋น„์Šค๋“ค์€ ์‹คํ–‰๋ ๋•Œ๋งˆ๋‹ค ๋ฌด์ž‘์œ„์˜ ์œ„์น˜์—์„œ ๋ฌด์ž‘์œ„์˜ ๊ณต์ธ IP๋ฅผ ๊ฐ€์ง€๊ณ ์„œ ๋™์ž‘ํ•œ๋‹ค. ์ด๋Ÿฐ๊ฑธ ํ”„๋ก์‹œ๋กœ ์“ฐ๋Š” ๊ฒƒ๋„ ์“ธ๋งŒํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.

๊ทผ๋ฐ ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด์„œ ํด๋ผ์šฐ๋“œ๋กœ ์ฐŒ๋ฅด๋Š” ๊ฒƒ๋„ ๋งŒ๋Šฅ์€ ์•„๋‹ˆ๋‹ค.
AWS ๊ฐ™์€ ์œ ๋ช…ํ•œ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค๋“ค์˜ IP ๋Œ€์—ญ์€ ์ •ํ•ด์ ธ์žˆ๊ณ  ์ž˜ ์•Œ๋ ค์ ธ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ, ๊ทธ ๋Œ€์—ญ์„ ํ†ต์งธ๋กœ ์‚ฌ์ „์— ์ฐจ๋‹จํ•ด๋ฒ„๋ฆฌ๋ฉด ๋˜๋Š” ๊ฒƒ์ด๋‹ค. ์‹ค์ œ๋กœ ๋งŽ์€ ํฌ๋กค๋Ÿฌ ํƒ์ง€ ์†”๋ฃจ์…˜๋“ค์ด ์ž์ฒด์ ์ธ IP ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๋ฅผ ๋“ค๊ณ ์„œ ์ฐจ๋‹จ์„ ๋จน์ธ๋‹ค. ๊ทธ๋ž˜์„œ ์š”์ฆ˜์€ AWS IP๋กœ ์ ‘๊ทผ์ด ์•ˆ๋˜๋Š” ์„œ๋น„์Šค๋“ค์ด ๊ฝค ์žˆ๋‹ค.




CAPTCHA

๊ทผ๋ฐ ์˜์‹ฌ์Šค๋Ÿฌ์šด ํŒจํ„ด์ด ๋ฐœ๊ฒฌ๋˜์—ˆ๋‹ค๊ณ  ํ•ด์„œ, ๋ฌด์ž‘์ • ์ฐจ๋‹จ์„ ๊ฑธ์–ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ๋„ ๋งˆ๋ƒฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ๋ณผ ์ˆ˜๋Š” ์—†๋‹ค.
์ง„์งœ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž์ธ๋ฐ ์–ด์ฉŒ๋‹ค ์ฐํ˜€์„œ ์–ต์šธํ•˜๊ฒŒ ์ฐจ๋‹จ๋‹นํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๊ณต์ธ IP์— ํ•จ๊ป˜ ๋ฌถ์ธ ๋ฌด๊ณ ํ•œ ํ”ผํ•ด์ž๋“ค์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋ž˜์„œ ๋งŽ์€ ํฌ๋กค๋Ÿฌ ์ฐจ๋‹จ ์†”๋ฃจ์…˜๋“ค์€ IP์— ์ฐจ๋‹จ์„ ๊ฑธ๋•Œ, ์‚ฌ๋žŒ์ž„์„ ํ™•์ธํ•ด์„œ ์ฐจ๋‹จ์„ ํ’€์–ด์ฃผ๋Š” ์‹œ์Šคํ…œ์„ ๋„์ž…ํ–ˆ๋‹ค.
๊ทธ๊ฒŒ ๋ฐ”๋กœ CAPTCHA๋ผ๋Š” ์‹œ์Šคํ…œ์ด๋‹ค.

ํ”„๋กœ๊ทธ๋žจ์ด ํ•˜๊ธฐ ์–ด๋ ต๊ณ , ์‚ฌ๋žŒ์€ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ์ถœ์ œํ•ด์„œ, ๊ทธ๊ฑธ ํ’€๋ฉด ์ฐจ๋‹จ์„ ํ’€์–ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

์ดˆ๊ธฐ์—๋Š” ๋‹ค ์ €๋ ‡๊ฒŒ ๊ทธ๋ƒฅ ๊พน ๋ˆ„๋ฅด๋Š” ์บก์ฑ ๋ผ์„œ ํ’€๊ธฐ ์‰ฌ์› ๋‹ค. ๊ทธ๋ƒฅ ์…€๋ ˆ๋‹ˆ์›€ ๋„์›Œ์„œ ์ขŒํ‘œ ์ฐ๊ณ  ํด๋ฆญ๋งŒ ํ•ด๋„ ๋˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์š”์ฆ˜์—๋Š” ๋ฌด์Šจ ๋“œ๋ž˜๊ทธํ•ด์„œ ํผ์ฆ์„ ํ‘ธ๋Š” ๊ฒƒ๋„ ์žˆ๊ณ , ๋ฐฉํ–ฅ ๋งž์ถ”๋Š” ๊ฒƒ๋„ ์žˆ๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์šด ์บก์ฑ ๋“ค์ด ๋งŽ์•„์ ธ์„œ ๊ณต๊ฒฉ์ž ์ž…์žฅ์—์„œ๋Š” ๋งค์šฐ ์–ด๋ ค์šด ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„์กŒ๋‹ค.

์ด๊ฒƒ๋„ ์š”์ฆ˜์€ ๋”ฅ๋Ÿฌ๋‹ ๊ธฐ๋ฐ˜์œผ๋กœ ๋šซ๋Š” ์‹œ๋„๋„ ์žˆ๊ณ  ๊ณ„์†ํ•ด์„œ ์ฐฝ๊ณผ ๋ฐฉํŒจ์˜ ์‹ธ์›€์ด ์ง€์†๋˜๊ธด ํ•˜๋Š”๋ฐ, ๋Œ€์ฒด๋กœ๋Š” ๋ฐฉ์–ด์ž๊ฐ€ ์ข€ ๋” ์œ ๋ฆฌํ•œ ๊ฒƒ ๊ฐ™๋‹ค.




์ฟ ํ‚ค ๊ธฐ๋ฐ˜์˜ ํŒจํ„ด ์ถ”์ 

๋งŽ์€ ์‚ฌ์ดํŠธ๋Š” ์ฟ ํ‚ค๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ํŒจํ„ด์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์ฟ ํ‚ค๊ฐ€ ์—†์œผ๋ฉด ์ ‘๊ทผ์„ ์•„์˜ˆ ์ œํ•œํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด, ์ฟ ํ‚ค๊ฐ€ ๊ฐ™์€๋ฐ ๊ฐ‘์ž๊ธฐ ์‚ฌ์šฉ์ž์˜ IP ์ฃผ์†Œ๊ฐ€ ํ•œ๊ตญ์—์„œ ๋ฏธ๊ตญ IP๋กœ ๋ฐ”๋€๋‹ค๊ฑฐ๋‚˜ ํ•˜๋ฉด ๋ฐ”๋กœ ์˜์‹ฌ์Šค๋Ÿฌ์šด ์‚ฌ์šฉ์ด๋ผ๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋ž˜์„œ ์‹ค์ œ๋กœ ๊ทธ๋Ÿฐ์ง“์„ ํ•˜๋ฉด ์ฐจ๋‹จ๋‹นํ•˜๋Š” ์‚ฌ์ดํŠธ๊ฐ€ ๋งค์šฐ ๋งŽ๋‹ค.




์ด๋ฒคํŠธ ์ถ”์ 

์‚ฌ์šฉ์ž์˜ ํ•˜๋“œ์›จ์–ด ๊ธฐ๋ฐ˜ ํ–‰๋™์œผ๋กœ ํฌ๋กค๋Ÿฌ๋ฅผ ์‹๋ณ„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ๋‹ค.

๋งˆ์šฐ์Šค ์ด๋™, ํด๋ฆญ, ํ‚ค ์ž…๋ ฅ ๋“ฑ์— ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋“ฑ๋กํ•ด์„œ, ์ถฉ๋ถ„ํ•œ ๋งˆ์šฐ์Šค ๋™์ž‘ ์ด๋ฒคํŠธ์™€ ํ•จ๊ป˜ ์‚ฌ์ดํŠธ ์ด๋™์ด ๋œ๋‹ค๋ฉด ์ •์ƒ์ ์ธ ์‚ฌ์šฉ์ž๋กœ ํŒ๋‹จํ•˜๊ณ , ๋งˆ์šฐ์Šค ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ์—†๋Š”๋ฐ ์‚ฌ์ดํŠธ ์ด๋™๋งŒ ๋˜๋ฉด ํฌ๋กค๋ง์œผ๋กœ ํŒ๋‹จํ•˜๊ณ  ์ฐจ๋‹จ์„ ๋จน์ด๋Š” ๊ฒƒ์ด๋‹ค.

์‹ค์ œ๋กœ Cloudflare๊ฐ€ ์ ๊ทน ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.




ํฌ๋กค๋Ÿฌ ํƒ์ง€ ๊ธฐ๋ฒ•: Fingerprinting

ํฌ๋กค๋Ÿฌ๋ฅผ ํƒ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๋Š”๋ฐ, ๋˜ ํ”ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ์ ‘๊ทผ๋ฒ•์ด Fingerprint๋ผ๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด ์ด๊ฑด ํ•ด๋‹น ์š”์ฒญ์ž๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ ์œ ๋ฒˆํ˜ธ ๊ฐ™์€๊ฑธ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฒƒ์ธ๋ฐ, ๊ณ„์ธต์ด ๋ช‡๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.
์—ฌ๊ธฐ์„œ ๋‹ค๋ฃจ์ง€ ์•Š์€ ๊ฒƒ๋„ ๋ช‡๊ฐ€์ง€๊ฐ€ ๋” ์žˆ๋‹ค.
์•„์šฐํŠผ ์š”์ง€๋Š”, ์ƒ์„ฑ๋œ fingerprinting์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ‘๋งˆ์ˆ ์„ ์ฅ์–ด์งœ์„œ ํฌ๋กค๋Ÿฌ๊ฐ€ ํ•˜๋Š” ๊ฑฐ์ง“๋ง์„ ์‹๋ณ„ํ•˜๋Š” ๊ฒƒ์— ์žˆ๋‹ค.



TLS ํ”„๋กœํ† ์ฝœ ์ˆ˜์ค€ Fingerprinting

TLS ํ•ธ๋“œ์…ฐ์ดํฌ๋กœ ์•”ํ˜ธํ™” ์ž‘์—…์„ ํ•˜๋Š” ์‹œ์ ์— ํŒจํ‚ท๋“ค์„ ๋ถ„์„ํ•ด์„œ Fingerprint๋ฅผ ๋งŒ๋“œ๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.
์ด๊ฑด ๋‹จ์ˆœํžˆ ์‚ฌ์šฉ์ž๋ฅผ ๊ณ ์œ ํ•˜๊ฒŒ ์‹๋ณ„ํ•˜๋Š”๊ฒŒ ๊ทธ์น˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ์‹ค์ œ๋กœ ๋ธŒ๋ผ์šฐ์ € ๋ง๊ณ  ์ด์ƒํ•œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ ‘๊ทผํ–ˆ๋Š”์ง€๊นŒ์ง€๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
์ผ๋ฐ˜์ ์ธ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ TLS ์•”ํ˜ธํ™”๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹๊ณผ, ๊ธฐํƒ€ ํด๋ผ์ด์–ธํŠธ๋“ค์ด TLS๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด ๋Œ€์ฒด๋กœ ๋‹ฌ๋ผ์„œ ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„์ด๋‹ค.

๊ทธ๋ž˜์„œ User-Agent์— ๋“ค์–ด์žˆ๋Š” ๋ธŒ๋ผ์šฐ์ € ์ •๋ณด์™€, TLS Fingerprint๋กœ ์•Œ์•„๋‚ธ ๋ธŒ๋ผ์šฐ์ € ์ •๋ณด๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€๋ฅผ ์•Œ์•„๋‚ด๊ณ , ๋‹ค๋ฅด๋‹ค๋ฉด ํฌ๋กค๋Ÿฌ๋กœ ํŒ๋‹จํ•ด์„œ ์ฐจ๋‹จ์„ ๋จน์ผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ Cloudflare๊ฐ€ ์ด๋ ‡๊ฒŒ ํ•œ๋‹ค.



Canvas ๊ธฐ๋ฐ˜ Fingerprinting

canvas๋‚˜ ๊ธฐํƒ€ ํ•˜๋“œ์›จ์–ด ์ •๋ณด ๋“ฑ์„ ํ™œ์šฉํ•˜๋ฉด ํ•ด๋‹น ๋จธ์‹ ์ด๋‚˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ๊ฑฐ์˜ ๊ณ ์œ ํ•œ ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
์•„๋ž˜๋Š” ๊ทธ์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ๋‹ค.

function generateFingerprintFromCanvas() {
  // canvas ์ƒ์„ฑ
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  const txt = "abz190#$%^@ยฃรฉรบ";

  ctx.fillStyle = "rgb(255,0,255)";
  ctx.beginPath();
  ctx.rect(20, 20, 150, 100);
  ctx.fill();
  ctx.stroke();
  ctx.closePath();
  ctx.beginPath();
  ctx.fillStyle = "rgb(0,255,255)";
  ctx.arc(50, 50, 50, 0, Math.PI * 2, true);
  ctx.fill();
  ctx.stroke();
  ctx.closePath();

  ctx.textBaseline = "top";
  ctx.font = '17px "Arial 17"';
  ctx.textBaseline = "alphabetic";
  ctx.fillStyle = "rgb(255,5,5)";
  ctx.rotate(0.03);
  ctx.fillText(txt, 4, 17);
  ctx.fillStyle = "rgb(155,255,5)";
  ctx.shadowBlur = 8;
  ctx.shadowColor = "red";
  ctx.fillRect(20, 12, 100, 5);

  const src = canvas.toDataURL();
  return src;
}
generateFingerprintFromCanvas();

์ด๊ฑด ์ง„์งœ ๋Œ€์ถฉ ๋งŒ๋“ค์–ด์ ธ์žˆ๋Š”๊ฑฐ๋ผ ํฐ ์˜๋ฏธ๋Š” ์—†๋Š”๋ฐ, ์•„๋ฌดํŠผ device ์ˆ˜์ค€์—์„œ ๋ถˆ๋ณ€์ธ ๊ฐ’์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

Cloudflare์˜ ๊ฒฝ์šฐ์—๋Š” ์ ‘์† ๊ธฐ๋ก์„ ๊ธฐ๋ฐ˜์œผ๋กœ User-Agent์™€ canvas fingerprint ๊ธฐ๋ก์„ ์Œ“์•„๋†จ๋‹ค๊ฐ€, User-Agent์™€ canvas fingerprint ๊ฐ’ ์Œ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ํฌ๋กค๋Ÿฌ๋กœ ๊ฐ์ง€ํ•˜๋Š” ํŠธ๋ฆญ์„ ์‚ฌ์šฉํ•œ๋‹ค.




ํ—ค๋“œ๋ฆฌ์Šค ๋ธŒ๋ผ์šฐ์ € ๊ฐ์ง€ (puppeteer)

์ด๊ฒƒ๋„ ํ•˜๋‚˜์˜ ์˜ˆ์‹œ๋งŒ ๋“ค์–ด๋ณด๊ฒ ๋‹ค. ๋๋„ ์—†์ด ๋‚˜์˜ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์„œ๋ฒ„์—์„œ ๋ฟŒ๋ ค์ฃผ๋Š” ํด๋ผ์ด์–ธํŠธ์ธก ์Šคํฌ๋ฆฝํŠธ์— ํ—ค๋“œ๋ฆฌ์Šค ๋ธŒ๋ผ์šฐ์ €์—๋งŒ ์กด์žฌํ•˜๋Š” ๊ฐ’์„ ๊ฐ์ง€ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์‹ฌ์œผ๋ฉด, ์ด ๋˜ํ•œ ๊ฐ•๋ ฅํ•œ ํฌ๋กค๋ง ๋ฐฉ์–ด๋ฒ•์ด ๋  ์ˆ˜ ์žˆ๋‹ค.

์›น๋“œ๋ผ์ด๋ฒ„ ํ™œ์„ฑํ™” ๊ฐ™์€ ๊ฒƒ์€ ํ†ต์ƒ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์—†์œผ๋‚˜, puppeteer ๊ฐ™์€ ํ—ค๋“œ๋ฆฌ์Šค ๋ธŒ๋ผ์šฐ์ €์—๋Š” true๋กœ ์กด์žฌํ•˜๋Š” ๊ฐ’์ด๋‹ค.
๊ทธ๋ž˜์„œ ์ €๊ฑธ ๊ฐ์ง€ํ•˜๋ฉด ๋กœ๊ทธ๋„ ์ฐ๊ณ  ์ฐจ๋‹จ๋„ ์ฐ๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

์ฐจ๋‹จ API๋Š” ๋Œ€์ถฉ ์ด๋ ‡๊ฒŒ ๋šซ์—ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋„์šฐ๋ฉด

์•„๋ฌด ๋ฌธ์ œ ์—†์ด ๋™์ž‘ํ•˜์ง€๋งŒ

ํผํŽซํ‹ฐ์–ด๋กœ ๋„์šฐ๋ฉด

import puppeteer from "puppeteer";

async function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto("http://localhost:3333");
  await sleep(1000);

  await page.setViewport({ width: 1080, height: 1024 });

  const textContent = await page.evaluate(() => {
    return document.querySelector("h1")?.textContent;
  });

  console.log(textContent);
}

main();

๋ฐ”๋กœ ์›น๋“œ๋ผ์ด๋ฒ„๊ฐ€ ๊ฐ์ง€๋ผ์„œ ์ฐจ๋‹จ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋™์ž‘ํ•  ๊ฒƒ์ด๋‹ค.


์ด๊ฑด ๋น„๋‹จ ํ—ค๋“œ๋ฆฌ์Šค ๋ชจ๋“œ์—๋งŒ ํ•ด๋‹น๋˜๋Š”๊ฑด ์•„๋‹ˆ๊ณ , headless ๋ชจ๋“œ๋ฅผ ๋„๊ณ  ์ผœ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์…€๋ ˆ๋‹ˆ์›€๊ณผ puppeteer๋ฅผ ํฌํ•จํ•œ ๋Œ€๋ถ€๋ถ„์˜ ํฌ๋กค๋ง์šฉ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ „๋ถ€ ์ด ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์ด ๋ฌธ์ œ์— ํ•œํ•ด์„œ๋ผ๋ฉด ํ•ด๊ฒฐ๋ฒ•์ด ์—†๋Š”๊ฑด ์•„๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์šฐ๊ฒจ๋„ฃ์–ด์„œ ๋™์ ์œผ๋กœ ์ € ๊ฐ’์„ ์กฐ์ž‘์‹œํ‚ฌ ์ˆ˜๋„ ์žˆ๊ณ , ์‹คํ–‰์‹œ์— ํ”Œ๋ž˜๊ทธ๋ฅผ ๋„ฃ์–ด์„œ webdriver๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋œ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฌธ์ œ๋Š”, ์ด๋Ÿฐ ํƒ์ง€๋ฒ•์ด ๊ฝค ๋‹ค์–‘ํ•˜๊ณ , ์ง€๊ธˆ๋„ ๋ณด๊ณ ๋˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
ํ—ค๋“œ๋ฆฌ์Šค ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—๋งŒ ์กด์žฌํ•˜๋Š” ํŠน์ˆ˜ํ•œ ๋™์ž‘์ด๋‚˜ ๊ฐ’๋“ค์ด ๋„ˆ๋ฌด ๋งŽ๋‹ค๋ณด๋‹ˆ ์—ฌ๊ธฐ๋„ ์ฐฝ๊ณผ ๋ฐฉํŒจ์˜ ์‹ธ์›€์ด ์ง€์†๋˜๊ณ  ์žˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋Ÿฐ ํด๋ผ์ด์–ธํŠธ์ธก ๊ฒ€์ฆ์„ ํ”ผํ•˜๋Š” ๊ฒƒ์— ํ•œํ•ด์„œ๋Š” ์•„์˜ˆ ํ—ค๋“œ๋ฆฌ์Šค ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  HTTP Client๋กœ ์ •์  ํŒŒ์‹ฑ์„ ํ•˜๋Š”๊ฒŒ ํšจ๊ณผ์ ์ผ ์ˆ˜ ์žˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ ๋™์ž‘์ด ์•ˆ๋˜๋‹ˆ๊นŒ...!

๋‹ค์Œ์€ ๋ฐฉ๊ธˆ ์‚ฌ์šฉํ•œ ์˜ˆ์ œ ์ฝ”๋“œ๋‹ค.

import puppeteer from "puppeteer";

async function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto("http://localhost:3333");
  await sleep(1000);

  await page.setViewport({ width: 1080, height: 1024 });

  const textContent = await page.evaluate(() => {
    return document.querySelector("h1")?.textContent;
  });

  console.log(textContent);
}

main();
use std::convert::Infallible;
use std::net::SocketAddr;

use http_body_util::Full;
use hyper::body::Bytes;
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper::{Request, Response, StatusCode};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;

const DETECT_PUPPETEER_SCRIPT: &str = r#"
<script>
let isBot = false;

if (navigator.webdriver) {
    console.log(1)
    isBot = true;
}

if (isBot) {
	console.log("Your bot has been detected!")
    fetch("/banned");
}
</script>
"#;

async fn hello(
    request: Request<hyper::body::Incoming>,
) -> Result<Response<Full<Bytes>>, Infallible> {
    let path = request.uri().path();
    if path == "/banned" {
        println!("์ฐจ๋‹จ๋จ!!");

        let response = Response::builder()
            .status(StatusCode::OK)
            .body(Full::new(Bytes::from(
                "<html><body><h1>Banned</h1></body></html>",
            )))
            .unwrap();

        return Ok(response);
    }

    request.headers().iter().for_each(|(name, value)| {
        println!("HEADER {}: {}", name.as_str(), value.to_str().unwrap());
    });

    let response_body = format!("<html><body><h1>OK</h1></body>{DETECT_PUPPETEER_SCRIPT}</html>")
        .as_bytes()
        .to_vec();

    let response = Response::builder()
        .status(StatusCode::OK)
        .body(Full::new(Bytes::from(response_body)))
        .unwrap();

    Ok(response)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([0, 0, 0, 0], 3333));

    let listener = TcpListener::bind(addr).await?;

    loop {
        let (stream, _) = listener.accept().await?;

        let ip = stream.peer_addr()?.ip();
        println!("Connection from: {}", ip);

        let io = TokioIo::new(stream);

        tokio::task::spawn(async move {
            if let Err(err) = http1::Builder::new()
                .serve_connection(io, service_fn(hello))
                .await
            {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}

ํฌ๋กค๋ง ๋Œ€์ƒ ์‚ฌ์ดํŠธ๊ฐ€ ์ด๋Ÿฐ ๋ฐฉ์–ด๋ฒ•์„ ํ•œ๋‘๊ฐ€์ง€๋งŒ ์“ด๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋šซ์„ ์ˆ˜ ์žˆ๊ฒ ์ง€๋งŒ, ์ด ๋ชจ๋“  ๋ฐฉ์–ด๋ฒ•์„ ์ ์šฉํ•ด๋†จ๋‹ค๋ฉด ๋šซ๊ธฐ๊ฐ€ ์ •๋ง ์–ด๋ ค์šธ ๊ฒƒ์ด๋‹ค.

์ด ์•ž์€ ํ—˜๋‚œํ•œ ๊ธธ์ด๋‹ค.



์ฐธ์กฐ
https://medium.com/@mayankchandel2567/how-does-cloudflare-bot-detection-work-d77179756cdc
https://murraycole.com/posts/web-crawling-stealth
https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
https://medium.com/@mayankchandel2567/exploring-methods-of-evading-datadomes-bot-protection-a-comprehensive-guide-for-2023-ef5274ee1698
https://piprogramming.org/articles/How-to-make-Selenium-undetectable-and-stealth--7-Ways-to-hide-your-Bot-Automation-from-Detection-0000000017.html
https://dev.gmarket.com/94