<html>
<head>
<title>Page Title</title>
</head>
<body>
<h2> This is page heading </h2>
<p> THis is first paragraph text </p>
</body>
</html>
6 크롤링 기초
API를 이용하면 데이터를 매우 쉽게 수집할 수 있지만, 국내 주식 데이터를 다운로드 하기에는 한계가 있으며, 원하는 데이터가 API의 형태로 제공된다는 보장도 없습니다. 따라서 우리는 필요한 데이터를 얻기 위해 직접 찾아 나서야 합니다.
크롤링 혹은 스크래핑이란 웹사이트에서 원하는 정보를 수집하는 기술입니다. 대부분의 금융 웹사이트는 간단한 형태로 작성되어 있어, 몇 가지 기술만 익히면 어렵지 않게 데이터를 크롤링할 수 있습니다.
**크롤링을 할 때 주의해야 할 점이 있습니다. 특정 웹사이트의 페이지를 쉬지 않고 크롤링하는 행위를 무한 크롤링이라고 합니다. 무한 크롤링은 해당 웹사이트의 자원을 독점하게 되어 타인의 사용을 막게 되며 웹사이트에 부하를 주게 됩니다. 일부 웹사이트에서는 동일한 IP로 쉬지 않고 크롤링을 할 경우 접속을 막아버리는 경우도 있습니다. 따라서 하나의 페이지를 크롤링한 후 1\~2초 가량 정지하고 다시 다음 페이지를 크롤링하는 것이 좋습니다.**
6.1 인코딩의 이해와 R에서 UTF-8 설정하기
R에서 스크립트를 한글로 작성해 저장한 후 이를 다시 불러올 때, 혹은 한글로 된 데이터를 크롤링하면 오류가 뜨거나 읽을 수 없는 문자로 나타나는 경우가 종종 있습니다. 이는 한글 인코딩 때문에 발생하는 문제이며, 이러한 현상을 흔히 ’인코딩이 깨졌다’라고 표현합니다. 인코딩이란 사람이 사용하는 언어를 컴퓨터가 사용하는 0과 1로 변환하는 과정을 말하며, 이와 반대의 과정을 디코딩이라고 합니다.
이렇듯 사람과 컴퓨터 간의 언어를 번역하기 위해 최초로 사용된 방식이 아스키(ASCII: American Standard Code for Information Interchange)입니다. 0부터 127까지 총 128개 바이트에 알파벳과 숫자, 자주 사용되는 특수문자 값을 부여하고, 문자가 입력되면 이에 대응되는 바이트가 저장됩니다. 그러나 아스키의 ’American’이라는 이름에서 알 수 있듯이 이는 영어의 알파벳이 아닌 다른 문자를 표현하는 데 한계가 있으며, 이를 보완하기 위한 여러 방법이 나오게 되었습니다.
6.2 한글 인코딩 방식의 종류
인코딩에 대한 전문적인 내용은 이 책의 범위를 넘어가며, 크롤링을 위해서는 한글을 인코딩하는 데 쓰이는 EUC-KR과 CP949, UTF-8 정도만 이해해도 충분합니다. 만일 ’알’이라는 단어를 인코딩한다면 어떤 방법이 있을까요? 먼저 ’알’이라는 문자 자체에 해당하는 코드를 부여해 나타내는 방법이 있습니다. 아니면 이를 구성하는 모음과 자음을 나누어 ㅇ, ㅏ, ㄹ 각각에 해당하는 코드를 부여하고 이를 조합할 수도 있습니다. 전자와 같이 완성된 문자 자체로 나타내는 방법을 완성형, 후자와 같이 각 자모로 나타내는 방법을 조합형이라고 합니다.
한글 인코딩 중 완성형으로 가장 대표적인 방법은 EUC-KR입니다. EUC-KR은 현대 한글에서 많이 쓰이는 문자 2,350개에 번호를 붙인 방법입니다. 그러나 2,350개 문자로 모든 한글 자모의 조합을 표현하기 부족해, 이를 보완하고자 마이크로소프트가 도입한 방법이 CP949입니다. CP949는 11,720개 한글 문자에 번호를 붙인 방법으로 기존 EUC-KR보다 나타낼 수 있는 한글의 개수가 훨씬 많아졌습니다. 윈도우의 경우 기본 인코딩이 CP949로 되어 있습니다.
조합형의 대표적 방법인 UTF-8은 모음과 자음 각각에 코드를 부여한 후 조합해 한글을 나타냅니다. 조합형은 한글뿐만 아니라 다양한 언어에 적용할 수 있다는 장점이 있어 전 세계 웹페이지의 대부분이 UTF-8로 만들어지고 있습니다.
6.3 웹의 동작 방식
크롤링은 웹사이트의 정보를 수집하는 과정입니다. 따라서 웹이 어떻게 동작하는지 이해할 필요가 있습니다.
먼저 클라이언트란 여러분의 데스크톱이나 휴대폰과 같은 장치와 크롬이나 파이어폭스와 같은 소프트웨어를 의미합니다. 서버는 웹사이트와 앱을 저장하는 컴퓨터를 의미합니다. 클라이언트가 특정 정보를 요구하는 과정을 ’요청’이라고 하며, 서버가 해당 정보를 제공하는 과정을 ’응답’이라고 합니다. 그러나 클라이언트와 서버가 연결되어 있지 않다면 둘 사이에 정보를 주고받을 수 없으며, 이를 연결하는 공간이 바로 인터넷입니다. 또한 건물에도 고유의 주소가 있는 것처럼, 각 서버에도 고유의 주소가 있는데 이것이 인터넷 주소 혹은 URL입니다.
여러분이 네이버에서 경제 기사를 클릭하는 경우를 생각해봅시다. 클라이언트는 사용자인 여러분이고, 서버는 네이버이며, URL은 www.naver.com 이 됩니다. 경제 기사를 클릭하는 과정이 요청이며, 클릭 후 해당 페이지를 보여주는 과정이 응답입니다.
6.3.1 HTTP
클라이언트가 각기 다른 방법으로 데이터를 요청한다면, 서버는 해당 요청을 알아듣지 못할 것입니다. 이를 방지하기 위해 규정된 약속이나 표준에 맞추어 데이터를 요청해야 합니다. 이러한 약속을 HTTP(HyperText Transfer Protocol)라고 합니다.
클라이언트가 서버에게 요청의 목적이나 종류를 알리는 방법을 HTTP 요청 방식(HTTP Request Method)이라고 합니다. HTTP 요청 방식은 크게 GET, POST, PUT, DELETE라는 네 가지로 나눌 수 있지만 크롤링에는 GET과 POST 방식이 대부분 사용되므로 이 두 가지만 알아도 충분합니다.
인터넷을 사용하다 보면 한 번쯤 ‘이 페이지를 볼 수 있는 권한이 없음(HTTP 오류 403 - 사용할 수 없음)’ 혹은 ’페이지를 찾을 수 없음(HTTP 오류 404 - 파일을 찾을 수 없음)’이라는 오류를 본 적이 있을 겁니다. 여기서 403과 404라는 숫자는 클라이언트의 요청에 대한 서버의 응답 상태를 나타내는 HTTP 상태 코드입니다.
HTTP 상태 코드는 100번대부터 500번대까지 있으며, 성공적으로 응답을 받을 시 200번 코드를 받게 됩니다. 각 코드에 대한 내용은 HTTP 상태 코드를 검색하면 확인할 수 있으며, 크롤링 과정에서 오류가 발생할 시 해당 코드를 통해 어떤 부분에서 오류가 발생했는지 확인이 가능합니다.
코드 | 내용 | 설명 |
---|---|---|
1xx | Informational (조건부 응답) | 리퀘스트를 받고, 처리 중에 있음 |
2xx | Success (성공) | 리퀘스트를 정상적으로 처리함 |
3xx | Redirection (리디렉션) | 리퀘스트 완료를 위해 추가 동작이 필요함 |
4xx | Client Error (클라이언트 오류) | 클라이언트 요청을 처리할 수 없어 오류 발생 |
5xx | Server Error (서버 오류) | 서버에서 처리를 하지 못하여 오류 발생 |
6.4 HTML과 CSS
클라이언트와 서버가 데이터를 주고받을 때는 디자인이라는 개념이 필요하지 않습니다. 그러나 응답받은 정보를 사람이 확인하려면 보기 편한 방식으로 바꾸어줄 필요가 있는데 웹페이지가 그러한 역할을 합니다. 웹페이지의 제목, 단락, 목록 등 레이아웃을 잡아주는 데 쓰이는 대표적인 마크업 언어가 HTML(HyperText Markup Language)입니다. HTML을 통해 잡혀진 뼈대에 글자의 색상이나 폰트, 배경색, 배치 등 화면을 꾸며주는 역할을 하는 것이 CSS(Cascading Style Sheets)입니다.
우리의 목적은 웹페이지를 만드는 것이 아니므로 HTML과 CSS에 대해 자세히 알 필요는 없습니다. 그러나 크롤링하고자 하는 데이터가 웹페이지의 어떤 태그 내에 위치하고 있는지, 어떻게 크롤링하면 될지 파악하기 위해서는 HTML과 CSS에 대한 기본적인 지식은 알아야 합니다.
HTML과 CSS의 실습은 아래 페이지에서 해볼 수 있습니다.
https://www.w3schools.com/html/tryit.asp?filename=tryhtml_intro
6.4.1 HTML 기본 구조
HTML은 크게 메타 데이터를 나타내는 head와 본문을 나타내는 body로 나누어집니다. head에서 title은 웹페이지에서 나타나는 제목을 나타내며 body 내에는 본문에 들어갈 각종 내용들이 포함되어 있습니다.
<head> 부분에 입력한 내역은 실습 페이지 구조 상 확인되지 않지만, <body> 부분에 입력한 글자들은 우측 결과물 페이지에서 확인이 가능합니다. <h2>와 <p> 등의 태그가 하는 역할들에 대해서 더욱 자세히 알아보도록 하겠습니다.
6.4.2 태그와 속성
HTML 코드는 태그와 속성, 내용으로 이루어져 있습니다. 크롤링한 데이터에서 특정 태그의 데이터만을 찾는 방법, 특정 속성의 데이터만을 찾는 방법, 뽑은 자료에서 내용만을 찾는 방법 등 내용을 찾는 방법이 모두 다르기 때문에 태그와 속성에 대해 좀 더 자세히 살펴보겠습니다.
꺾쇠(<>)로 감싸져 있는 부분을 태그라고 하며, 여는 태그 <>가 있으면 반드시 이를 닫는 태그인 </>가 쌍으로 있어야 합니다. 속성은 해당 태그에 대한 추가적인 정보를 제공해주는 것으로, 뒤에 속성값이 따라와야 합니다. 내용은 우리가 눈으로 보는 텍스트 부분을 의미합니다. 앞의 HTML 코드는 문단을 나타내는
, 정렬을 나타내는 align 속성과 center를 통해 가운데 정렬을 지정하며, 내용에는 ’퀀트 투자 Cookbook’을 나타내고,
태그를 통해 태그를 마쳤습니다.
6.4.3 h 태그와 p 태그
h 태그는 폰트의 크기를 나타내는 태그이며, p 태그는 문단을 나타내는 태그입니다. 이를 사용한 간단한 예제는 다음과 같습니다. h 태그의 숫자가 작을수록 텍스트 크기는 커지는 것이 확인되며, 숫자는 1에서 6까지 지원됩니다. p 태그를 사용하면 각각의 문단이 만들어지는 것이 확인됩니다.
<html>
<body>
<h1>Page heading: size 1</h1>
<h2>Page heading: size 2</h2>
<h3>Page heading: size 3</h3>
<p>Quant Cookbook</p>
<p>By Henry</p>
</body>
</html>
h 태그의 숫자가 작을수록 텍스트 크기는 커지며, 숫자는 1에서 6까지 지원됩니다. 또한 p 태그를 사용하면 각각의 문단이 만들어집니다.
6.4.4 리스트를 나타내는 ul 태그와 ol 태그
ul과 ol 태그는 리스트(글머리 기호)를 만들 때 사용됩니다. ul은 순서가 없는 리스트(unordered list), ol은 순서가 있는 리스트(ordered list)를 만듭니다.
<html>
<body>
<h2> Unordered List</h2>
<ul>
<li>List 1</li>
<li>List 2</li>
<li>List 3</li>
</ul>
<h2> Ordered List</h2>
<ol>
<li>List A</li>
<li>List B</li>
<li>List C</li>
<li>List D</li>
</ol>
</body>
</html>
ul 태그로 감싼 부분은 글머리 기호가 순서가 없는 •으로 표현되며, ol 태그로 감싼 부분은 숫자가 순서대로 표현됩니다. 각각의 리스트는 li를 통해 생성됩니다.
6.4.5 table 태그
table 태그는 표를 만드는 태그입니다.
<html>
<body>
<h2>Sample Table</h2>
<table>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>C</td>
</tr>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
</tr>
</table>
</body>
</html>
table 태그 내의 tr 태그는 각 행을 의미하며, 각 셀의 구분은 th 혹은 td 태그를 통해 구분할 수 있습니다. th 태그는 진하게 표현되므로 주로 테이블의 제목에 사용되고, td 태그는 테이블의 내용에 사용됩니다.
6.4.6 a 태그와 img 태그 및 속성
a 태그와 img 태그는 다른 태그와는 다르게, 혼자 쓰이기보다는 속성과 결합해 사용됩니다. a 태그는 href 속성과 결합해 다른 페이지의 링크를 걸 수 있습니다. img 태그는 src 속성과 결합해 이미지를 불러옵니다.
<html>
<body>
<h2>a tag & href attribute</h2>
<p>HTML links are defined with the a tag.
</p>
The link address is specified in the href attribute:
<a href="https://blog.naver.com/leebisu">Henry's Quantopia</a>
<h2>img tag & src attribute</h2>
<p>HTML images are defined with the img tag,
and the filename of the image source is</p>
specified in the src attribute:
<img src="https://www.python.org/static/img/python-logo.png",
width="200",height="100">
</body>
</html>
a 태그 뒤 href 속성에 연결하려는 웹페이지 주소를 속성값(https://blog.naver.com/leebisu)으로으로){.uri} 입력한 후 내용(Henry’s Quantopia)을 입력하면, 내용 텍스트에 웹페이지의 링크가 추가됩니다. img 태그 뒤 src 속성의 속성값에는 불러오려는 이미지 주소를 입력하며, width 속성과 height 속성을 통해 이미지의 가로 세로 길이를 조절할 수도 있습니다. 페이지 내에서 링크된 주소를 모두 찾거나, 모든 이미지를 저장하려고 할 때 속성값을 찾으면 손쉽게 원하는 작업을 할 수 있습니다.
6.4.7 div 태그
div 태그는 화면의 전체적인 틀(레이아웃)을 만들 때 주로 사용하는 태그입니다. 단독으로도 사용될 수 있으며, 꾸밈을 담당하는 style 속성과 결합되어 사용되기도 합니다.
<html>
<body>
<div style="background-color:black;color:white">
<h5>First Div</h5>
<p>Black backgrond, White Color</p>
</div>
<div style="background-color:yellow;color:red">
<h5>Second Div</h5>
<p>Yellow backgrond, Red Color</p>
</div>
<div style="background-color:blue;color:grey">
<h5>Second Div</h5>
<p>Blue backgrond, Grey Color</p>
</div>
</body>
</html>
div 태그를 통해 총 세 개의 레이아웃으로 나누어진 것을 알 수 있습니다. style 속성 중 background-color는 배경 색상을, color는 글자 색상을 의미하며, 각 레이아웃마다 다른 스타일이 적용되었습니다.
6.4.8 CSS
CSS는 앞서 설명했듯이 웹페이지를 꾸며주는 역할을 합니다. head에서 각 태그에 CSS 효과를 입력하면 본문의 모든 해당 태그에 CSS 효과가 적용됩니다. 이처럼 웹페이지를 꾸미기 위해 특정 요소에 접근하는 것을 셀렉터(Selector)라고 합니다.
<html>
<head>
<style>
background-color: powderblue;}
body {color: blue;}
h4 {</style>
</head>
<body>
<h4>This is a heading</h4>
<p>This is a first paragraph.</p>
<p>This is a second paragraph.</p>
</body>
</html>
head의 style 태그에서 여러 CSS 효과가 정의되었습니다. 먼저 body의 전체 배경 색상을 powderblue로 설정했으며, h4 태그의 글자 색상은 파란색(blue)으로 설정했습니다. body 태그 내에서 style에 태그를 주지 않더라도, head에서 정의한 CSS 효과가 모두 적용됩니다.
6.4.9 클래스와 id
위의 예제에서 클래스 속성을 이용하면 특정 이름을 가진 클래스에 동일한 효과를 적용할 수 있습니다.
<html>
<style>
.language {
background-color: tomato;
color: white;
padding: 10px;
} .desc {
background-color: moccasin;
color: black;
padding: 10px;
} </style>
<div>
<h2 class="language">Python</h2>
<p class="desc"> Python is a high-level, general-purpose programming language.</p>
</div>
<div>
<h2>SQL</h2>
<p>SQL is a domain-specific language used in programming and designed for managing data held in a RDBMS, or for stream processing in a RDBMS. </p>
</div>
<div>
<h2 class="language">R</h2>
<p class="desc">R is a free software environment for statistical computing and graphics.</p>
<div>
</html>
셀렉터를 클래스에 적용할 때는 클래스명 앞에 마침표(.)를 붙여 표현합니다다. 위 예제에서 language 클래스는 배경 색상이 tomato, 글자 색상은 흰색, 여백은 10px로 정의됩니다. desc 클래스는 배경 색상이 moccasin, 글자 색상은 검은색, 여백은 10px로 정의되었습니다. 본문의 첫 번째(Python)와 세 번째(R) 레이아웃의 h2 태그 뒤에는 language 클래스를, p 태그 뒤에는 desc 클래스를 속성으로 입력했습니다. 따라서 해당 레이아웃에만 CSS 효과가 적용되며, 클래스 값이 없는 두 번째 레이아웃에는 효과가 적용되지 않습니다.
id 또한 이와 비슷한 역할을 합니다. HTML 내에서 클래스는 여러 개가 정의될 수 있는 반면, id는 단 하나만 사용하기를 권장합니다.
<html>
<head>
<style>
#myHeader {
background-color: lightblue;
color: black;
padding: 15px;
text-align: center;
}
</style>
</head>
<body>
<h1 id="myHeader">My Header</h1>
</body>
</html>
셀렉터를 id에 적용할 때는 id명 앞에 샵(#)를 붙여 표현하며, 페이지에서 한 번만 사용된다는 점을 제외하면 클래스와 사용 방법이 거의 동일합니다. 클래스나 id 값을 통해 원하는 내용을 크롤링하는 경우도 많으므로, 각각의 이름 앞에 마침표(.)와 샵(#) 을 붙여야 한다는 점을 꼭 기억해야 합니다.
7 크롤링 이해하기
7.1 GET과 POST 방식 이해하기
우리가 인터넷에 접속해 서버에 파일을 요청하면, 서버는 이에 해당하는 파일을 우리에게 보내줍니다. 크롬과 같은 웹 브라우저는 이러한 과정을 사람이 수행하기 편하고 시각적으로 보기 편하도록 만들어진 것이며, 인터넷 주소는 서버의 주소를 기억하기 쉽게 만든 것입니다. 우리가 서버에 데이터를 요청하는 형태는 다양하지만 크롤링에서는 주로 GET과 POST 방식을 사용합니다.
7.1.1 GET 방식
GET 방식은 인터넷 주소를 기준으로 이에 해당하는 데이터나 파일을 요청하는 것입니다. 주로 클라이언트가 요청하는 쿼리를 앰퍼샌드(&) 혹은 물음표(?) 형식으로 결합해 서버에 전달합니다.
네이버 홈페이지에 접속한 후 [퀀트]를 검색하면, 주소 끝부분에 [&query=퀀트]가 추가되며 이에 해당하는 페이지의 내용을 보여줍니다. 즉, 해당 페이지는 GET 방식을 사용하고 있으며 입력 종류는 query, 입력값은 [퀀트]임을 알 수 있습니다.
[헤지펀드]를 다시 검색하면, 주소 끝부분이 [&query=헤지펀드&oquery=퀀트…]로 변경됩니다. 현재 입력값은 헤지펀드, 기존 입력값은 퀀트이며 이러한 과정을 통해 연관검색어가 생성됨도 유추해볼 수 있습니다.
7.1.2 POST 방식
POST 방식은 사용자가 필요한 값을 추가해서 요청하는 방법입니다. GET 방식과 달리 클라이언트가 요청하는 쿼리를 body에 넣어서 전송하므로 요청 내역을 직접 볼 수 없습니다. 동행복권 홈페이지에 접속해 [당첨결과] 메뉴를 확인해봅시다.
이번엔 회차 바로가기를 변경한 후 [조회]를 클릭합니다. 페이지의 내용은 선택일 기준으로 변경되었지만, 주소는 변경되지 않고 그대로 남아 있습니다. GET 방식에서는 입력 항목에 따라 웹페이지 주소가 변경되었지만, POST 방식을 사용해 서버에 데이터를 요청하는 해당 웹사이트는 그렇지 않은 것을 알 수 있습니다.
POST 방식의 데이터 요청 과정을 살펴보려면 개발자도구를 이용해야 하며, 크롬에서는 [F12]키를 눌러 개발자도구 화면을 열 수 있습니다. 개발자도구 화면을 연 상태에서 다시 한번 [조회]를 클릭해봅시다. [Network] 탭을 클릭하면, [조회]을 클릭함과 동시에 브라우저와 서버 간의 통신 과정을 살펴볼 수 있습니다. 이 중 상단의 gameResult.do?method=byWin 이라는 항목이 POST 형태임을 알 수 있습니다.
해당 메뉴를 클릭하면 통신 과정을 좀 더 자세히 알 수 있습니다. [Payload] 탭의 [Form Data]에는 서버에 데이터를 요청하는 내역이 있습니다. drwNo와 dwrNoList에는 선택한 회차의 숫자가 들어가 있습니다.
이처럼 POST 방식은 요청하는 데이터에 대한 쿼리가 GET 방식처럼 URL을 통해 전송되는 것이 아닌 body를 통해 전송되므로, 이에 대한 정보는 웹 브라우저를 통해 확인할 수 없으며, 개발자도구 화면을 통해 확인해야 합니다.
8 크롤링 예제
일반적으로 크롤링은 다음의 과정을 따릅니다. 먼저, httr 패키지의 GET()
혹은 POST()
함수를 이용해 데이터를 다운로드한 후 rvest 패키지의 함수들을 이용해 원하는 데이터를 찾는 과정으로 이루어집니다.
8.1 명언 크롤링하기
크롤링의 간단한 예제로 ‘Quotes to Scrape’ 사이트에 있는 명언을 수집힙니다.
먼저 해당사이트에 접속한 후, 명언에 해당하는 부분에 마우스 커서를 올려둔 후 마우스 오른쪽 버튼을 클릭하고 [검사]를 선택하면 개발자 도구 화면이 나타납니다. 여기서 해당 글자가 HTML 내에서 어떤 부분에 위치하는지 확인할 수 있습니다. 각 네모에 해당하는 부분은 [div 태그의 quote 클래스]에 위치하고 있으며, 명언은 [div 태그 → span 태그의 text 클래스]에, 명언가는 [div 태그 → span 태그 → small 태그의 author 클래스]에 위치하고 있습니다.
먼저 해당 페이지의 내용을 불러옵니다.
library(httr)
library(rvest)
= 'https://quotes.toscrape.com/'
url = GET(url)
quote
print(quote)
Response [https://quotes.toscrape.com/]
Date: 2023-01-19 03:02
Status: 200
Content-Type: text/html; charset=utf-8
Size: 11.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quotes to Scrape</title>
<link rel="stylesheet" href="/static/bootstrap.min.css">
<link rel="stylesheet" href="/static/main.css">
</head>
<body>
<div class="container">
...
url에 해당 주소를 입력한 후 GET()
함수를 이용해 해당 페이지의 내용을 받아온다. Response 부분을 확인해보면 Status가 200, 즉 데이터가 이상 없이 받아졌음이 확인됩니다.
이제 rvest 패키지를 이용해 html 정보를 불러오도록 합니다.
= read_html(quote)
quote_html
print(quote_html)
{html_document}
<html lang="en">
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
[2] <body>\n <div class="container">\n <div class="row header-box"> ...
read_html()
함수는 받아온 페이지 내용을 html 정보로 변환합니다. 이를 확인해보면 head 부분과 body 부분이 존재합니다.
우리는 개발자 도구 화면에서 명언에 해당하는 부분이 [div 태그의 quote 클래스 → span 태그의 text 클래스]에 위치하고 있음을 살펴보았습니다. 이를 활용해 명언만을 추출하는 방법은 다음과 같습니다.
= quote_html %>%
quote_div html_nodes('div.quote') %>%
html_nodes('span.text')
print(quote_div)
{xml_nodeset (10)}
[1] <span class="text" itemprop="text">“The world as we have created it is a ...
[2] <span class="text" itemprop="text">“It is our choices, Harry, that show ...
[3] <span class="text" itemprop="text">“There are only two ways to live your ...
[4] <span class="text" itemprop="text">“The person, be it gentleman or lady, ...
[5] <span class="text" itemprop="text">“Imperfection is beauty, madness is g ...
[6] <span class="text" itemprop="text">“Try not to become a man of success. ...
[7] <span class="text" itemprop="text">“It is better to be hated for what yo ...
[8] <span class="text" itemprop="text">“I have not failed. I've just found 1 ...
[9] <span class="text" itemprop="text">“A woman is like a tea bag; you never ...
[10] <span class="text" itemprop="text">“A day without sunshine is like, you ...
html_nodes()
함수는 태그, id, 클래스, xpath 등의 정보를 이용해 해당하는 데이터를 추출합니다. div 태그에서 클래스에 해당하는 quote 클래스에 해당하는 데이터를 찾으며, 클래스의 경우 앞에 콤마(.)를 붙입니다(id의 경우 앞에 샵(#)을 붙입니다).html_node()
함수는 해당하는 정보 중 가장 첫번째 데이터만을 추출하며,html_nodes()
함수는 모든 데이터를 추출하는 차이가 있습니다.- span 태그 중 text 클래스를 찾습니다.
결과를 확인하면 개발자 도구 화면과 거의 일치합니다. 이제 텍스트에 해당하는 부분만 추출합니다.
= quote_div %>%
quote_text html_text()
print(quote_text)
[1] "“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”"
[2] "“It is our choices, Harry, that show what we truly are, far more than our abilities.”"
[3] "“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”"
[4] "“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”"
[5] "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”"
[6] "“Try not to become a man of success. Rather become a man of value.”"
[7] "“It is better to be hated for what you are than to be loved for what you are not.”"
[8] "“I have not failed. I've just found 10,000 ways that won't work.”"
[9] "“A woman is like a tea bag; you never know how strong it is until it's in hot water.”"
[10] "“A day without sunshine is like, you know, night.”"
html_text()
함수를 이용하면 html 에서 텍스트 정보만을 추출할 수 있습니다.
이번에는 명언을 말한 사람에 해당하는 정보만 수집합니다.
= quote_html %>%
quote_who html_nodes('div.quote') %>%
html_nodes('span') %>%
html_nodes('small.author') %>%
html_text()
print(quote_who)
[1] "Albert Einstein" "J.K. Rowling" "Albert Einstein"
[4] "Jane Austen" "Marilyn Monroe" "Albert Einstein"
[7] "André Gide" "Thomas A. Edison" "Eleanor Roosevelt"
[10] "Steve Martin"
파이프 오퍼레이터(%>%)를 통해 html에서 원하는 부분만 추출하는 작업을 단계별로 진행할 수 있습니다.
마지막으로 명언가 정보에 해당하는 링크(about)를 불러오도록 합니다. 이는 [quote 클래스 → span 태그 → a 태그 내에서 href 속성]에 위치하고 있습니다.
= quote_html %>%
quote_link html_nodes('div.quote') %>%
html_nodes('span') %>%
html_nodes('a') %>%
html_attr('href')
print(quote_link)
[1] "/author/Albert-Einstein" "/author/J-K-Rowling"
[3] "/author/Albert-Einstein" "/author/Jane-Austen"
[5] "/author/Marilyn-Monroe" "/author/Albert-Einstein"
[7] "/author/Andre-Gide" "/author/Thomas-A-Edison"
[9] "/author/Eleanor-Roosevelt" "/author/Steve-Martin"
html_attr()
함수는 속성값을 찾아주는 함수이며, href 속성에 해당하는 값을 찾아줍니다.
기본 url(https://quotes.toscrape.com)에에){.uri} 해당하는 정보가 없으므로 이를 추가적으로 붙여줍니다.
= paste0('https://quotes.toscrape.com', quote_link)
quote_link
quote_link
[1] "https://quotes.toscrape.com/author/Albert-Einstein"
[2] "https://quotes.toscrape.com/author/J-K-Rowling"
[3] "https://quotes.toscrape.com/author/Albert-Einstein"
[4] "https://quotes.toscrape.com/author/Jane-Austen"
[5] "https://quotes.toscrape.com/author/Marilyn-Monroe"
[6] "https://quotes.toscrape.com/author/Albert-Einstein"
[7] "https://quotes.toscrape.com/author/Andre-Gide"
[8] "https://quotes.toscrape.com/author/Thomas-A-Edison"
[9] "https://quotes.toscrape.com/author/Eleanor-Roosevelt"
[10] "https://quotes.toscrape.com/author/Steve-Martin"
세 개의 데이터를 하나의 데이터프레임으로 묶어 정리합니다.
= data.frame(
quote_df 'quote' = quote_text,
'author' = quote_who,
'link' = quote_link
)
quote_df
quote
1 “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”
2 “It is our choices, Harry, that show what we truly are, far more than our abilities.”
3 “There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”
4 “The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”
5 “Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”
6 “Try not to become a man of success. Rather become a man of value.”
7 “It is better to be hated for what you are than to be loved for what you are not.”
8 “I have not failed. I've just found 10,000 ways that won't work.”
9 “A woman is like a tea bag; you never know how strong it is until it's in hot water.”
10 “A day without sunshine is like, you know, night.”
author link
1 Albert Einstein https://quotes.toscrape.com/author/Albert-Einstein
2 J.K. Rowling https://quotes.toscrape.com/author/J-K-Rowling
3 Albert Einstein https://quotes.toscrape.com/author/Albert-Einstein
4 Jane Austen https://quotes.toscrape.com/author/Jane-Austen
5 Marilyn Monroe https://quotes.toscrape.com/author/Marilyn-Monroe
6 Albert Einstein https://quotes.toscrape.com/author/Albert-Einstein
7 André Gide https://quotes.toscrape.com/author/Andre-Gide
8 Thomas A. Edison https://quotes.toscrape.com/author/Thomas-A-Edison
9 Eleanor Roosevelt https://quotes.toscrape.com/author/Eleanor-Roosevelt
10 Steve Martin https://quotes.toscrape.com/author/Steve-Martin
8.2 xpath를 이용해 원하는 지점만 찾기
xpath란 HTML의 위치를 나타내는 형태입니다. 만일 웹페이지에서 한 지점의 데이터만 추출해야 할 경우, 번거롭게 html의 구조를 모두 발라내기 보다는 xpath를 이용하는 것이 훨씬 효율적입니다.
예제로 yes24의 판매지수를 크롤링 하도록 하겠습니다. 해당 지수는 책의 출간일 그리고 판매량에 따라 매일 바뀝니다.
http://www.yes24.com/Product/Goods/108408162
이 중 개발자도구 화면에서 판매지수에 해당하는 부분을 찾은 후, 우클릭 해 [Copy → Copy xpath]를 선택하면 해당 내역이 복사됩니다. 이는 다음과 같습니다.
//*[@id="yDetailTopWrap"]/div[2]/div[1]/span[3]/span[3]
이를 이용해 크롤링 하는 법은 다음과 같습니다.
library(rvest)
library(httr)
= 'http://www.yes24.com/Product/Goods/108408162'
url
= GET(url)
data
= data %>%
data_sales read_html() %>%
html_nodes(xpath = '//*[@id="yDetailTopWrap"]/div[2]/div[1]/span[3]/span[3]')
print(data_sales)
{xml_nodeset (1)}
[1] <span class="gd_sellNum">\r\n <em class="divi">|</em>\r\n ...
xpath를 통해 해당 부분만 손쉽게 추출할 수 있습니다. 이제 판매지수에 해당하는 숫자만 선택하도록 하면 됩니다.
library(stringr)
%>%
data_sales html_text() %>%
str_extract_all('(\\d)+') %>%
1]] %>%
.[[str_c(., collapse = '') %>%
as.numeric()
[1] 4155
먼저 정규표현식을 이용해 숫자부분만 추출할 수 있습니다.
html_text()
함수를 통해 텍스트 데이터만 불러옵니다.str_extract_all()
함수 내에 정규표현식을 이용해 숫자에 해당하는 데이터만 추출합니다.- 첫번째 원소를 선택한다.
str_c()
함수를 통해 모든 텍스트를 붙입니다.as.numeric()
함수를 이용해 숫자 형태로 변경합니다.
그러나 정규표현식은 꽤나 복잡합니다. 다행히 패키지 내 함수를 사용하면 위 과정을 매우 간단하게 수행할 수 있습니다.
%>%
data_sales html_text() %>%
::parse_number() readr
[1] 4155
readr 패키지의 parse_number()
함수는 여러 텍스트 중 숫자 형태만 추출해주는 매우 유용한 함수입니다.
8.3 표 크롤링
표는 html 중 td와 th 태그로 구성되어 있으며, 이를 일일이 추출하며 다시 표 형태로 만드는 것은 사실상 불가능에 가깝습니다. 다행히 R의 크롤링 패키지에는 표 형태만 간단하게 추출할 수 있는 함수 역시 존재합니다.
예제로써 UFC의 역대 챔피언 리스트를 크롤링 해보도록 합시다.
https://en.wikipedia.org/wiki/List_of_UFC_champions
위 페이지에는 각 체급별 챔피언의 역사가 표 형태로 정리되어 있습니다. 이 중 표만 가져오는 법은 다음과 같습니다.
library(rvest)
library(httr)
= 'https://en.wikipedia.org/wiki/List_of_UFC_champions'
url = GET(url) data_ufc
%>%
data_ufc read_html() %>%
html_table()
html_table()
함수는 테이블 형태의 데이터만 추출하는 함수입니다. 그러나 윈도우의 경우 다음과 같은 오류가 뜹니다.
in type.convert.default(x, as.is = TRUE, dec = dec, na.strings = na.strings) :
Error '<e2><94>' invalid multibyte string at
이는 웹페이지와 컴퓨터의 인코딩이 달라 발생하는 오류이며, 잠시 로케일 언어를 영어로 변경하여 크롤링 하면 오류가 발생하지 않습니다.
Sys.setlocale("LC_ALL", "English")
Warning in Sys.setlocale("LC_ALL", "English"): using locale code page other than
65001 ("UTF-8") may cause problems
[1] "LC_COLLATE=English_United States.1252;LC_CTYPE=English_United States.1252;LC_MONETARY=English_United States.1252;LC_NUMERIC=C;LC_TIME=English_United States.1252"
= data_ufc %>%
champion_list read_html() %>%
html_table()
Sys.setlocale("LC_ALL", "Korean")
Warning in Sys.setlocale("LC_ALL", "Korean"): using locale code page other than
65001 ("UTF-8") may cause problems
[1] "LC_COLLATE=Korean_Korea.949;LC_CTYPE=Korean_Korea.949;LC_MONETARY=Korean_Korea.949;LC_NUMERIC=C;LC_TIME=Korean_Korea.949"
champion_list는 44개 원소로 이루어진 리스트이며, 각 원소에는 웹페이지의 테이블 정보가 들어가 있습니다. 이 중 원하는 표만 선택하면 됩니다.
1]] champion_list[[
# A tibble: 9 x 4
Division Champion Since Defenses
<chr> <chr> <chr> <chr>
1 Heavyweight Vacant Jan 14, 2023 <U+2014>
2 Light Heavyweight Vacant Nov 23, 2022 <U+2014>
3 Middleweight Alex Pereira Nov 12, 2022 0
4 Welterweight Leon Edwards Aug 20, 2022 0
5 Lightweight Islam Makhachev Oct 22, 2022 0
6 Featherweight Alexander Volkanovski Dec 14, 2019 4
7 Bantamweight Aljamain Sterling Mar 6, 2021 2
8 Flyweight Deiveson Figueiredo Jan 22, 2022 0
9 Flyweight Brandon Moreno (interim) Jul 30, 2022 0
8.4 POST 방식 크롤링
GET 방식은 요청 쿼리가 url에 포함되어 있으므로 매우 손쉽게 페이지 내역을 받을 수 있었습니다. 그러나 POST 방식은 요청 쿼리가 숨겨져 있으므로 이를 직접 입력해야 합니다. 앞서 살펴본 로또 당첨번호를 크롤링 해보도록 하겠습니다.
https://www.dhlottery.co.kr/gameResult.do?method=byWin
개발자도구 화면을 연 상태에서 조회를 누른 후 [gameResult.do?…] 부분을 클릭합니다. [Headers] 탭의 General 에서 Request URL은 데이터를 요청하는 서버 주소를 의미합니다.
Payload 탭의 [Form Data] 에는 요청하는 쿼리가 나타나 있습니다.
또한 당첨번호에 해당하는 부분의 html 위치를 확인해보면, num win 클래스에 위치하고 있음을 알 수 있습니다.
위의 정보를 이용해 POST 형태로 데이터를 받아보도록 하겠습니다.
library(httr)
library(rvest)
= 'https://www.dhlottery.co.kr/gameResult.do?method=byWin'
url
= POST(
data_lotto
url, body = list(
drwNo = "1009",
dwrNoList = "1009"
)
)
= data_lotto %>% read_html() data_lotto_html
- url에는 서버 주소를 입력하며, 쿼리값은 body 내에 리스트 형태로 입력한다. 요청한 쿼리, 즉 1009회 당첨결과에 해당하는 데이터가 받아집니다. 만일 다른 회차의 값이 궁금하면 1009 숫자를 다른 숫자로 입력하면 됩니다.
read_html()
함수를 통해 html 내용을 추출합니다.
이제 당첨번호에 해당하는 데이터만을 뽑아내도록 합니다.
%>%
data_lotto_html html_nodes('.num.win') %>%
html_text()
결과를 확인해보면 당첨번호와 함께 , 같은 문자들이 보입니다. 이는 띄어쓰기, 줄바꿈 등에 해당하는 태그입니다. 우리에게 남은 일은 숫자에 해당하는 부분만 뽑아내는 것이며, 클렌징 방법은 매우 많습니다.
library(stringr)
# 1
%>%
data_lotto_html html_nodes('.num.win') %>%
html_text() %>%
str_extract_all('(\\d)+') %>%
unlist()
# 2
%>%
data_lotto_html html_nodes('.num.win') %>%
html_text() %>%
str_replace_all("[\r\n\t]", " ") %>%
str_split(" ") %>%
unlist() %>%
unique() %>%
3:8] .[
9 셀레니움을 이용한 동적 크롤링
일반적인 크롤링으로는 정적인 데이터, 즉 변하지 않는 데이터만을 수집할 수 있습니다. 한 페이지 안에서 원하는 정보가 모두 드러나는 것을 정적 데이터라 합니다. 반면 입력, 클릭, 로그인 등을 통해 데이터가 바뀌는 것을 동적 데이터라 합니다. 예를 들어 네이버 지도에서 매장을 검색을 한 후 좌측에서 원하는 선택할 때 마다 이에 해당하는 내용이 뜹니다.
이는 웹페이지에서 사용자가 클릭 등과 같은 조작을 하면 AJAX 호출이 발생하여 그 결과가 페이지의 일부분에만 반영되어 변경되기 때문입니다. 즉 매장을 클릭하면 웹브라우저가 연결된 자바스크립트 코드를 실행하여 해당 매장의 상세 정보가 동일한 페이지에 동적으로 표시됩니다. 정적 페이지와 동적 페이지의 작동 방식 차이는 다음과 같습니다.
셀레니움을 이용한 경우 정적 페이지와 동적 페이지를 모두 크롤링 할 수 있다는 강력함이 있지만, 상대적으로 속도가 느립니다. 따라서 정적 페이지는 기존의 방법을 이용한 크롤링을, 동적 페이지는 셀레니움을 이용한 크롤링을 하는 것이 일반적입니다.
9.1 셀레니움 실습하기
셀레니움의 경우 R에서 세팅하기는 지나치게 번거로우므로, 파이썬에서 하는 것을 권장합니다.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time from bs4 import BeautifulSoup
webdriver.Chrome(service=Service(ChromeDriverManager().install()))
코드를 실행하면 크롬 브라우저의 버전을 탐색한 다음, 버전에 맞는 웹드라이버를 다운로드하여 해당 경로를 셀레니움에 전달해준다. 또한 크롬 창이 열리며, 좌측 상단에 'Chrome이 자동화된 테스트 소프트웨어에 의해 제어되고 있습니다.'라는 문구가 뜬다. 이제 파이썬 코드를 이용해 해당 페이지를 조작할 수 있다.
= 'https://www.naver.com/'
url driver.get(url)
1:1000] driver.page_source[
driver.get()
내에 URL 주소를 입력하면 해당 주소로 이동한다. 또한 driver.page_source
를 통해 열려있는 창의 HTML 코드를 확인할 수도 있다. 이제 네이버 메인에서 [뉴스]버튼을 누르는 동작을 실행해보자. 개발자도구 화면을 통해 확인해보면 [뉴스] 탭은 아래 HTML에 위치하고 있다.
<a href="https://news.naver.com/" class="nav" data-clk="svc.news">뉴스</a>
driver.find_element(By.LINK_TEXT , value = '뉴스').click()
브라우저 상에서 보이는 버튼, 검색창, 사진, 테이블, 동영상 등을 엘레먼트(element, 요소)라고 한다. find_element()
는 다양한 방법으로 엘레먼트에 접근하게 해주며, By.*
를 통해 어떠한 방법으로 엘레먼트에 접근할지 선언한다. LINK_TEXT의 경우 링크가 달려 있는 텍스트로 접근하며, value = '뉴스'
, 즉 뉴스라는 단어가 있는 엘레먼트로 접근한다. click()
함수는 마우스 클릭을 실행하며 결과 적으로 뉴스 탭을 클릭한 후 페이지가 이동되는 것을 확인할 수 있다. find_element()
내 접근방법 및 셀레니움의 각종 동작 제어 방법에 대해서는 나중에 다시 정리하도록 한다.
이제 뒤로가기를 실행해보도록 하자.
driver.back()
back()
은 뒤로가기를 의미하며, 기존 페이지인 네이버 메인으로 이동한다.
이제 특정 검색어를 검색하는 방법에 대해 알아보자. 먼저 검색창의 위치가 어디에 있는지 확인해보면 query라는 id와 input_text라는 class에 위치하고 있다.
driver.find_element(By.CLASS_NAME, value = 'input_text').send_keys('퀀트 투자 포트폴리오 만들기')
find_element()
내에 By.CLASS_NAME을 입력하면 클래스 명에 해당하는 엘레먼트에 접근하며, 여기서는 검색창에 접근한다. 그 후 send_keys()
내에 텍스트를 입력하면 해당 내용이 웹페이지에 입력된다. 이제 웹페이지에서 검색 버튼 해당하는 돋보기 모양을 클릭하거나 엔터키를 누르면 검색이 실행된다. 먼저 돋보기 모양의 위치를 확인해보면 search_btn id와 btn_submit 클래스에 위치하고 있다.
driver.find_element(By.CLASS_NAME, value = 'btn_submit').send_keys(Keys.ENTER)
find_element(By.CLASS_NAME, value = 'btn_submit')
를 통해 검색 버튼에 접근한다. 그 후 send_keys(Keys.ENTER)
를 입력하면 엔터키를 누르는 동작이 실행된다. 페이지를 확인해보면 검색이 실행된 후 결과를 확인할 수 있다.
이번에는 다른 단어를 검색해보도록 하자. 웹에서 기존 검색어 내용을 지운 후, 검색어를 입력하고, 버튼을 클릭해야 한다. 이를 위해 검색어 박스와 검색 버튼의 위치를 찾아보면 다음과 같다.
검색어 박스: box_window 클래스
검색 버튼: bt_search 클래스
driver.find_element(By.CLASS_NAME, value = 'box_window').clear()
driver.find_element(By.CLASS_NAME, value = 'box_window').send_keys('이현열 퀀트')
driver.find_element(By.CLASS_NAME, value = 'bt_search').click()
- 검색어 박스(box_window)에 접근한 후,
clear()
를 실행하면 모든 텍스트가 지워진다. send_keys('이현열 퀀트')
를 실행하여 새로운 검색어를 입력한다.- 검색 버튼(bt_search)에 접근한 후,
click()
을 실행하여 해당 버튼을 클릭한다.
이번에는 [VIEW] 버튼을 클릭하는 동작을 실행해보도록 한다. 기존처럼 링크나 클래스명을 통해 엘레먼트에 접근할 수도 있지만, 이번에는 XPATH를 이용해 접근해보도록 하자. XPATH란 XML 중 특정 값의 태그나 속성을 찾기 쉽게 만든 주소다. 예를 들어 윈도우 탐색기에서는 특정 폴더의 위치가 'C:\Program Files'과 같이 주소처럼 보이며 이는 윈도우의 PATH 문법이다. XML 역시 이와 동일한 개념의 XPATH가 있다. 웹페이지에서 XPATH를 찾는 법은 다음과 같다.
개발자도구 화면에서 위치를 찾고 싶은 부분에서 마우스 우클릭을 한다.
[Copy → Copy Xpath]를 선택한다.
위 과정을 통해 XPATH가 복사된다. 메모장을 확인해보면 VEW 부분의 XPATH는 다음과 같다.
//*[@id="lnb"]/div[1]/div/ul/li[2]/a
이를 이용해 해당 부분을 클릭하는 동작을 실행해보자.
driver.find_element(By.XPATH, value = '//*[@id="lnb"]/div[1]/div/ul/li[2]/a').click()
탭이 [통합] 검색이 아닌 [VIEW]로 변경되었다. 이번에는 [옵션]을 클릭한 후 정렬을 [최신순]으로 하는 동작을 실행해보자. 둘의 위치는 다음과 같다.
옵션 버튼: option_filter (클래스)
최신순 버튼: //*[@id="snb"]/div[2]/ul/li[2]/div/div/a[2] (Xpath)
코드 실행에 앞서 옵션 창이 열려있다면 [X] 버튼을 눌러 닫아주도록 한다.
옵션 창을 닫은 후 아래의 코드를 실행하도록 한다.
driver.find_element(By.CLASS_NAME, value = 'option_filter').click()
driver.find_element(By.XPATH, value = '//*[@id="snb"]/div[2]/ul/li[2]/div/div/a[2]').click()
옵션 클릭 후 최신순 버튼을 클릭하는 동작을 수행하여 검색어가 최신순으로 정렬되었다. 이제 page down 기능을 수행해보도록 하자.
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
# driver.find_element(By.TAG_NAME, value = 'body').send_keys(Keys.PAGE_DOWN)
먼저 document.body.scrollHeight
는 웹페이지의 높이를 나타내는 것으로써, window.scrollTo(0, document.body.scrollHeight);
는 웹페이지의 가장 하단까지 스크롤을 내리라는 자바스크립트 명령어다. driver.execute_script()
를 통해 해당 명령어를 실행하면 웹페이지가 아래로 스크롤이 이동된다. send_keys(Keys.PAGE_DOWN)
는 키보드의 페이지다운(PgDn) 버튼을 누르는 동작이며 이 역시 페이지가 아래로 이동시킨다.
그러나 결과를 살펴보면 스크롤이 끝까지 내려간 후 얼마간의 로딩이 있은 후에 새로운 데이터가 생성된다. 이처럼 유튜브나 인스타그램, 페이스북 등 많은 검색결과를 보여줘야 하는 경우 웹페이지 상에서 한 번에 모든 데이터를 보여주기 보다는 스크롤을 가장 아래로 위치하면 로딩을 거쳐 추가적인 결과를 보여준다. 따라서 스크롤을 한 번만 내리는 것이 아닌 모든 결과가 나올 때까지 내리는 동작을 실행해야 한다.
= driver.execute_script('return document.body.scrollHeight')
prev_height
while True:
driver.execute_script('window.scrollTo(0, document.body.scrollHeight);')
time.sleep(2)
= driver.execute_script('return document.body.scrollHeight')
curr_height if curr_height == prev_height:
break
= curr_height prev_height
return document.body.scrollHeight
은 현재의 창 높이는 반환하는 자바스크립트 명령어이며, 이를 prev_height에 저장한다.- while문을 통해 반복문을 실행한다.
- 셀레니움을 통해 페이지의 최하단으로 스크롤을 내린다.
- 페이지가 로딩되는 시간을 기다리기 위해 2초간 슬립을 준다.
- curr_height에 현재 창 높이를 저장한다.
- curr_height와 prev_height가 동일하다는 의미는 페이지가 끝까지 내려왔다는 의미이다. 따라서 이 경우
break
를 통해 while문을 멈추며, 그렇지 않을 경우 다시 스크롤을 내리는 동작을 반복한다. - prev_height에 새로운 창 높이를 입력한다.
이제 모든 검색 결과가 나타났으면 이전 장에서 살펴보았던 정적 크롤링을 통해 데이터 수집이 가능하다. 제목 부분을 확인해보면 api_txt_lines total_tit _cross_trigger 클래스에 위치하고 있으며, 이를 통해 모든 제목을 크롤링해보자.
= BeautifulSoup(driver.page_source, 'lxml')
html = html.find_all(class_ = 'api_txt_lines total_tit _cross_trigger')
txt = [i.get_text() for i in txt]
txt_list
0:10] txt_list[
driver.page_source
를 통해 현재 웹페이지의 HTML 정보를 가져올 수 있으며, 이를 BeautifulSoup 객체로 만들어준다.find_all()
함수를 통해 제목 부분에 위치하는 데이터를 모두 불러온다.for문을 통해 텍스트만 추출한다.
이처럼 동적 페이지의 경우도 셀레니움을 통해 웹페이지를 제어한 후 BeautifulSoup
패키지를 사용해 원하는 부분을 추출하면 얼마든지 크롤링이 가능하다.
driver.quit()