Python 기반 동기 웹 크롤링: XPath를 활용한 데이터 추출

동기 로딩 페이지의 특징

브라우저에서 렌더링된 콘텐츠와 서버 응답 본문, 또는 우클릭으로 확인하는 소스 코드가 완전히 일치합니다. 이는 클라이언트 측 스크립트 없이 정적 콘텐츠로 구성된 페이지임을 의미합니다.

기본 요청 구조

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)

자동화 스크립트 설계

다음 단계로 전개 가능한 구조적 접근:

  1. 책 목록 수집 → 책별 링크 확보
  2. 각 책의 장 목록 추출 → 장별 링크 수집
  3. 각 장의 본문 내용 추출 → 텍스트 저장
  4. 파일 시스템에 저장
  5. 모듈화 및 반복 실행 가능하도록 함수화

핵심 함수 패키지

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()

태그: python requests lxml XPath Web Scraping

6월 10일 17:56에 게시됨