INDEX
- 개요
- 웹 크롤링(Web Crawling)과 웹 스크래핑(Web Scraping) 개념 정리
- 웹 스크래핑 프로세스의 이해
- Robots.txt 윤리에 대해
- JAVA 웹 스크래핑 프레임워크와 라이브러리
- Python 웹 스크래핑 프레임워크와 라이브러리
- 웹 스크래핑 시, 해야 할 일과 하지 말아야 할 일
개요
웹 스크래핑(Web Scraping)은 웹 페이지로부터 원하는 정보를 추출하는 기법입니다.
어떤 서비스에서 API가 별도로 제공되고 있지 않지만 웹 페이지로는 정보가 제공되고 있을 때, 웹 스크래핑 기법을 이용하면 원하는 정보를 획득할 수 있습니다.
웹 크롤링(Web Crawling)과 웹 스크래핑(Web Scraping) 개념 정리
웹 크롤링(Web Crawling)과 웹 스크래핑(Web Scraping) 개념 정리
기술을 알아보기에 앞서 크롤링과 스크래핑 모두 웹을 탐색하여 자료를 수집한다는 점에서 혼용하기 쉬운 개념이라 의미와 원리를 간단하게 정리해보았습니다.
웹 크롤링 (Web crawling)
스파이더 또는 스파이더 봇(Spider Bot)이라고도 불리는 웹 크롤러(Web Crawler)는 World Wide Web 을 체계적으로 탐색하고 일반적으로 웹 인덱싱 (Web Spidering) 을 목적으로 검색 엔진에서 작동하는 인터넷 봇입니다.
인터넷을 돌아다니며 여러 웹 사이트에 접속하여, 페이지의 내용과 링크의 복사본을 생성하여 다운로드하고 요약본을 생성, 검색 시 유용한 정보만을 노출하도록 검색 색인을 붙이는 작업을 말합니다.
간단한 예시로는 검색 포털이 있습니다.
특정 웹페이지를 목표로 하지 않으며, 가장 큰 특징은 탐색 후에 정보를 추출하는 선탐색 후추출이다.
중복된 데이터를 가져와서 작업을 복잡하게 만들 수 있으므로 중복 제거가 필수이다.
차이점 : 웹 상의 방대한 양의 정보를 수집하기 때문에, 특정 키워드에 대한 심층 분석이 필요할 때 유용함, 실시간 정보 수집을 위해 계속해서 작동하기 때문에 자주 변화하는 데이터를 파악하기에 좋다.
웹 스크래핑 (Web Scraping)
특정 웹 사이트나 페이지에서 필요한 데이터를 자동으로 추출하는 것을 의미한다.
보통 봇 또는 웹 크롤러 를 사용하여 구현된 자동화된 프로세스를 뜻한다.
사이트가 응답한 경우에 스크래퍼는 HTML문서를 분석하여 특정 패턴을 지닌 데이터를 추출하여추출된 데이터를 데이터베이스에 저장한다.
데이터를 추출할 타겟이 정해져있기 때문에 선결정 후추출이다.
중복된 데이터라 할지라도 고유하게 사용될 수 있으므로 중복 제거가 필수가 아니다.
차이점 : 특정 사이트나 페이지에 대한 정보를 찾는데 집중하므로 포인트를 정확히 잡고 확실한 정보만을 수집할 수 있다는 점에서 유용, 서비스 대역폭과 비용 절약 부분에서 장점이다.
용어를 혼용해서 사용하는 경우가 많지만 크게 두 가지 개념으로 나뉜다는 것은 이해하고 사용하도록 하자.
웹 스크래핑 프로세스 이해
출처: https://mobigesture.com/reliable-webscraping-robot.html
웹 스크래핑은 크게 3가지 파트로 구분된다.
데이터 수집 : 특정 사이트에서 원하는 데이터를 파싱(Parsing)하여 수집하는 단계
데이터 분석 : 저장소에 저장하기 전, 원하는 데이터가 맞는지 유효성을 검증하는 단계
저장소 : 파싱하고 분석된 데이터들을 스토리지나 DB에 저장하는 단계
웹 스크래핑을 위한, 웹문서의 종류
- HTML (HyperText Markup Language)
팀 버너스리가 개발한 마크업 요소(tag)와 속성등을 이용하여 웹 페이지를 쉽게 작성할 수 있도록 하는 마크업 언어입니다. - XML(Extensible Markup Language)
XML은 서로 다른 유형의 데이터를 기술하는 마크업 언어 다른 종류의 시스템간 (특히 인터넷에 연결된 시스템)끼리 데이터를 쉽게 주고 받을 수 있도록 고안되었습니다.
HTML의 한계에 대한 대안문서 객체 모델(DOM; Document Object Model): XML이나 HTML 문서에 접근하기 위한 API로 W3C 표준 권고안으로 문서 내의 모든 요소를 정의하고, 해당 요소에 접근하는 방법까지 정의합니다. - DOM
문서 객체 모델(DOM, Document Object Model)은 XML이나 HTML 문서에 접근하기 위한 일종의 인터페이스입니다.
Robot.txt 윤리에 대해
모든 크롤링과 스크래핑은 각 서버의 정책이 허용하는 범위 내에서만 이루어져야 한다.
웹 페이지라고 해서 데이터를 무분별하게 수집해서는 안 되며 Robots.txt 파일은 각 서버에서 지켜야할 규약, 권고사항을 적는 일종의 교통표지판인 셈이다.
보통 도메인 최상위 루트에 작성되어있는 robots.txt (도메인/robots.txt) 작성된 룰에 따라 수집을 해도 되는 페이지가 있고 수집을 해선 안 되는 페이지가 있다.
이 규약은 권고안이며, 로봇이 robots.txt 파일을 읽고 접근을 중지하는 것을 목적으로 한다. 따라서, 접근 방지 설정을 하였다고 해도, 다른 사람들이 그 파일에 접근할 수 있다. robots.txt 파일은 항상 사이트의 루트 디렉토리에 위치해야 한다.
따라서 웹 크롤링과 스크래핑을 위해 라이브러리를 선택하거나 프로그램을 설계할 때 꼭 고려해봐야 할 사항이다.
JAVA 웹 스크래핑 프레임워크와 라이브러리
- Heritrix( https://webarchive.jira.com/wiki/spaces/Heritrix/overview/ ) : 확장성이 뛰어남 , robots.txt. 제외 지시문과 지시문과 메타 로봇 태그를 매우 존중 , 웹 기반 사용자 인터페이스를 제공, 주로 리눅스 환경에서 사용
- Web Harvest( https://web-harvest.sourceforge.net/index.php ) : 지정된 페이지에서 데이터 추출 , XSLT, XQuery, 정규 표현식과 같은 기술과 기술을 활용하여 HTML/XML 기반 웹 사이트의 콘텐츠를 작동하거나 필터링 제공 , 변수 저장 및 사용을 위한 변수 컨텍스트
- Apache Nutch ( https://nutch.apache.org/ ) : 모듈화된 아키텍처로 미디어 유형 구문 분석, 데이터 검색, 플러그인 제작 , 맞춤형 구현을 위한 인터페이스 제공 , robots.txt. 제외 지시문과 지시문과 메타 로봇 태그를 매우 존중
- Jsoup ( https://jsoup.org/ ) : CSS 선택자 완벽 지원, 내장 프록시 지원, 웹에서 대상 데이터를 추출하기 위해 HTML DOM 트리를 탐색하는 API를 제공
Jsoup 예시
@Slf4j
@Component
@RequiredArgsConstructor
public class WebScraper {
private final ObjectMapper objectMapper;
private final ResourceLoader resourceLoader;
private final String URL = "크롤링 대상 URL";
private final String domain = "도메인 URL";
private final String saveFileName = "/scraping/scraping.json";
private final String saveFolderPath = "/scraping";
private String userProjectPath = "/blah/blah";
/**
* 크롤링
* @return
*/
public List<ScraperDto> parseHTMLData() {
List<ScraperDto> scrapers = new ArrayList<>();
try {
// timeout을 설정하지 않으면 ReadTimeoutException이 발생할 수 있다.
Document doc = Jsoup.connect(URL).timeout(50000).get();
// class 명이 title이고, 그 아래에 있는 a 태그들을 elements에 담는다.
Elements elements = doc.select(".title > a");
for(Element element : elements) {
// JSON 객체로 변환할 DTO
ScraperDto scraper = new ScraperDto();
// a 태그의 href 속성에 있는 값을 가져온다.
String link = element.attr("href");
String title = element.text();
scraper.setLink(domain + link);
scraper.setTitle(title);
scrapers.add(borrowBestDto);
}
return convertScraperDto(scrapers);
} catch (IOException e) {
log.error(e.getMessage(), e);
return new ArrayList<>();
}
}
/**
* ObjectMapper로 Convert
* @param scarpers
* @return
*/
private List<ScraperDto> convertScraperDto(List<ScraperDto> scarpers) {
// List<ScraperDto>의 형태로 convert
List<ScraperDto> convertedList = objectMapper.convertValue(scarpers, new TypeReference<List<ScraperDto>>() {});
return convertedList;
}
/**
* JSON String 타입으로
* @param convertedList
* @return
*/
private String convertToString (List<ScraperDto> convertedList) {
try {
// Json String 타입으로 변환
return objectMapper.writeValueAsString(convertedList);
} catch (JsonProcessingException e) {
log.error("Convert Error");
throw new RuntimeException(e);
}
}
/**
* JSON 파일 생성
*/
public void createJsonToScraper() {
String jsonString = convertToString(parseHTMLData());
try {
File saveFolder = new File(userProjectPath+saveFolderPath);
File saveJsonFile = new File(userProjectPath, saveFileName);
if(!saveFolder.exists() || saveFolder.isFile()) {
if(!saveFolder.mkdirs()) {
throw new IOException("디렉터리 생성 실패");
}
}
org.apache.commons.io.FileUtils.writeStringToFile(saveJsonFile, jsonString, Charset.forName("UTF-8"));
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* JSON 파일 읽기
* @return
*/
public List readScraperJson() {
Resource resource = resourceLoader.getResource(saveFileName);
if (!resource.exists()) {
log.error("해당 json 파일이 존재하지 않습니다.");
}
try {
File file = resource.getFile();
String jsonString = FileUtils.readFileToString(file, Charset.forName("UTF-8"));
return objectMapper.readValue(jsonString, new TypeReference<List<ScraperDto>>() {});
} catch (IOException e) {
log.error("JSON 파일 읽는중 예외 발생", e.getMessage(), e);
return new ArrayList<>();
}
}
}
@Component
@RequiredArgsConstructor
public class ScrpaerScheduler {
private final WebScraper webScraper;
@Scheduled(cron="0 0 1 1 * ?")
public void runningScraperJson() {
webScraper.createJsonToScraper();
}
}
Python 웹 스크래핑 프레임워크와 라이브러리
- Scrapy ( https://scrapy.org/ ): Python에서 가장 인기있는 Web Crawler , 추출한 데이터를 원하는 형식(JSON, XML 및 CSV)으로 저장하는 데 용이 , 비동기 네트워킹 프레임워크를 기반으로 구축
- MechanicalSoup ( https://mechanicalsoup.readthedocs.io/en/stable/ ) : Python의 Request (http 세션용) 및 BeautifulSoup(문서탐색용)기반으로 구축 , 자동으로 쿠키 저장 및 전송 리디렉션을 따르고 링크를 따라가며 양식을 제출 , 특정 이벤트를 기다리거나 데이터를 스크랩하는 대신 특정 항목을 클릭하는 것과 같은 유저의 행동을 시뮬레이션하는 경우에 유리 , CSS 및 XPath 선택기 지원
- PySpider ( https://docs.pyspider.org/en/latest/ ) : 스크립트 편집기, 프로젝트 관리자 및 결과 뷰어가 포함된 강력한 Web UI, 분산된 아키텍처, 데이터 저장을 위해 MongoDB 및 MySQL과 같은 데이터베이스 지원
- BeautifulSoup ( https://www.crummy.com/software/BeautifulSoup/bs4/doc/ ) : HTML 과 XML 파일로부터 특정 데이터를 가져오기 위해서 HTML의 태그들을 구성을 트리 형태로 만들어주어서, 특정 태그에 접근하기 쉬움, 정보 파싱 및 크롤링 속도가 빠르다. !! Sing Page Application 구조 및 Javascript를 이용한 비동기 처리된 데이터들은 파싱할 수 없다!
BeautifulSoup 예시
import requests
from bs4 import BeautifulSoup
import json
import os
## python파일의 위치
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
req = requests.get('https://beomi.github.io/beomi.github.io_old/')
html = req.text
soup = BeautifulSoup(html, 'html.parser')
my_titles = soup.select(
'h3 > a'
)
data = {}
for title in my_titles:
data[title.text] = title.get('href')
with open(os.path.join(BASE_DIR, 'result.json'), 'w+') as json_file:
json.dump(data, json_file)
웹 스크래핑 시, 해야 할 일과 하지 말아야 할 일
- 한 개의 IP연결만 이용할 것
- Web Server에 부하가 가지 않도록 하기 위해서 피크타임이 아닌 시간에 크롤링하며, 크롤링 속도를 조절할 필요가 있다. : 서버의 부하가 커지면 IP 차단의 가능성이 있다.
- 사이트의 ToS(서비스 약관) 지킬 것.
- Robots.txt 파일을 준수하여야 한다.
- 필요한 정보 수집 이외의 목적으로 정보를 수집하지 않도록 프로그램 제작을 하도록 지향한다.
- GDPR(일반 데이터 보호 규칙) / CCPA(캘리포니아 소비자 프라이버시 보호법) 룰 지킬 것
---
- 출처
[ Wikipedia | Web Crawling ](https://en.wikipedia.org/wiki/Webcrawler)_
[ Wikipedia | Web Scraping](https://en.wikipedia.org/wiki/Webscraping)_
[ Wikipedia | Robots.txt ](https://ko.wikipedia.org/wiki/%EB%A1%9C%EB%B4%87%EB%B0%B0%EC%A0%9C%ED%91%9C%EC%A4%80)
xperti | JAVA와 Python의 Web Scraping Library
[ blog |텍스트 자료의 수집 ](http://bigdata.dongguk.ac.kr/lectures/bigdata/book/%EC%9B%B9%EC%8A%A4%ED%81%AC%EB%9E%98%ED%95%91.html)_