동기 로딩 페이지의 특징
브라우저에서 렌더링된 콘텐츠와 서버 응답 본문, 또는 우클릭으로 확인하는 소스 코드가 완전히 일치합니다. 이는 클라이언트 측 스크립트 없이 정적 콘텐츠로 구성된 페이지임을 의미합니다.
기본 요청 구조
import requests
from lxml import etree
target_url = 'https://www.shu.com/bookmark/sidamingzhu.html'
request_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}
response = requests.get(target_url, headers=request_headers)
page_data = response.content.decode('utf-8')
html_tree = etree.HTML(page_data)
# 테이블 내 모든 행 추출
rows = html_tree.xpath('//table[@border="1"]/tbody/tr')
for row in rows:
cells = row.xpath('./td/text()')
print(cells)
실제 적용 사례
1. 대표 고전 4권의 제목과 링크 수집
import requests
from lxml import etree
url = 'https://www.shu.com/bookmark/sidamingzhu.html'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
response = requests.get(url, headers=headers)
tree = etree.HTML(response.content.decode('utf-8'))
book_elements = tree.xpath('//div[@class="book-item"]/h3/a')
book_collection = {}
for elem in book_elements:
title = elem.xpath('./text()')[0]
link = 'https://www.shu.com' + elem.xpath('./@href')[0]
book_collection[title] = link
print(book_collection)
2. 한 권의 책 전체 장 목록 추출
url = 'https://www.shu.com/book/sanguoyanyi.html'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36'}
response = requests.get(url, headers=headers)
tree = etree.HTML(response.content.decode('utf-8'))
chapters = tree.xpath('//div[@class="book-mulu"]/ul/li/a')
chapter_map = {}
for item in chapters:
chapter_name = item.xpath('./text()')[0]
chapter_link = 'https://www.shu.com' + item.xpath('./@href')[0]
chapter_map[chapter_name] = chapter_link
print(chapter_map)
3. 특정 장의 본문 내용 다운로드
url = 'https://www.shu.com/book/sanguoyanyi/1.html'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36'}
response = requests.get(url, headers=headers)
tree = etree.HTML(response.content.decode('utf-8'))
title = tree.xpath('//div[@class="card bookmark-list"]/h1/text()')[0]
content_parts = tree.xpath('//div[@class="card bookmark-list"]/div/p/text()')
full_content = ''.join(part.strip() + '\n' for part in content_parts)
print(title)
print(full_content)
자동화 스크립트 설계
다음 단계로 전개 가능한 구조적 접근:
- 책 목록 수집 → 책별 링크 확보
- 각 책의 장 목록 추출 → 장별 링크 수집
- 각 장의 본문 내용 추출 → 텍스트 저장
- 파일 시스템에 저장
- 모듈화 및 반복 실행 가능하도록 함수화
핵심 함수 패키지
HTML 파싱 함수
def fetch_html_content(url):
response = requests.get(url, headers=headers)
return etree.HTML(response.content.decode('utf-8'))
책 정보 추출 함수
def extract_book_info(html_tree):
books = {}
elements = html_tree.xpath('//div[@class="book-item"]/h3/a')
for elem in elements:
title = elem.xpath('./text()')[0]
link = 'https://www.shu.com' + elem.xpath('./@href')[0]
books[title] = link
return books
장 목록 추출 함수
def extract_chapter_list(html_tree):
chapters = {}
items = html_tree.xpath('//div[@class="book-mulu"]/ul/li/a')
for item in items:
name = item.xpath('./text()')[0]
url = 'https://www.shu.com' + item.xpath('./@href')[0]
chapters[name] = url
return chapters
장 내용 추출 함수
def extract_chapter_content(html_tree):
title = html_tree.xpath('//div[@class="card bookmark-list"]/h1/text()')[0]
paragraphs = html_tree.xpath('//div[@class="card bookmark-list"]/div/p/text()')
content = ''.join(p.strip() + '\n' for p in paragraphs)
return {title: content}
파일 저장 함수
def save_chapter_to_file(book_title, chapter_data):
if not os.path.exists(book_title):
os.makedirs(book_title)
for chap_name, chap_text in chapter_data.items():
file_path = os.path.join(book_title, f"{chap_name}.txt")
with open(file_path, 'w', encoding='utf-8') as f:
f.write(f"{chap_name}\n\n{chap_text}")
메인 실행 로직
def main():
start_url = 'https://www.shu.com/bookmark/sidamingzhu.html'
root_tree = fetch_html_content(start_url)
book_list = extract_book_info(root_tree)
for book_name, book_url in book_list.items():
print(f"처리 중: {book_name}")
chapter_tree = fetch_html_content(book_url)
chapter_list = extract_chapter_list(chapter_tree)
for title, detail_url in chapter_list.items():
content_tree = fetch_html_content(detail_url)
content = extract_chapter_content(content_tree)
save_chapter_to_file(book_name, content)
print(f"→ {title} 저장 완료")
time.sleep(random.uniform(1, 3))
if __name__ == '__main__':
main()