목록 페이지에서 URL 목록 추출하기
import requests
import lxml.html
response = requests.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
root = lxml.html.fromstring(response.content)
for a in root.cssselect('.view_box a'):
url = a.get('href')
print(url)
코드 설명
- 코드에서 `root.cssselect('.view_box a')`는 HTML 문서에서 특정 요소들을 선택하기 위해 CSS 선택자를 사용하는 부분입니다. 여기서 `.view_box a`는 다음과 같은 의미를 가집니다:
- 1. `.view_box`: 클래스 이름이 `view_box`인 요소를 선택합니다. 클래스 선택자는 점(`.`)으로 시작하며, 해당 클래스가 적용된 모든 요소를 대상으로 합니다.
- 2. `a`: `view_box` 클래스 내부에 있는 `<a>` 태그(링크 요소)를 선택합니다. 이는 후손 선택자로, `view_box` 클래스 안에 포함된 모든 `<a>` 태그를 찾습니다.
- 이유
- 특정 링크 요소를 타겟팅: `view_box`라는 특정 클래스 내의 `<a>` 태그만을 대상으로 작업하기 위해 사용됩니다. 예를 들어, 페이지 내 다른 위치에 있는 `<a>` 태그는 제외됩니다.
- HTML 구조 탐색: 웹 페이지에서 새 책 목록과 관련된 링크들이 `.view_box`라는 클래스를 가진 컨테이너 안에 있을 가능성이 높습니다. 이 구조에 맞춰 데이터를 효율적으로 추출하려는 의도입니다.
- CSS 선택자의 활용: CSS 선택자는 HTML 문서에서 원하는 요소를 정확히 지정할 수 있는 강력한 도구로, 이 경우에도 필요한 링크만 추출하기 위해 사용됩니다.
- 동작 원리
- 1. `requests.get()`으로 해당 URL의 HTML 콘텐츠를 가져옵니다.
- 2. `lxml.html.fromstring()`을 통해 HTML 콘텐츠를 파싱하여 DOM 트리로 변환합니다.
- 3. `.cssselect('.view_box a')`로 DOM 트리에서 `.view_box` 클래스 내부의 모든 `<a>` 태그를 선택합니다.
- 4. 반복문을 통해 각 `<a>` 태그의 `href` 속성을 가져와 출력합니다.
절대 URL 변환
- 코드 결과를 보니 'javascript:'라는 부분이 있습니다. 개발자 도구를 확인해보니, '.view_box .book_tit a'로 찾으면 이를 제외 가능함을 알 수 있습니다.
- 또한 현재 결과는 모두 상대 URL입니다. 따라서 이를 절대 URL로 변환해야 합니다. make_links_absolute() 메서드를 활용하여 문서 내부의 모든 링크의 href 속성을 절대 URL로 변환해 줍니다.
import requests
import lxml.html
response = requests.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
root = lxml.html.fromstring(response.content)
# 모든 링크를 절대 URL로 변환합니다.
root.make_links_absolute(response.url)
# 선택자를 추가해서 명확한 선택을 할 수 있게 합니다.
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
print(url)
함수를 사용하여 리팩토링
- main() 함수에서 scrape_list_page() 함수를 호출하는 형태로 변환
- scrape_list_page() 함수의 반환값은 list처럼 반복 가능한 제너레이터
import requests
import lxml.html
def main():
"""
크롤러의 메인 처리
"""
# 여러 페이지에서 크롤링할 것이므로 Session을 사용합니다.
session = requests.Session()
# scrape_list_page() 함수를 호출해서 제너레이터를 추출합니다.
response = session.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
urls = scrape_list_page(response)
# 제너레이터는 list처럼 사용할 수 있습니다.
for url in urls:
print(url)
def scrape_list_page(response):
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
# yield 구문으로 제너레이터의 요소 반환
yield url
if __name__ == '__main__':
main()
import requests
import lxml.html
def main():
"""
크롤러의 메인 처리
"""
# 여러 페이지에서 크롤링할 것이므로 Session을 사용합니다.
session = requests.Session()
# scrape_list_page() 함수를 호출해서 제너레이터를 추출합니다.
response = session.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
urls = scrape_list_page(response)
# 제너레이터는 list처럼 사용할 수 있습니다.
for url in urls:
print(url)
def scrape_list_page(response):
"""
책 제목을 추출하는 함수
"""
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselect('.view_box .book_tit a'):
title = a.text_content().strip()
# yield 구문으로 제너레이터의 요소 반환
yield title
if __name__ == '__main__':
main()
상세 페이지에서 스크레이핑하기
추출하고 싶은 요소 | CSS 선택자 |
타이틀 | .store_product_info_box h3 |
가격 | .pbr strong |
목차 | #tabs_3 .haabit_edit_view 내부의 p 태그들 |
import requests
import lxml.html
def main():
# 여러 페이지에서 크롤링할 것이므로 Session을 사용합니다.
session = requests.Session()
response = session.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
urls = scrape_list_page(response)
for url in urls:
response = session.get(url) # Session을 사용해 상세 페이지를 추출합니다.
ebook = scrape_detail_page(response) # 상세 페이지에서 상세 정보를 추출합니다.
print(ebook) # 책 관련 정보를 출력합니다.
break # 책 한 권이 제대로 되는지 확인하고 종료합니다.
def scrape_list_page(response):
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
yield url
def scrape_detail_page(response):
"""
상세 페이지의 Response에서 책 정보를 dict로 추출합니다.
"""
root = lxml.html.fromstring(response.content)
ebook = {
'url': response.url,
'title': root.cssselect('.store_product_info_box h3')[0].text_content(),
'price': root.cssselect('.pbr strong')[0].text_content(),
'content': [p.text_content()\
for p in root.cssselect('#tabs_3 .hanbit_edit_view p')]
}
return ebook
if __name__ == '__main__':
main()
- 1. `#tabs_3`:
- ID가 `tabs_3`인 요소를 선택합니다. ID 선택자는 `#`로 시작하며, HTML 문서에서 고유한 요소를 타겟팅합니다.
- 이 경우, `tabs_3`는 책 상세 페이지에서 책의 상세 설명을 담고 있는 탭이나 섹션일 가능성이 큽니다.
- 2. `.hanbit_edit_view`:
- 클래스 이름이 `hanbit_edit_view`인 요소를 선택합니다. 클래스 선택자는 점(`.`)으로 시작하며, 해당 클래스가 적용된 모든 요소를 대상으로 합니다.
- 이 클래스는 상세 설명이 포함된 특정 컨테이너를 나타낼 수 있습니다.
- 3. `p`:
- `hanbit_edit_view` 클래스 내부에 있는 모든 `<p>` 태그(단락)를 선택합니다.
- `<p>` 태그는 일반적으로 텍스트 내용을 담는 데 사용되며, 책의 상세 설명이 여러 단락으로 나뉘어 있을 경우 이를 모두 가져오기 위해 사용됩니다.
{'url': 'https://www.hanbit.co.kr/store/books/look.php?p_code=B7597652225', 'title': 'Vue 3와 타입스크립트로 배우는 프런트엔드 개발', 'price': '32,400', 'content': ['\r\n ', '도입 편', '\xa0', '1장 프런트엔드 개발 흐름과 Vue_1.1 자바스크립트의 변천과 프런트엔드 개발의 등장\xa0_1.2 프런트엔드 프레임워크와 Vue', '\xa0', '2장 Vi
상세 페이지 크롤링하기(1부터 10페이지까지)
import time # time 모듈을 임포트합니다.
import re
import requests
import lxml.html
from bs4 import BeautifulSoup
def main():
session = requests.Session()
base_url = "https://www.hanbit.co.kr/store/books/new_book_list.html?page="
max_pages = 10
for page_num in range(1, max_pages+1):
url = f'{base_url}{page_num}'
response = session.get(url)
urls = scrape_list_page(response)
for url in urls:
time.sleep(1) # 1초 동안 휴식합니다.
response = session.get(url)
ebook = scrape_detail_page(response)
print(ebook)
def scrape_list_page(response):
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
yield url
def scrape_detail_page(response):
"""
상세 페이지의 Response에서 책 정보를 dict로 추출합니다.
"""
root = lxml.html.fromstring(response.content)
ebook = {
'url': response.url,
'title': root.cssselect('.store_product_info_box h3')[0].text_content(),
'price': root.cssselect('.pbr strong')[0].text_content(),
'content': [normalize_spaces(p.text_content())
for p in root.cssselect('#tabs_3 .hanbit_edit_view p')
if normalize_spaces(p.text_content()) != '']
}
return ebook
def normalize_spaces(s):
"""
연결돼 있는 공백을 하나의 공백으로 변경합니다.
"""
return re.sub(r'\s+', ' ', s).strip()
if __name__ == '__main__':
main()
스크레이핑 데이터 저장하기_to MySQL
import time
import re
import requests
import lxml.html
import pymysql
def main():
"""
크롤러의 메인 처리
"""
# MySQL 데이터베이스 연결 설정
db_config = {
"host": "localhost", # MySQL 서버 주소
"user": "root", # MySQL 사용자 이름
"password": "sqlstudent", # MySQL 비밀번호
"database": "scraping", # 사용할 데이터베이스 이름
"charset": "utf8mb4" # 문자 인코딩 설정
}
# MySQL 연결 생성
conn = pymysql.connect(**db_config)
cursor = conn.cursor()
# 테이블 생성 (필요시)
create_table_query = """
CREATE TABLE IF NOT EXISTS ebooks (
id INT AUTO_INCREMENT PRIMARY KEY,
url VARCHAR(255) NOT NULL,
`key` VARCHAR(50) NOT NULL UNIQUE,
title VARCHAR(255) NOT NULL,
price VARCHAR(50) NOT NULL,
content TEXT
)
"""
cursor.execute(create_table_query)
conn.commit()
# 페이지네이션 처리: 1페이지부터 10페이지까지 크롤링
session = requests.Session()
base_url = "https://www.hanbit.co.kr/store/books/new_book_list.html?page="
max_pages = 3
for page_num in range(1, max_pages + 1):
url = f"{base_url}{page_num}"
response = session.get(url)
urls = scrape_list_page(response)
for url in urls:
# URL로 키를 추출합니다.
key = extract_key(url)
# MySQL에서 key에 해당하는 데이터가 있는지 확인합니다.
cursor.execute("SELECT * FROM ebooks WHERE `key` = %s", (key,))
ebook = cursor.fetchone()
# MySQL에 존재하지 않는 경우만 상세 페이지를 크롤링합니다.
if not ebook:
time.sleep(1) # 1초 동안 휴식합니다.
response = session.get(url)
ebook_data = scrape_detail_page(response)
# 책 정보를 MySQL에 저장합니다.
insert_query = """
INSERT INTO ebooks (url, `key`, title, price, content)
VALUES (%s, %s, %s, %s, %s)
"""
cursor.execute(insert_query, (
ebook_data['url'],
ebook_data['key'],
ebook_data['title'],
ebook_data['price'],
ebook_data['content']
))
conn.commit()
# 책 정보를 출력합니다.
print(ebook_data)
# 연결 종료
cursor.close()
conn.close()
def scrape_list_page(response):
"""
목록 페이지의 Response에서 상세 페이지의 URL을 추출합니다.
"""
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
yield url
def scrape_detail_page(response):
"""
상세 페이지의 Response에서 책 정보를 dict로 추출합니다.
"""
root = lxml.html.fromstring(response.content)
ebook = {
'url': response.url,
'key': extract_key(response.url),
'title': root.cssselect('.store_product_info_box h3')[0].text_content(),
'price': root.cssselect('.pbr strong')[0].text_content(),
'content': normalize_spaces(" ".join(
[p.text_content() for p in root.cssselect('#tabs_3 .hanbit_edit_view p') if normalize_spaces(p.text_content()) != '']
))
}
return ebook
def extract_key(url):
"""
URL에서 키(URL 끝의 p_code)를 추출합니다.
"""
m = re.search(r"p_code=(.+)", url)
return m.group(1)
def normalize_spaces(s):
"""
연결돼 있는 공백을 하나의 공백으로 변경합니다.
"""
return re.sub(r'\s+', ' ', s).strip()
if __name__ == '__main__':
main()
'[업무 지식] > Crawling' 카테고리의 다른 글
[CacheControl] 변경된 데이터만 추출하기 (0) | 2025.01.01 |
---|---|
[HTTP 통신 오류] HTTP 상태 코드 & 파이썬에서 오류 처리하기 (0) | 2025.01.01 |
[MySQL 접속] 파이썬에서 MySQL에 저장하기 (0) | 2025.01.01 |
[RSS 스크레이핑] feedparser로 RSS 스크레이핑하기 (0) | 2024.12.31 |
[RSS 스크레이핑] feedparser 이해하기 (0) | 2024.12.31 |