티스토리 뷰
정적 데이터는 html이 불러올 때마다 바뀌어진다.
html 코드의 경우에는 html 코드는 데이터 프레임으로 바꾸기가 어렵다.
그래서 BeautifulSoup(뷰티풀 숩)이라는 걸로 DF로 바꿔준다.
이걸 위해서 html,css-selector를 배워야 한다.
동적 페이지와 정적 페이지로 안 되는 경우에는 셀레니움을 쓴다.
파이썬으로 브라우저를 조종한다.
셀레니움은 사실 크롤링을 위해서 만들어진 건 아니다.
셀레니움이 만들어진 이유는 화면의 내용을 바꿀 때마다 동작 테스트를 해봐야 하는데, 자동으로 해주는 웹 브라우저 테스팅 용도로 만들어졌다.
우클릭 -> 페이지 소스 보기
html 코드에서 해당 엘리먼트를 선택해서 문자열 데이터를 가져와야 한다.
html의 문법
- Document : 한페이지를 나타내는 단위다. 한 페이지의 전체 코드.
- Element : 하나의 레이아웃을 나타내는 단위 : 시작태그, 끝태그, 텍스트로 구성(엘리먼트는 계층 구조)
- Tag : 엘리먼트의 종류를 정의 : 시작태그(속성값), 끝태그 - <a> </a>
- Attribute : 시작태그에서 태그의 특정 기능을 하는 값
- id : 웹 페이지에서 유일한 값(div 다음에 id가 붙는다. id 값은 한번만 사용하는 게 원칙)
- class : 동일한 여러개의 값 사용 가능 : element를 그룹핑 할때 사용(이것도 div 다음에 붙는다. 여러 개를 사용할 수 있고, 엘리먼트를 그루핑할 때 주로 사용, )
- attr : id와 class를 제외한 나머지 속성들s
- href = 클릭했을 때 이동할 페이지 url
- 속성값에 따라서 어떤 역할인지가 달라진다.
- Text : 시작태그와 끝태그 사이에 있는 문자열 (시작태그 중간에 들어가는 문자열)
- 우리가 하는 건, 이 text 사이에 있는 문자들을 가져오는 것이다.
- 엘리먼트는 서로 계층적 구조를 가질수 있습니다.
개발자 도구에서 위 아이콘을 클릭하면, 해당 부분의 html을 가져올 수 있다.
%%html
html 코드를 해석해서 출력을 해준다.
<div class='wrap'>
</div> #하나의 엘리먼트
%%를 하면 스페셜 커맨드
spacial command : ipython에서 제공하는 특별한 기능을 가지는 명령어
# %(하나의 명령어로 실행), %%(셀 전체를 명령어로 실행할 때)
data1, data2 =1, 'py'
%whos = 현재 선언되어 있는 명령어 조회 가능
%ls = 현재 디렉토리에 있는 파일 목록
엘리먼트 안에 버튼을 만든다.
%%html
html 코드를 해석해서 출력을 해준다.
<div id='contents' class='wrap'>
<button class='btn no1' type='button' value='1'>Btn1</button>
<button class='btn no2' type='button' value='2'>Btn2</button>
</div> #하나의 엘리먼트
class는 공백으로 데이터를 구별해준다.
btn 과 no1은 다른 클래스이다.
엘리먼트의 선택
1.id로 선택,
2. 클래스로 선택
3. 아이디와 클래스로 선택
html 전체에 대한 구조
- DOCTYPE
- 문서의 종류를 선언하는 태그 입니다.
- html
- head
- meta
- 웹페이지에 대한 정보를 넣습니다.
- title
- 웹페이지의 제목 정보를 넣습니다.
- meta
- body
- 화면을 구성하는 엘리먼트가 옵니다.
- head
<!-- HTML 웹문서의 기본적인 구조 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
</html>
head는 대표 이미지나 디스크립션
페이지에 대한 정보
body에 데이터가 들어간다.
%%html
<h1>Title</h1>
h 태그 - 숫자가 커질수록 문자의 크기가 줄어든다
h1~h6까지
p 태그
한 줄의 문자열을 출력
%%html
<p> </p>
%%html
<span> </span>
span
한 블럭의 문자열을 출력
pre - 자주 사용되지는 않는다.
<pre>
html1
html2
</pre>
<code>
</code>
중간에 있는 공백문자까지 포함
code는 코드 형태로 데이터를 출력
문자 이외의 Html 태그
div - 레이아웃을 나타내는 태그이다.
가장 많이 사용하는 태그이다.
%%html
<div>
<div>div1 </div>
<div>div2 </div>
</div>
<div>div3</div>
계층 구분이 가능하다.
table - 로우와 컬럼이 있는 테이블 모양을 나타낼 때 사용한다.
%%html
<table>
<caption>테이블제목</caption>
<thead> #테이블 헤드, 판다스에서 컬럼 이름을 쓸 때 사용
<tr>
<th>table1 </th>
<th>table1 </th>
</tr>
</thead>
<tbody>
<tr>
<th>data1</th>
<th>data2</th>
</tr>
<tr>
<th>data3</th>
<th>data4</th>
</tr>
</tbody>
</table>
결과 출력
테이블제목
table1 table1
data1 data2
data3 data4
이렇게 df의 구조처럼 만들어준다.
thead가 없는 경우도 있다.
브라우저가 보완을 해서 돌려주는 경우도 있기 때문.
ul,li - 가장 많이 사용하는 태그
목록을 만들 때 사용한다.
리스트를 나타낸다
%%html
<ul>
<li>data1</li>
<li>data2</li>
</ul>
- data1
- data2
a - 어떤 페이지로 이동시킬 때 나타내는 태그
a href='주소' 형태로 쓴다.
하이퍼링크
%%html
<a href='https://kt.com' target='_blank'>Move KT</a>
target에 _blank가 들어가면 새 탭을 열어서 링크로 이동한다.
image 태그
이미지 상품을 수집할 떄 필요하다.
%%html
<img src='https://cfm.kt.com/images/v2/layout/gnb-ktlogo.png ' alt='kt logo'></img>
src에 해당 이미지의 url을 출력해야 한다.
alt에는 그 이미지가 어떤 이미지인지 글자로 표현
해당하는 url의 이미지 파일을 다운받는다.
responce에 이미지 데이터가 다 존재한다.
iframe
url 1,url2가 있으면
url 2 페이지에서 url1번을 특정 영역에 띄운다.
iframe으로 되어 있으면 크롤링이 안되는 경우가 생긴다.
%%html
<iframe src='https://kt.com' width='100%' height='200px'></iframe>
대표적인 곳이 네이버 카페.
네이버 카페에서는 iframe을 가져온 게 게시글이라서, 게시글을 불러오는 다른 url을 사용해야 한다.
html의 input
text - 문자열을 입력받을 때 사용하는 input 타입
%%html
<input type='text' placeholder='이메일'></input>
문자열을 입력받을 수 있는 칸이 생성
placeholder는 설명을 빈칸에 넣어줄 수 있다.
password - 비밀번호를 입력받을 때 사용하는 input 타입
입력하면 *로 입력된다.
%%html
<input type='password' placeholder='비밀번호'></input>
radio - 여러 개의 버튼 중 하나만 눌리는 타입
name으로 하나의 그룹을 구분한다.
%%html
<input type='radio' name='no1'>btn1</input>
<input type='radio' name='no1'>btn2</input>
<input type='radio' name='no1'>btn3</input>
<input type='radio' name='no1'>btn4</input>
<input type='radio' name='no1'>btn5</input>
checkbox - 여러개의 버튼이 체크된다.
%%html
<input type='checkbox' name='no1'>btn1</input>
<input type='checkbox' name='no1'>btn2</input>
select,option
옵션선택을 할 수 있는 드랍다운 태그이다.
%%html
<select>
<option>opt1</option>
<option>opt2</option>
<option>opt3</option>
</select>
브라우저를 자동화 할때, 이런 엘리먼트를 선택되도록 컨트롤 할 수 있다.
textarea
여러줄 입력이 가능한 태그이다.
%%html
<textarea row='4' cols='50'></textarea>
버튼
<div id='contents' class='wrap'>
<button class='btn no1' type='button' value='1'>Btn1</button>
<button class='btn no2' type='button' value='2'>Btn2</button>
</div> #하나의 엘리먼트
대표적인 문법
해커가 된 기분을 느낄 수 있는 방법 - 해커 타이퍼
css -selector
css 셀렉터는 css 스타일을 적용시킬 html 엘리먼트를 찾기 위한 방법이다.
4가지 방법으로 선택
1.엘리먼트를 이용하여 선택
div를 사용하면 가장 위에 있는 sdad이 선택된다.
div가 여러개 있으면 가장 앞에 있는 게 선택된다.
<div> sdad</div>
<p> dsa </p>
<span> sdsad</span>
element가 선택되어서 뒤에
2. id selector
아이디를 이용하여 선택할 때 사용
dom.select_one(#ds1)
<p id="ds1">dss1</p>
<p id="ds2">dss2</p>
<p id="ds3">dss3</p>
만약에 _one을 지우고, p를 선택하면, 전부 다 p 태그이기 때문에 여러개가 선택이 된다.
여러개를 쓸 때는 select만, 한개면 select_one을 쓴다.
3. class selector
# 대신에 .을 사용한다.
dom.select_one(.ds1)
<p class="ds1">dss1</p>
<p class="ds2">dss2</p>
<p class="ds2">dss3</p>
만약에 두개 다 선택하고 싶다면, ds1,ds2로 구분해서 여러 개 선택 가능하다.
p 태그로 class가 ds1
span 대크로 class가 ds1 이렇게 되어 있다면, .ds1하면 둘 다 선택
그러면 p만 선택하고 싶으면 p.ds1으로 넣어주면 된다.
#attribute를 이용하여 선택하는 방법
data2 선택 : [value='no2'] #대괄호 안에 attr을 넣어주면 된다.
[data='no1']도 똑같이 가능
<p value='no1', data='no1'>data1</p>
<p value='no2'>data2</p>
element = dom.select_one(.cjt_s)
element.text
하면 해당 클래스 안에 있는 text를 불러온다.
4. not selector
전체를 선택한 가운데 ds2
.ds1,.ds3,.ds4보다는,
.ds : not(.ds2)로 표현한다.
즉, 제외하고 싶은 걸 써주면 된다.
5. first-child selector, 6. last-child selector
많이 사용이 안됨
7. nth-child selector
<div class="wrap">
<span class="ds">ds2</span>
<p class="ds ds1">ds3</p>
<p class="ds ds2">ds4</p>
<p class="ds ds3">ds5</p>
<p class="ds ds4">ds6</p>
<p class="ds ds5">ds7</p>
</div>
만약에 span이 없다고 하면,
.ds:nth-chlid(3) 하면 ds5가 선택된다.
그런데 span이 있다면,
.ds:nth-chlid(3)을 했을 때 ds4가 선택된다.
왜냐하면, span도 ds를 가지기 때문이다.
p : nth-child(3) 이면?
이 경우에도 ds4다.
착각하기 쉬운 게, nth-child(3)을 먼저 체크를 하고, 그 후에 앞이 p 태그인지 아닌지를 선택한다.
그래서 3번째가 먼저 조건이 맞는지 확인하고
조건이 맞지 않으면 아예 선택을 아무것도 안 해준다.
8. 모든 하위 depth(공백) selector
모든 하위 엘리먼트를 선택
select_one(.contants h1)
공백문자를 넣으면 모든 하위 엘리먼트를 선택한다는 뜻이다.
그래서 contants의 h1을 모두 선택한다.
<div class="contants">
<h1>inner_1</h1>
<div class="txt">
<h1>inner_2</h1>
</div>
</div>
inner_1, inner_2가 모두 선택됨.
그래서 >를 넣으면
9. 바로 아래 depth(>) selector
select_one(.contants > h1)
바로 아래 엘리먼트를 선택한다.
inner_1만 선택된다.
계층적 구조를 활용하려면
strong 태그에 sa라고 하면
strong.sa로 하면 되고,
계층적 구조로 선택하려면
strong sa div.sa > a> strong
이런식으로 하면 계층적으로 선택할 수 있다.
.sa_list > li:nth-child(2) strong #두번째 꺼 중에 li 태그가 있는 첫번째 태그이고, 공백으로 strong을 구분했으므로 그 하위단계에 있는 모든 strong을 선택한다.
정리
하나의 엘리먼트 선택
tag : div
id : #data
class : .data
attr : [value='no1']
.data:not(.data1) : data 클래스 전체에서 data1 클래스 엘리먼트만 제외
.data:nth-child(3) : 3번째 하위 엘리먼트에서 .data 클래스를 가진 엘리먼트를 선택한다.
계층적 구조에서는 공백 : #data .btn : data 아이디 아래에 있는 모든 btn 클래스 엘리먼트 선택
> : #data> .btn : data 아이디 한 단계 아래의 btn 클래스 엘리먼트를 선택한다.
.d1 , .d2 : 콤마로 구분하면 d1, d2 클래스 둘 다 선택이 된다.
사실 몰라도 엘리먼트 선택은 할 수 있다.
엘리먼트 선택해서, 개발자 도구에서 copy selector하면 selector를 만들어준다.
그런데 100%가 아니라서 잘못된 경우에는 규칙을 알고 있어야 수정이 가능하다.
정적 웹페이지 데이터 수집
import requests
from bs4 import BeautifulSoup #html 포맷의 데이터를 css-selector를 이용하여 필요한 데이터를 추출
연관검색어를 어디서 가져오는 확인
개발자 도구의 네트워크 탭 , 새로고침 후 -> all 설정에서 가장 위에 있는 걸 선택
search에서 response가 맞는지를 보려면
ctrl+f로 해서 해당 연관검색어를 보면 그 안에 데이터가 있다는 걸 확인할 수 있다.
url의 쿼리를 제거해도 되므로, 최대한 간소화된 쿼리를 써도 된다.
이게 아니라
query = '삼성전자'
url = f'https://search.naver.com/search.naver?query={query}'
url
이렇게 써도 가능
response = requests.get(url)
response
파싱을 위해서 text를 뽑아준다.
4. bs object를 css selector
dom = BeautifulSoup(response.text, 'html.parser')
#response.text는 받아온 html 전체
type(dom)
>bs4.BeautifulSoup
dom.select() : 엘리먼트 여러개 선택 : 결과가 리스트로 나온다.
dom.select_one() : 엘리먼트 한개 선택 : 결과가 태그 객체 한개가 출력이 된다.
연관검색어를 보면 엘리먼트가 10개이다.
select를 써줘야 한다.
그래서
elements = dom.select('.item')
len(elements)
>129
css 셀렉터가 맞는지 확인하기 위해서 len으로 개수를 확인한다.
원하는 것보다 많다.
그래서 위에 있는 클래스를 복사해서 붙여준다.
elements = dom.select('.lst_related_srch > .item')
len(elements)
element = elements[0] #확인
element.text.strip() #개행문자 제거
keywords = [element.text.strip() for element in elements]
keywords
다음과 같이 하면, 연관검색어의 키워드들을 불러올 수 있다.
g마켓 베스트 상품 200개의 데이터 수집
+상품의 이미지 200개 다운로드
import pandas as pd
import requests
from bs4 import BeautifulSoup #html 포맷의 데이터를 css-selector를 이용하여 필요한 데이터를 추출
개발자도구, network에서 새로고침.
가장 위에 있는 best.
해당 url을 불러온다.
url = 'https://www.gmarket.co.kr/n/best'
response = requests.get(url)
response
똑같이 beautifulsoup으로 바꿔준다.
dom = BeautifulSoup(response.text, 'html.parser')
200개의 아이템을 선택하려면 css selector가 필요하다.
item을 클릭해보면 200개의 엘리먼트로 있는 것을 확인할 수 있다.
이번에는 오른쪽 버튼을 클릭하고, copy>copy selector
#gBestWrap > div.best-list > ul > li:nth-child(1)
li:nth-child가 나오는데, 전체를 선택하려면 li만 선택하여야 하므로, nth-child는 지워준다.
ele = dom.select('#gBestWrap > div.best-list > ul > li')
len(ele)
<li class="first"><span class="box__corners-id"></span><p class="no1">1</p><div class="thumb"><a aria-hidden="true" href="http://item.gmarket.co.kr/Item?goodscode=2498713345&ver=20240308" tabindex="-1"><img alt="[출판사 블루래빗]LEGO 사이언스 스타트 2종(판타스틱 머신 + 기어 봇) 선택구매" class="image__lazy" decoding="async" height="210" loading="lazy" src="//gdimg.gmarket.co.kr/2498713345/still/300?ver=1680482283" width="210"/></a></div><a class="itemname" href="http://item.gmarket.co.kr/Item?goodscode=2498713345&ver=20240308">[출판사 블루래빗]LEGO 사이언스 스타트 2종(판타스틱 머신 + 기어 봇) 선택구매</a><div class="item_price"><div class="o-price"><span class="for-a11y">정가</span><span>29,000<!-- -->원</span></div><div class="s-price"><span class="for-a11y">할인가</span><strong><span>13,800<!-- -->원</span></strong> <span><span class="sale"></span><em>52<!-- -->%</em></span></div><div class="icon"></div></div></li>
제목, 링크, 이미지를 불러온다.
element 에 0번째를 넣어주고,
필요한 데이터를 뽑아온다.
element = ele[0]
data = {
'title' : element.select_one('.itemname').text,
'link' : element.select_one('.itemname').get('href'), #속성값을 가져오기
'img' : 'https:'+ element.select_one('.image__lazy').get('src'), #이미지 로드가 있는데 lazy가 추가가 되었다.
#src만 가져온다.
's-price' : element.select_one('.s-price').text.split(' ')[0][3:-1].replace(',',''),
}
data
안에 있는 데이터만 출력하기 위해서 각각을 선택해준다.
위에 있는 코드를 for로 각각 돌리면 된다.
items =[]
for element in ele:
data = {
'title' : element.select_one('.itemname').text,
'link' : element.select_one('.itemname').get('href'), #속성값을 가져오기
'img' : 'https:'+ element.select_one('.image__lazy').get('src'), #이미지 로드가 있는데 lazy가 추가가 되었다.
#src만 가져온다.
's-price' : element.select_one('.s-price').text.split(' ')[0][3:-1].replace(',',''),
}
items.append(data)
items
그 후에 데이터 프레임으로 만들고, 엑셀로 만들면 된다.
df = pd.DataFrame(items)
df.tail(2)
df.to_excel('g_items.xlsx', index=False, engine='openpyxl') #인코딩
#실행이 안되서 찾아봐야 함
이미지 데이터 다운로드
이미지 데이터를 위해 디렉토리를 먼저 생성
import os
os.makedirs('data')#디렉토리 만들기
os.listdir('data') #파일 리스트를 출력
#디렉토리 삭제
import shutil
shutil.rmtree('data')
%ls
파이썬 모듈 파일 안에 shutil이 있는데 실행하면 디렉토리 삭제 될 수 있으니 조심
img_link = df.loc[0, 'img']
img_link
response = requests.get(img_link)
response
이렇게 하면 url이 이미지를 가져오는 것.
그래서 이 이미지 파일을 저장한다.
with open('data/test.png', 'wb') as file : #data에 test.png로 저장한다. 'wb' = 이진으로
file.write(response.content) #response에 있는 컨텐츠를
이 파일이 잘 다운로드 되었는지 확인
pillow 패키지
from PIL import Image as pil
pil.open('data/test.png')
이제 for 문만 잘 쓰면 된다.
iterrows를 쓰면 분리 가능.
for idx,data in df[:5].iterrows(): #0이라고 하는 인덱스 값이 idx에, 나머지가 data에 들어간다.
print(idx,data['img'])
response = requests.get(data['img'])
filename = f'data/{idx}.png'
with open(filename, 'wb') as file:
file.write(response.content)
os.listdir('data') #파일 리스트를 출력
pil.open('data/test.png')
리스트하고 확인
셀레니움
조건
크롬 브라우저 설치되어 있는 상태여야 한다.
크롬 브라우저 드라이버를 다운로드해야 한다.
셀레니움 패키지를 설치해야 한다.
https://googlechromelabs.github.io/chrome-for-testing/
stable에서 url 복사해서 붙여넣기 후 다운로드
실행하는 게 아니라, 크롬 드라이버 exe 파일을 쥬피터 노트북과 동일한 디렉토리에 넣어준다.
그 다음에 셀레니움 패키지 설치
!pip install selenium
그 후에
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
이렇게 하면, 자동화된 소프트웨어에 의해 제어되는 크롬 창이 열린다.
# 페이지 이동
driver.get("https://daum.net")
# 브라우져 사이즈 조절
driver.set_window_size(200, 600)
# alert 다루기 - 자바스크립트 코드 실행해서 alert 메세지를 제거해주기(확인버튼을 누름)
driver.execute_script("alert('hello selenium!!!');")
alert = driver.switch_to.alert
alert.accept()
# 팝업 닫기 버튼 클릭
driver.find_element(By.CSS_SELECTOR, '.link_close').click()
# 브라우저 스크롤 조절
driver.execute_script("window.scrollTo(200, 300);")
# 브라우저 사이즈 조절
driver.set_window_size(800, 800)
# 문자열 입력
driver.find_element(By.CSS_SELECTOR, "#q").send_keys("셀레니움")
# 문자열 입력
driver.find_element(By.CSS_SELECTOR, "#q").clear()
# 검색 버튼 클릭
driver.find_element(By.CSS_SELECTOR, '.btn_ksearch').click()
# 브라우져 종료
driver.quit()
중요한 것은 브라우저를 컨트롤 할 수 있다는 사실을 아는 것이다.
셀레니움 브라우저 건드리면 꺼지므로 되도록 건드리지 말 것.
텍스트 데이터 가져오기
driver.get("https://www.ted.com/talks")
# CSS Selector를 이용하여 HTML 태그와 태그 사이의 text 데이터 가져오기
driver.find_element(By.CSS_SELECTOR, ".text-textPrimary-onLight").text
# 제목 데이터 가져오기
selector = '.container > section > .relative > div:nth-child(2) > div > div'
contents = driver.find_elements(By.CSS_SELECTOR, selector)
len(contents)
# 가장 처음 텍스트 데이터 가져오기
contents[0].find_element(By.CSS_SELECTOR, '.text-textPrimary-onLight').text
# 전체 제목 데이터 가져오기
titles = []
for content in contents:
title = content.find_element(By.CSS_SELECTOR, '.text-textPrimary-onLight').text
titles.append(title)
titles[:3], len(titles) #앞에 있는 거 3개만 출력
# 링크 데이터 크롤링 (속성(attribute)값 가져오는 방법)
links = []
selector = '[data-testid="TalkGrid Talk Item"]'
for content in contents:
link = content.find_element(By.CSS_SELECTOR, selector).get_attribute("href")#get 대신에 attribute를 해줬다.
links.append(link)
links[-3:]
driver.quit() #100메가 반환
가장 최후의 수단
Headless
브라우저를 띄우지 않고 메모리상에서만 올려서 크롤링
windows가 지원되지 않는 환경에서 사용하는 용도로 쓴다.
크롬 버전 60 이상부터 지원
# 현재 사용중인 크롬 버전 확인
driver = webdriver.Chrome()
version = driver.capabilities["browserVersion"]
print(version)
driver.quit()
# headless 사용
options = webdriver.ChromeOptions()
options.add_argument('headless')
driver = webdriver.Chrome(options=options)
driver.get("https://www.ted.com/talks")
text = driver.find_element(By.CSS_SELECTOR, ".text-textPrimary-onLight").text
driver.quit()
print(text)
크롤링 정책
robots.txt : 크롤링 정책을 설명한 페이지
과도한 크롤링으로 서비스에 영향을 주었을 때 법적 문제가 있을 수 있다.
사람인 잡코리아 :
api 사용 > robots.txt > 서비스에 피해가 가지 않는 선에서 수집
서비스 피해,지적재산권,서비스과부하,데이터 사용표준
robots.txt를 보면, disallow를 빼고는 수집이 안된다.
즉, disallow만 가능하고, 나머지는 못 가져온다