웹사이트 게시판을 크롤링할 때는 RSS 피드와 달리 표준화된 규격이 없기 때문에, 해당 사이트의 URL 구조와 게시물 번호 체계를 분석하는 것이 가장 중요합니다.
일반적으로 다음과 같은 기준들을 조합하여 판단합니다.
대부분의 게시판은 URL 내에 해당 게시물만을 지칭하는 고유한 ID(숫자 또는 문자열)를 포함하고 있습니다.
https://example.com/board/view?idx=12345에서 **12345**가 고유 식별자입니다.Primary Key나 Unique Index로 사용합니다.고유 번호가 URL 파라미터 형태가 아니라 경로 형태(https://example.com/posts/my-first-post)일 때 사용합니다.
m.example.com/123www.example.com/123example.com/123?source=main?utm_..., ?ref=...)를 제거한 **'대표 URL(Canonical URL)'**을 추출하여 비교 기준으로 삼습니다.URL에 고유 ID가 없고, URL 자체가 수시로 변하는 특수한 경우에 사용합니다.
제목 + 작성자 + 작성일을 하나의 문자열로 합친 뒤, 이를 해시 함수(MD5, SHA-256)로 변환하여 고유 키를 생성합니다.눈에 보이는 URL에는 ID가 없지만, 목록 페이지의 HTML 소스를 보면 고유 번호가 숨겨져 있는 경우가 많습니다.
<tr data-article-id="99887">...</tr>data- 속성이나 id 속성 값을 파싱하여 식별자로 활용합니다.DB Unique Index 설정:
site_name + article_id)업데이트 여부 확인 (Content Hash):
GUID는 같은데 ContentHash가 달라졌다면, 해당 게시물이 수정된 것으로 판단하고 DB를 Update합니다.삭제된 게시물 처리:
어떤 사이트를 크롤링할 계획이신가요? URL 구조를 예시로 보여주시면 더 구체적인 추출 방법을 알려드릴 수 있습니다.
3번 방법(제목, 작성자, 날짜 등을 조합한 해시값)에서 생성되는 해시값의 크기는 사용하는 해시 알고리즘에 따라 달라집니다.
가장 흔히 사용되는 3가지 알고리즘의 결과값 크기는 다음과 같습니다.
해시값을 DB에 저장할 때 어떤 타입을 쓰느냐에 따라 효율이 달라집니다.
CHAR(32), SHA-256을 쓰면 CHAR(64)로 고정 길이를 지정하면 됩니다.HEX() 함수 등을 써야 하는 번거로움이 있습니다.구현 예시 (Python):
import hashlib
data = "제목입니다" + "작성자" + "2023-10-27"
# MD5 해시 생성
hash_value = hashlib.md5(data.encode()).hexdigest()
print(len(hash_value)) # 결과: 32
네, 단 한 글자, 혹은 공백 하나만 바뀌어도 해시값은 완전히 달라집니다.
이것을 암호학 용어로 **'눈사태 효과(Avalanche Effect)'**라고 부릅니다. 입력값의 아주 작은 변화가 결과값에 눈사태처럼 큰 변화를 일으킨다는 뜻입니다.
아래 두 문장은 마침표 하나 차이지만, 결과값은 전혀 다릅니다.
안녕하세요
7476839d375d3368294e50339d67566d안녕하세요. (마침표 추가)
49e91763784114757046896173041935이 특성 때문에 크롤링 프로그램을 만들 때 데이터 전처리(Cleaning) 과정 없이 해시를 만들면, 실제로는 같은 글인데도 "중복이 아니다"라고 판단하는 오류가 자주 발생합니다.
다음과 같은 상황을 주의해야 합니다.
1. 불필요한 공백 및 줄바꿈
\n(줄바꿈)이나 공백이 붙을 수 있습니다.strip() 함수로 앞뒤 공백을 제거해야 합니다.2. 대소문자 구분
lower())나 대문자(upper())로 통일한 뒤 해싱합니다.3. HTML 태그 포함 여부
<p> 태그가 포함된 본문을 긁고, 어떤 때는 텍스트만 긁으면 해시값이 달라집니다.4. 동적인 요소 (조회수, 추천수, 현재 시간)
해시는 **"조금이라도 다르면 무조건 다르다"**고 판단하는 매우 엄격한 기준입니다. 따라서 중복 체크용으로 쓰실 때는 비교 대상 문자열을 최대한 깨끗하게 다듬은(Normalization) 후에 해시를 생성하는 것이 핵심입니다.
네, 그렇습니다. 입력되는 문자열의 길이에 관계없이, 선택한 해시 알고리즘이 출력하는 결과값의 길이는 항상 일정합니다.
이것이 해시 함수(Hash Function)의 가장 중요한 특징 중 하나입니다.
해시 알고리즘은 아주 긴 데이터라도 고정된 크기의 '디지털 지문'으로 압축하도록 설계되었기 때문입니다.
어떤 내용을 넣든 결과는 해당 알고리즘의 약속된 길이를 유지합니다.
| 알고리즘 | 입력 예시 1 ("Hi") | 입력 예시 2 (매우 긴 본문) | 결과 길이(고정) |
|---|---|---|---|
| MD5 | c1a52... (32자) |
8b1a2... (32자) |
32자 |
| SHA-1 | 16fa3... (40자) |
456ef... (40자) |
40자 |
| SHA-256 | 3635a... (64자) |
2498b... (64자) |
64자 |
CHAR(32)나 CHAR(64)처럼 고정 길이 타입으로 설정할 수 있어 저장 효율이 좋습니다.길이가 일정하다는 말은, 무한한 종류의 입력값을 유한한 길이의 결과값으로 대응시킨다는 뜻입니다. 아주 희박한 확률이지만, 서로 다른 내용이 우연히 같은 해시값을 가질 수도 있는데 이를 '충돌'이라고 합니다.
요약하자면: 게시물 본문이 10글자이든 10만 글자이든, DB에 저장되는 식별자(해시값)의 길이는 항상 똑같기 때문에 관리가 매우 편리해집니다.
네, 가능합니다. 다만, 이미지의 **'같음'**을 정의하는 기준에 따라 크게 두 가지 방법으로 나뉩니다.
파일의 바이너리(데이터) 자체가 1비트도 틀리지 않고 일치하는지 확인하는 방법입니다.
사람의 눈으로 봤을 때 "아, 이거 같은 사진이네"라고 느껴지는 이미지를 찾아내는 방법입니다. 크롤링이나 이미지 검색 엔진에서 주로 사용합니다.
| 구분 | 파일 해시 (MD5, SHA-256) | 지각 해시 (pHash, dHash) |
|---|---|---|
| 판단 기준 | 데이터(Byte)의 일치 여부 | 시각적 특징의 유사성 |
| 변화에 대한 내성 | 전혀 없음 (조금만 변해도 다름) | 매우 강함 (리사이징, 포맷 변경 허용) |
| 속도 | 매우 빠름 | 비교적 빠름 |
| 주요 알고리즘 | MD5, SHA-1, SHA-256 | aHash, pHash, dHash |
| 라이브러리(Python) | hashlib |
ImageHash |
만약 웹사이트에서 이미지를 크롤링하여 DB에 저장한다면 다음 전략을 권장합니다.
src)가 같으면 같은 이미지로 간주 (가장 빠름).pHash 값을 저장하여 유사도를 비교합니다.Python 예시 코드 (지각 해시):
from PIL import Image
import imagehash
hash1 = imagehash.phash(Image.open('image1.jpg'))
hash2 = imagehash.phash(Image.open('image2_resized.jpg'))
if hash1 - hash2 < 5: # 차이가 5 미만이면 같은 이미지로 간주
print("눈으로 보기에 같은 이미지입니다.")
어떤 목적으로 이미지 비교가 필요하신가요? 구체적인 상황을 알려주시면 더 적합한 알고리즘을 추천해 드릴 수 있습니다.
네, 가능합니다. 이 작업은 **"HTML 선택적 파싱(Selective Sanitization)"**이라고 불립니다. 모든 태그를 없애는 것이 아니라, 내가 허용한 특정 태그(<a>, <img>)만 남기고 나머지는 텍스트로 변환하는 과정이 필요합니다.
가장 대중적인 Python의 BeautifulSoup 라이브러리를 기준으로 구현 방법과 주의사항을 설명해 드릴게요.
/img/logo.png)로 되어 있으면 내 사이트에서 보이지 않습니다. 이를 절대 경로(예: https://original.com/img/logo.png)로 바꿔야 합니다.<div>, <p>, <span> 등은 제거하되, 그 안의 **내용(텍스트)**은 보존합니다.<img>, <a> 태그는 속성(src, href)과 함께 그대로 둡니다.from bs4 import BeautifulSoup
from urllib.parse import urljoin
def clean_html_keep_media(raw_html, base_url):
soup = BeautifulSoup(raw_html, 'html.parser')
# 1. 링크와 이미지 주소를 절대 경로로 변환 (원본 사이트를 가리키도록)
for a in soup.find_all('a', href=True):
a['href'] = urljoin(base_url, a['href'])
a['target'] = '_blank' # 새창에서 열기 옵션 추가(선택)
for img in soup.find_all('img', src=True):
img['src'] = urljoin(base_url, img['src'])
# 2. 허용할 태그 리스트
allowed_tags = ['a', 'img', 'br'] # br은 줄바꿈을 위해 포함 가능
# 3. 모든 태그를 순회하며 허용되지 않은 태그는 텍스트만 남기고 제거
for tag in soup.find_all(True): # 모든 태그 찾기
if tag.name not in allowed_tags:
# 태그 자체는 없애고 그 안의 내용(text)은 유지함 (unwrap)
tag.unwrap()
return str(soup)
# 실행 예시
html_content = """
<div class="article">
<p>안녕하세요. <b>반갑습니다.</b></p>
<a href="/post/123">상세보기</a>
<img src="/files/photo.jpg" alt="사진">
<span>이 문구는 태그가 사라집니다.</span>
</div>
"""
origin_url = "https://example.com"
result = clean_html_keep_media(html_content, origin_url)
print(result)
변환 결과:
안녕하세요. 반갑습니다.
<a href="https://example.com/post/123" target="_blank">상세보기</a>
<img alt="사진" src="https://example.com/files/photo.jpg"/>
이 문구는 태그가 사라집니다.
이미지를 다운로드하지 않고 원본 주소를 그대로 사용할 때, 원본 사이트에서 '핫링크 방지(Hotlink Protection)' 설정을 해두었다면 이미지가 엑박으로 뜰 수 있습니다.
Referer 헤더를 체크하여 자기 사이트가 아닌 곳에서 이미지를 요청하면 차단함.<meta name="referrer" content="no-referrer">를 추가하면 해결되는 경우가 많습니다.원본 사이트의 <a> 태그에 href="javascript:alert('attack')" 같은 코드가 숨겨져 있을 수 있습니다.
bleach 같은 화이트리스트 기반의 HTML 세정(Sanitize) 라이브러리를 사용하는 것이 보안상 가장 안전합니다.태그만 남고 CSS 스타일은 모두 사라지기 때문에, 이미지가 화면보다 너무 크게 나오거나 글자와 엉망으로 섞일 수 있습니다.
.content-area img { max-width: 100%; height: auto; } /* 이미지가 화면을 안 벗어나게 */
.content-area a { color: blue; text-decoration: underline; } /* 링크 표시 */
이 방법을 사용하면 텍스트 위주의 깔끔한 본문에 필요한 미디어만 포함된 형태로 데이터를 재구성할 수 있습니다. 바로 적용해 보시겠어요?
웹사이트의 크롤링 차단 시스템은 갈수록 정교해지고 있습니다. 이를 우회하여 차단을 최소화하기 위한 방법들을 입문부터 전문가 수준까지 단계별로 정리해 드립니다.
가장 기본은 서버가 나를 "파이썬 스크립트"가 아닌 **"실제 사람의 브라우저"**로 인식하게 만드는 것입니다.
python-requests/2.x)을 쓰면 즉시 차단됩니다. 실제 브라우저의 정보를 복사해서 넣으세요.fake-useragent 라이브러리: 매번 다른 브라우저 정보를 무작위로 생성해 줍니다.from fake_useragent import UserAgent
ua = UserAgent()
headers = {'User-Agent': ua.random}
사람은 1초에 100페이지를 읽을 수 없습니다. 기계적인 패턴을 깨야 합니다.
import time
import random
time.sleep(random.uniform(1.0, 5.0)) # 1~5초 사이 무작위 대기
단순한 HTTP 요청은 클라우드플레어(Cloudflare) 같은 방어 솔루션에 쉽게 걸립니다. 이를 우회하는 전용 도구들이 있습니다.
undetected-chromedriver: 셀레니움(Selenium) 사용 시 브라우저가 "나는 자동화 도구입니다"라고 보내는 신호(webdriver flag)를 지워줍니다.playwright + stealth: 최신 브라우저 자동화 도구인 Playwright에 stealth 플러그인을 조합하면 탐지율이 매우 낮아집니다.cloudscraper: 클라우드플레어의 "잠시만 기다려주세요(Waiting Room)" 페이지를 자동으로 통과해 주는 라이브러리입니다.curl-impersonate: HTTP 요청의 암호화(TLS) 지문까지 실제 브라우저와 똑같이 흉내 냅니다. 최근 보안 솔루션은 헤더뿐만 아니라 이 지문까지 체크하므로 매우 유용합니다.가장 강력한 차단은 IP 주소 차단입니다.
서버가 자바스크립트 실행 여부를 확인하여 봇을 가려낼 때 사용하는 방법입니다.
Playwright나 Puppeteer를 사용하여 실제로 브라우저를 띄우고(창은 안 보임), 자바스크립트를 모두 실행한 뒤 데이터를 가져옵니다.상황에 따라 아래 조합을 추천합니다.
requests + fake-useragent + random sleepPlaywright (Stealth 모드) + Randomize viewport/timezoneundetected-chromedriver 혹은 Playwrightexample.com/robots.txt를 확인하여 크롤링 허용 범위를 먼저 보세요.가장 먼저 막히는 부분이 **"동일 IP에서의 과도한 요청"**이므로, 속도 조절과 IP 변환 두 가지만 잘 관리해도 차단의 80%는 피할 수 있습니다.
네, 기술적으로 충분히 가능하며 매우 활발하게 이루어지는 분야입니다. 이를 보통 "자동 포스팅 시스템" 또는 "뉴스 애그리게이터(News Aggregator)" 구축이라고 부릅니다.
이 프로세스를 완성하기 위한 전체적인 아키텍처와 단계별 핵심 기술을 정리해 드립니다.
블로그 플랫폼에 따라 난이도가 다릅니다.
python-wordpress-xmlrpc 라이브러리나 내장 requests 모듈로 쉽게 구현 가능합니다.Selenium이나 Playwright 같은 브라우저 자동화 도구를 사용하여 실제로 브라우저를 띄워 글을 쓰는 동작을 코드로 구현해야 합니다. (차단 위험이 높습니다.)이미지를 다운로드만 한다고 끝나는 것이 아니라, 블로그 서버에 다시 올려야 함을 잊지 마세요.
requests.get(img_url).content로 이미지를 받아 임시 폴더에 저장합니다.<img src="https://othersite.com/a.jpg"><img src="https://myblog.com/files/123.jpg">자동 포스팅 시스템을 만들 때 기술보다 더 중요한 것이 운영 정책입니다.
Playwright를 사용하여 데이터 수집.OpenAI API (GPT)를 사용하여 수집한 텍스트를 나만의 말투로 재작성.Pillow로 이미지 하단에 출처 워터마크 삽입 후 블로그에 업로드.WordPress REST API를 사용하여 자동 게시.GitHub Actions나 Linux Cron을 사용하여 매일 정해진 시간에 작동.결론적으로 가능합니다. 다만, 단순 복사보다는 수집 -> AI 요약/가공 -> 자동 포스팅의 흐름으로 만드시는 것이 블로그를 오래 유지할 수 있는 비결입니다. 어떤 플랫폼에 게시하고 싶으신가요? 플랫폼을 정해주시면 구체적인 API 사용법을 안내해 드릴 수 있습니다.
웹 크롤링 생태계에서 가장 강력하고 인기 있는 도구들을 언어별, 목적별로 정리해 드립니다. 결론부터 말씀드리면 가장 추천하는 언어는 Python입니다.
크롤링 입문부터 대규모 수집까지 가장 많이 쓰입니다. 데이터 분석 및 AI(GPT 등) 라이브러리와 연동하기 매우 좋습니다.
웹 프론트엔드 기술에 익숙하거나, 실시간 처리가 중요할 때 유리합니다.
속도가 최우선이고 서버 자원을 적게 써야 할 때 선택합니다.
이미 만들어진 강력한 오픈소스 도구들을 활용하면 바닥부터 코딩할 필요가 없습니다.
개인적인 추천: 입문자라면 Python으로 시작하세요. 커뮤니티가 가장 커서 막히는 부분을 구글링이나 ChatGPT로 해결하기가 가장 쉽습니다. 특히 Playwright는 크롤링 차단을 피하는 기능이 강력해서 실무에서 쓰기 아주 좋습니다.