금번 시간은 html 을 쉽게 파싱(parsing) 할 수 있는, BeautifulSoup 이라는 모듈을 사용하는 방법에 대해서 살펴보고, 지난 시간에 API 를 사용해 호출했던 WhoIS 사이트를 웹 호출을 통해 결과를 받아 파싱하여 결과를 얻는 부분을 간단히 시연하면서 파싱을 할때 생기는 현실적인 이슈들에 대해 얘기를 하려 한다. 크롤링 이라고 부르기엔 좀 조촐한 범위라서 파싱이라고 주제를 정했다.
[목차]
0. 왜 파이썬 공부에 구글을 이용하는게 좋은가?
1. 언어를 바라보는 방법. 파이썬을 어떻게 바라봐야 할까?
2. 파이썬 설치와 환경, 버전 선택 하기의 이유.
3. 만들고자 하는 기능을 모르는 조각으로 나눠 조사해 보기
4. 데이터 베이스에서 내용 가져와 출력하기
5. 암호화 모듈을 이용해 암복호화 해보기
6. 퍼즐 조각들을 합쳐보기
7. 엑셀 파일 사용해 보기 -> 부록 : fuction 을 이용해서, 코드 정리해 보기
8. 정규표현식을 왜 사용해야 할까? 언어속의 미니 언어 정규표현식 살펴보기
9. 입력과 결과를 GUI 화면과 연결해 보기
10. Whois API 이용해 보기
11. 웹페이지 호출해 내용 파싱 하기(BeautifulSoup 그리고 한계)
12. 자동화 - 웹 자동화(with Selenium)
13. 자동화 - 윈도우즈 GUI 자동화(with pywinauto)
14. 자동화 - 작업 자동화
15. 수학 라이브러리 살펴보기
16. 그래픽 라이브러리 살펴보기
17. 머신러닝에서의 파이썬의 역활
18. 웹 프로그래밍 - Legacy Web
19. 웹 프로그래밍 - Flask 살펴보기(feat. d3.js)
20. 웹 프로그래밍 - Django 살펴보기
21. 정리 - 이런저런 이야기
[들어가면서]
먼저 파싱이란 어떤 작업일까? 원래 언어학에서 먼저 나온 용어 같고, 간단히 얘기하면 문장의 구조를 분석해서, 문법 요소를 뽑아서 구조를 체계화 한다고 할 수 있다. 그렇게 체계화 하면 무엇이 좋아질까? 특히 컴퓨터 분야에서는 주어진 문구들을 체계화 하고 나면 해당 문법요소에 따라서, 원하는 내용들을 추출 할 수 도 있게 된다. 뭐 잘은 모르지만 자동 번역이나, 음성 인식 등 에서도 그렇게 문장 요소를 분류하는 게 먼저 일 거 같다.
https://ko.wikipedia.org/wiki/%EA%B5%AC%EB%AC%B8_%EB%B6%84%EC%84%9D
자 그럼 html 을 파싱하려면 어떻게 해야 할까? 만약 beautifulsoup 같은 파서가 없다면, 아마 정규 표현식 같은 방법들을 별 수 없이 이용해야 할 것 이다. 그러기 위해 우리는 우선 html 이 어떤 문법 요소를 가지고 있는지 이해해야 한다. 어떤 태그들을 가지고 있고, 어떤 속성을 가지고 있고, 각 태그와 속성의 위치, 상대적 관계, 문서에서의 역활 등에 대해서 이해해야 한다. 그래야 html 문서안에 있는 어떤 필요한 정보 값에 대해서, 그러한 외부 요소들의 관계를 이용해서 콕 지정해서 가져올 수 있을 것이다.
해당 부분은 beautifulsoup 같은 파서를 이용한다고 해도 마찬가지다. 기본적으로 html 구조에 대한 지식이 있어야 브라우저의 개발자 도구 같은 유틸리티 들을 이용해서 살펴 보면서 페이지도 분석하고, 원하는 값을 가져오는 코드도 만들 수 있다. 결국 예전에 얘기했던 프로그램의 외적 요소를 사용하기 쉽게 해주는 모듈들은 많지만, 해당 모듈들을 잘 사용하기 위해서 해당 외적 요소에 대해서 포괄적인 이해를 하는게 유리하다는 것이다.
html 공부에 대해서는 아래 링크의 w3school 같은데서, 하나씩 항목들을 살펴보거나, head first html(헤드 퍼스트 html) 같은 쉽게 설명해 주는 책들을 한 권 정도 보게 되면 해당 모듈을 사용하는 기본 소양은 갖추게 된다고 볼 수 있다. 이 후는 실제 여러 다양한 웹 페이지를 분석해 살펴보거나, 만들어 보는 것이(특히 만들면서 생각한 데로 구현이 안 되서 헤메다 보면, 좀 더 깊이 이해되고 잘 잊혀지지 않게 된다) 최선의 방법이다. 많이 경험하면 할수록 모르면 10분 걸리는 분석이 1분 만에 끝날 수도 있으며, 해당 지식은 앞으로 만나는 어떤 언어, 어떤 모듈에도 비슷한 주제와 형태라면 마찬가지로 사용할 수 있다. 또한 xml 같이 비슷하지만 좀더 엄격한 언어를 공부하거나, javascript 같이 웹의 이런 저런 요소들에 모두 문어발 관계를 가지는 언어를 배울 때 허들을 낮추게 해준다.
https://www.w3schools.com/html/default.asp
[Beautiful Soup]
그럼 BeatutifulSoup 에 대해서 어떻게 접근하면 되는지 간단한 브리핑과 샘플을 실행해 보도록 하겠다. 먼저 'soup' 이란 영어 단어의 뜻 대로 '스프'는 아니고 아마 'soap' 의 패러디 같은게 아닌가 싶다--; 확실히는 모르지만 soap은 http 프로토콜을 이용해서 원격에 있는 서비스를 호출하여 사용하자는 규격이다. 전송을 위한 데이터는 xml 이나 그 사촌 정도인 html 이 사용된다. 그래서 이 모듈을 만든 사람은 html 이나 xml 을 아름답게 잘 파싱하여 원하는 결과를 만들 수 있다고 해서 이름을 붙이지 않았을까 추측한다. 여기서는 순수 웹과 연관된 html 쪽만 살펴본다. 뭐 전문적인 수준의 얘기는 아니니 전체적인 이야기의 맥락만을 이해하고, 위에 얘기한 책이나, 여러 웹 자료들에서 자세한 내용들은 살펴보길 바란다.
https://ko.wikipedia.org/wiki/SOAP
html 이란 HyperText Markup Language 의 약자이다. 이것은 링크를 가진 태그 형식의 언어라는 의미가 된다. 그럼 html 의 문법 요소는 무엇 일까? 크게 Element(tag), Attribute(id, class, name) 로 나눌 수 있다. 먼저 엘리먼트는 html 모든 태그를 얘기한다고 보면 될 듯 하다. attribute(속성)은 여러가지가 있는데 태그안에 들어간 어려가지 요소로 id, class, name, height, width 등으로 태그의 identity 나 표시 방법을 나타내 준다고 보면 된다.
처음 html 을 쓰다보면 id, class, name 이 조금 헷깔리는데(보통 자바스크립트나, 웹 프로그래밍, css 를 다룰때야 비로소 고민된긴 하지만..), id 는 고유의 태그를 표시하는 용도로 써서 같은 p 태그라도 id="myp" 같이 지정하면 고유한 p 태그가 되는 것이다(어린왕자의 장미라 그럴까?). 중복되는 경우 javascript 등에서 접근시 에러난다곤 하는데(확인 안 해봤다), 중복 된다고 html 의 관대한 특성상 페이지에서 당장 에러가 나는 경우는 없다(beautifulsoup 에서도 id 를 지정할때 중복되도 에러가 안 나고 그냥 다른 attribute 처럼 모두 결과로 가져온다). 이러한 값들은 나중에 beautifulsoup 이나 javascript, css 나아가 여러가지 자동화 라이브러리에서도 사용하게 된다.
name 이 좀 id 와 개념이 헷깔리긴 하나(누가 이름을 지었는지...), 보통 name 은 form 태그 안에서 각 폼 요소들의 종류를 가리킬때 쓰며, 복수 지정이 가능하다(예를 들어 radio 버튼 한 그룹은 type=radio 이고 name 이 myradio 같이 같을 수 있다. 그럼 브라우저나, 자바스크립트는 해당 구조를 해석해서 동일한 그룹의 라디오 버튼이라는 것을 판단해서, 클릭 시 같은 동작시 서로 배타적으로 선택되게 만든다). class 는 보통 css(Cascading Style Sheets) 에서 많이 사용하게 되는데, 같은 이름의 class 를 가진 td 태그를 css 에 지정해서 자동으로 같은 배경색으로 표시한다는지 하는 식으로 디자인 적으로 많이 사용되게 된다. 뭐 하지만 프로그래머가 얼마나 해당 개념들을 헷깔려 하느냐에 따라서, 두서 없이 섞어 쓰는 경우도 종종 있는 듯 하다. id, name, class 의 차이는 아래 링크를 참고한다.
https://css-tricks.com/the-difference-between-id-and-class/
http://stackoverflow.com/questions/1397592/difference-between-id-and-name-attributes-in-html
이러한 html 의 많은 요소는 DOM(document object model) 이라는 체계로 표현되고 관리된다. 이것은 html 이나 xml 을 트리형태로 구성하고, 해당 요소들에 대해서 프로그래밍 적으로 접근하거나 조작하는 것을 가능하게 해준다. 우리가 보는 html 문서는 dom 형태로 정리되어 javascript 나 beautifulsoup 같은 모듈에서 접근 할수 있다고 봐도 무방하다.
https://en.wikipedia.org/wiki/Document_Object_Model
그럼 beautifulsoup 을 가지고 페이지에서 원하는 개체를 어떻게 찾을까? 구글에서 'beautifulsoup doc' 을 찾아보면 아래의 공식 메뉴얼이 나오는데, 해당 페이지내에 설명된 방식이 사실상 전부라고 보면 된다.
https://www.crummy.com/software/BeautifulSoup/bs4/doc/
우선 아까 얘기한 특정 요소들로 찾을 수 있다. id, class, name, tag name 등을 기준으로 찾아 배열에 담아서 이용할수도 있고(findall), css selector 란 방식으로도 찾을 수 있다(search). 자세한 내용은 아래 링크를 참고 한다.
https://www.crummy.com/software/BeautifulSoup/bs4/doc/#find-all
https://www.crummy.com/software/BeautifulSoup/bs4/doc/#searching-by-css-class
find_all 은 find_all('a') (<a> 태그를 전부 찾아라), find_all(id="mylink") (id가 mylink 인 태그를 찾아라) 같이 명시적으로 접근하는 편이고, css select 는 css 라는 html 디자인을 나타내는 스타일 언어의 개체 접근 방식을 사용해서 select('a'), select('#mylink') 같이 약간 약어 같이 쓴다.(정규 표현식 필이 조금 난다). 그래서 css selector 형태의 경우 좀더 간결한 듯 하다. 해당 부분은 밑의 링크를 참조하길 바란다.
https://www.w3schools.com/cssref/css_selectors.asp
또한 상대적 위치 또한 이용할 수 있다. 현재 id 가 'mytd' 인 <td> 태그의 부모 역활을 하는 <tr> 태그를 찾는다던지, 특정 <tr> 태그 바로 밑에 있는 동일한 레벨의 <tr> 태그(sibling)를 찾을 수도 있다. 이런 것들은 beautifulsoup 모듈과 html 코드에 대한 전체적인 개념이 잡혔으면, 메뉴얼에서 필요한 부분을 훝어 보면서 어떤 접근 방식이 가능한지 보는게 좋다.(자바스크립트나 css 의 접근 방식과 많이 비슷하다. 어차피 비슷한 목적으로 접근하기 때문에 말이다)
[Beautiful Soup 으로 샘플 파싱 해보기]
그럼 예제를 하나 보면서 실제 코드를 만들어 보도록 하자. 실제 웹에 있는 페이지 하나를 봐도 좋지만, 그런 페이지들은 보통 복잡한 요소들이 막 믹스되어 있으므로, 주제에 집중을 위해서 하나의 html 코드를 만들어서 원하는 요소를 선택하는 부분을 시연하려 한다. 파일을 읽어 올까도 했지만, beaufilsoup 샘플을 보다보니 변수에 문서 내용을 지정해 시연하는게 좋은 듯 해서, 우선 html 코드를 담을 변수를 만들기 위해서 구글에 'python multi line string' 이라고 검색 한다. 아래의 페이지를 보면 """ 세개로 스트링 앞 뒤를 감싸면 멀티라인을 가진 스트링이 되나 보다.
http://stackoverflow.com/questions/10660435/pythonic-way-to-create-a-long-multi-line-string
그럼 해당 우리가 웹에서 가져오는 데이터와 비슷한 데이터를 담은 (이상적인) 소스를 하나 만들어 본다. 해당 소스는 하나의 테이블로 이루어져 있고, 위 줄에 <th> 태그(테이블 제일 상단 표시) 2개와, 아랫 줄에 <td> 태그 2개와 있다. <tr> 태그는 각각의 행을 나타낸다. <th> 태그는 'choco', 'cookie' 라는 두개의 id 를 각각 가지고 있고, <td> 태그는 위쪽 태그만 'candy' 라는 name 속성을 가지고 있다.
1
2
3
4
5
6
7
8
9
10
11
12 |
html_doc = """
<table>
<tr>
<th id = "choco">초콜릿</th>
<th id = "cookie">과자</th>
</tr>
<tr>
<td name = "candy">사탕</td>
<td>오렌지</td>
</tr>
</table>
""" |
cs |
브라우저로 보면 아래와 같은 모양일 것이다. 이해를 돕기 위해 대충 tr, td 태그의 관계적 위치와 id, name 의 위치도 표시했다. (원래 크기가 크진 않고 잘 보이라고 확대 했다 --;)
그럼 위의 공식 문서에 안내된 대로 아래와 같이 입력해서 beautifulsoup 를 설치해 보자. 성공적으로 잘 설치 됬다.
c:\Python\code>pip install beautifulsoup4
Collecting beautifulsoup4
Downloading beautifulsoup4-4.5.3-py3-none-any.whl (85kB)
100% |################################| 92kB 178kB/s
Installing collected packages: beautifulsoup4
Successfully installed beautifulsoup4-4.5.3
예제에는 아래 5가지의 접근 방식을 소개하려 한다. 아래 예를 보고 나머지는 구글이나 공식 문서를 참조해서 이것 저것 해보다 보면 자기만의 감이 올 것 이다.
1) td 태그를 모두 찾아서 출력 한다. -> '사탕', '오렌지' 가 출력될 것이다.
2) id 가 'choco' 인 태그를 찾아서 출력한다 -> '초콜릿' 이 출력될 것이다.
3) td 태그이며, name 이 'candy' 인 태그의 옆에 있는 td 태그를 출력한다 -> '오렌지' 출력.
4), 5) 앞의 2, 3번과 같지만 css selector 방식을 이용한다 -> 초콜릿, 오렌지 가 출력 된다.
태그에서 내용만 뽑아내는 방법은 'beautifulsoup find_all get text' 로 구글에서 찾았다.
http://stackoverflow.com/questions/16835449/python-beautifulsoup-extract-text-between-element
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 |
#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
html_doc = """
<table border=1>
<tr>
<th id = "choco">초콜릿</th>
<th id = "cookie">과자</th>
</tr>
<tr>
<td name = "candy">사탕</td>
<td>오렌지</td>
</tr>
</table>
"""
# html 파서를 이용해서 html 형태의 데이터를 읽어온다.
soup = BeautifulSoup(html_doc, 'html.parser')
# 1. td 태그를 모두 찾아서 td_list 담은 후, 루프를 돌리면서 각 td 태그 내용을 출력한다.
td_list = soup.find_all('td')
for td_item in td_list:
print(td_item.string)
print ('\n')
# 2. id 가 choco 인 항목을 찾아서 해당 태그 내용을 출력한다.
id_list = soup.find(id='choco')
print(id_list.string)
# 3. td 태그이면서 name 속성이 candy 인 항목('캔디')을 찾아서
# 그 다음에 있는 같은 td 속성을 찾아서 태그 내용을('오렌지') 출력한다.
td_list = soup.find('td', {'name':'candy'})
print(td_list.find_next_sibling().string)
print ('\n')
# 4. 2번과 동일하지만 css selector 방식으로 사용한다.
td_list = soup.select('#choco')
print(td_list[0].string)
# 5) 3번과 동일하지만 css selector 방식으로 사용한다.
td_list = soup.select('td[name="candy"]')
print(td_list[0].find_next_sibling().string) |
cs |
해당 코드를 c:\python\code 폴더에 beautiful.py 라고 저장한다(역시 방법을 모르면 2교시 때 자세히 설명한 게 있으니 본다). 이후 실행 아래와 같이 시켜본다. 그런데 에러가 났다.
c:\Python\code>python beautiful.py
File "beautiful.py", line 14
"""
SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xc3 in position 0: invalid continuation byte
작성한 내용이 utf-8 코드가 아니라고 난다. 첨에는 python 3 버전도 뭔가 한글 처리 문제가 있어서 변환 방법을 찾아봐야 하나 하다가, 혹시나 해서 py 문서의 저장 방식을 살펴 보았더니 메모장에서 저장할때 ansi 로 인코딩 되어 저장되어 있다. 아래와 같이 메모장에서 열어 "파일 > 다른 이름으로 저장..."을 통해서 utf-8로 다시 저장해 본다.
이후 다시 실행해 보면 아래와 같이 정상으로 동작이 된다.
c:\Python\code>python beautiful.py
사탕
오렌지
초콜릿
오렌지
초콜릿
오렌지
이렇게 다 해놓고 보면 별거 아닌거 같지만, 사실 별거 아닌게 맞다--; 다만 전제 조건은 html 문법에 대해서 충분히 이해하여 소스를 잘 볼 수 있고, 복잡한 html 소스(javascript, css 가 섞여 있고, 코드가 꼬여 있을 수도 있다)를 만나도 개발자 도구 정도만 있으면(최악의 경우 메모장으로 소스를 보더라도 화면하고 비교해 가면서 찾아서) 충분히 원하는 태그의 특징과 위치를 지정 할 수 있는 방법만 파악할 수만 있음 된다. 그럼 이정도로 beautifulsoup 사용법 부분은 마치고, 그래도 짧아 아쉬운 느낌이 든다면 해당 내용을 기반으로 구글에서 크롤링에 대한 다른 블로그들을 보면서 파싱 부분을 어떻게 만든건지 하나씩 소화해 보자.
부록으로 개발자 도구를 사용하는 방법은 구글을 예로 들면, 구글 검색 페이지로 간후 'F12' 키를 누르면 하단에 개발자 도구가 생긴다(물론 다시 F12 를 누르면 사라진다). 이후 구글 검색 박스를 클릭해 포커스를 주고, 마우스 오른쪽 버튼을 눌러서 컨택스트 메뉴를 띄워서 '요소 검사' 항목을 선택한다.
그럼 아래에 해당 되는 요소(엘리먼트, 태그)에 해당되는 소스 위치가 하이라이트 되서 나타난다. 이 소스 부분 구조를 분석해서, 해당 태그에 접근하는 방법을 beautifulsoup 이든 정규식이든 이용해서 설계하는 것이다. 이게 단순하게 보이지만 웹 파싱의 전부다(파싱 난이도는 대상인 소스가 어떤가에 의해 결정난다).
[WhoIS 사이트 웹을 통해서 읽어오기]
그럼 10교시에서 API 를 이용해서 호출했던 WhoIS 페이지를 웹을 통해서 파싱해서 값을 가져와 보자. 문제를 단순해 하기 위해서 여러 IP 를 가져온다든지 하는 기능은 다 제쳐 두고 1개의 주소만 검색해 가져오려 한다(1개의 주소 호출만 해결나면 나머지 코드는 10장의 소스를 참고해서 적당히 머지해 만들면 된다). 우선 WhoIS 페이지로 이동하여 샘플 IP 를 하나 조회해 본다(조회 방법을 모른다면 10교시 내용을 보고오자).
http://whois.kisa.or.kr/kor/whois/whois.jsp
위의 내용 중에 주소를 가져오고 싶은데, 주소 위에서 마우스 오른쪽 버튼을 눌러도 컨텍스트 메뉴가 안 뜬다. 그리고 위쪽 주소창을 보면 URL 뒤에 아무 인자도 없으니 없으니 post 방식으로 조회되는 듯 한다. Ctrl+U 를 눌러 소스보기 창으로 간다. IE11 기준으로 개발자 도구가 열리며 아래와 같이 하단의 탭에 두 개의 파일이 보인다.
whois.jsp 와 whois.jsc 이다. 실제 호출은 하나만 일어난 것 같이(whois.jsp) 주소창에 보이지만, 2개의 파일이(whois.jsp, whois.jsc) 호출됬다고 볼 수 있다. '주소' 항목이 있는 소스 위치를 찾기 위해서, 'Ctrl+F' 를 눌러서 '주소'라고 입력후 검색창 옆의 '▶' 화살표를 눌러 다음 항목을 찾다보면 whois.jsc 파일 안에서 아래의 검색된 내용이 있는 것이 보인다. 소스 형태를 보니 우리가 원하던 html 태그가 아닌 일반 텍스트 형태이다. 흠 그럼 문제가 조금 복잡해 진다.
그럼 여기서 10교시에서 사용했던 피들러를 다시 사용한다(설치나 세팅에 대해서는 10교시를 참고한다). 피들러를 띄우고 F5 를 눌러 WhoIS 페이지를 재로딩 하여 다시 주소를 조회해 본다. 피들러로 잡힌 소스 중, js, css 파일을 제외하고(del 키를 눌러 필요없는 애들을 삭제했다), 두 개의 주요 파일만 보면 아래와 같다. 분명히 호출은 jsp 파일만 했는데 내부적으로 보니 jsc 파일이 그 후에 연달아 호출된 것이 보인다.
해당 호출이 어떻게 일어났나 이해하기 위해서 왼쪽 창에서 'whois.jsp' 파일을 선택 하고, 오른쪽 하단의 Response 창에서 'TextView' 탭을 선택 하고, 아래의 검색 창에 'whois.jsc' 를 입력 후 enter 키를 누른다.
그럼 안에 폼이 하나 있고, 해당 폼을 이용해서 whois.jsc 를 호출하는 듯 하다. 폼의 인자는 query(검색할 IP주소), ip(본인 PC IP-아마 감사용인듯), 그리고 호출 결과는 frm 이라는 iframe 에 담기게 되는 듯 하다. 검색 창에서 'ifrm' 을 넣어 찾아보면 javascript에서 submit 을 하는 코드가 있다.
또 'frm' 을 넣어 찾아보면, 결과를 담는 iframe 코드가 보인다.
그럼 파이썬으로 구현할때는 굳이 jsp 파일을 호출할 필요 없이 jsc 파일을 직접 호출해도 될것 같다. 대신 호출 할때 post 방식으로 query(검색 대상 IP) 와 ip(본인 PC IP) 를 넣어서 보내면 된다. 이후 결과 값을 받아온 후 파싱 하면 되는데, 불행하게도 돌아오는 데이터는 일반 텍스트 형태이기 때문에, beautifulsoup 을 이용할 순 없고, 8교시에 배운 정규 표현식을 이용해 본다.
[파이썬 코드 제작]
그럼 이제 제작에 필요한 모든 밑 작업은 완료되었으므로, 언제나처럼 우리가 현재 모르는게 무언지 체크해 보자.
1) post 로 호출해 값을 얻어오는 방법을 모른다(예전 API 는 거의 get 방식이였다)
2) "주소(적당한 공백들 ): 원하는 주소" 를 파싱하는 정규 표현식을 모른다(정규식을 대충 배웠으니 이젠 어떻게든 할수 있어 보이긴 한다)
먼저 post 로 호출 하는 방법을 찾아보자. 구글에서 'python requests post' 를 이용해 아래 페이지를 찾았다.
http://docs.python-requests.org/en/master/user/quickstart/
해당 페이지 내에서 아래의 more-complicated-post-requests 항목의 소스를 이용하려고 한다.
http://docs.python-requests.org/en/master/user/quickstart/#more-complicated-post-requests
1
2
3
4 |
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("http://httpbin.org/post", data=payload)
print(r.text) |
cs |
그 담에 정규식은 8교시에서 이미 다루었으니, 해결 결과만 얘기하면 아래와 같다.
"주소.*: ([^0-9].*)" -> 해석 해보면 주소란 단어로 시작하고(주소), 아무 글자나(.* : 사실 공백을 나타내는 /s 를 써도 좋지만 굳이 현재 상황상 그럴 필요까진 없을 듯해서 임의의 문자로 지정 했다) 0개 이상 나타나고 : 기호가(:) 가 나온 후 공백( ) 이 하나 나오고, 숫자가 나오지 않으면서 다른 아무 문자들로나 채워진([^0-9].*) 부분을 지정한다. 실제 가져오는 값은 이전에 8교시에 배에 배운 괄호 그룹기호를 이용한다(([^0-9].*)) . 결과에서 숫자를 제외한 이유는 피들러에서 '주소'를 검색했을때 '주소' 란 글자로 시작하는 경우에 'IPv4주소'가 있기 때문에 배제하려고 했다. 첨엔 주소 앞에 ^를 넣어 구분하려 했으나 각 행이 문장의 처음으로는 인식되지 않아서 위와 같이 만들었다. 주소 앞에 특정 문자가 있을 때는 무시하도록 정규 표현식을 짜도 될 듯 하지만 사실 넘어오는 값에 대해서 정확히 텍스트 구조가 파악 안되서 쉬운 길로 갔다.
1
2
3
4 |
import re
pattern = re.compile("주소.*: ([^0-9].*)")
match = re.findall(pattern, r.text)
print (match[0]) |
cs |
[두 개의 주소가 나오는 상황]
IPv4주소 : 202.30.50.0 - 202.30.51.255 (/23) <- 이 부분을 배제하기
주소 : 서울특별시 송파구 중대로 <- 그룹으로 묶어 가져옴
위 두 개의 코드를 합치면 아래와 같이 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 |
#-*- coding: utf-8 -*-
# jsc 페이지 post 로 호출하기
import requests
payload = {'query': '202.30.50.51', 'ip': '본인의 IP 주소'}
r = requests.post("https://whois.kisa.or.kr/kor/whois.jsc", data=payload)
r.encoding = 'utf-8'
# print(r.text)
# 결과에서 주소 얻어오기
import re
pattern = re.compile("주소.*: ([^0-9].*)")
match = re.findall(pattern, r.text)
print (match[0]) |
cs |
위의 소스를 복사해서 '본인의 IP 주소' 부분을 본인의 IP 로 바꾼 후(피들러에서 whois.jsc 검색 후 본 IP를 적음 된다), c:\python\code 폴더에 whois_test.py 라고 저장한다. 이후 아래와 같이 실행 시켜본다. 파싱된 주소를 정상적으로 가져온다.
c:\Python\code>python whois_test.py
서울특별시 송파구 중대로
[마무리 하면서]
위 예제는 웹 파싱이 꼭 beautifulsoup 을 사용할 수 있도록 아름답게만 제공되지 않는다는 것을 보여주려고 진행했다. 또한 html 뿐만 아니라, css, javascript 등의 연관된 언어를 잘 알아야 분석이 용이 하다는 얘기도 하고 싶었다. 실제 웹 대상으로 구현 하다보면 여러가지 파싱 장벽에 만나게 된다. 태그가 일부 깨져서 제대로 모듈이 동작 안할 수도 있고(브라우저는 왠만하면 에러를 안내고 깨진 태그를 적당히 해석해서 잘 보여주려 한다), 개발자가 기존 코드를 복사해서 개발하느라 고유해야 할 id 등이 여러 개 존재할 수도 있고, iframe 등으로 페이지가 복잡하게 꼬여 있을 경우 지금 처럼 해당 되는 실제 소스의 url을 찾아야 할때도 있고, 보안 상의 이유로 이리저리 자바스크립트를 이용해 호출을 숨겨놓거나, 난독화 시킨 코드들도 만들수 있다. 또 겨우 만든 파이썬 코드가, 해당 웹 페이지가 변경되면서 구조가 변경되어 다시 수정해야 되는 일이 어느날 갑자기 생길 수도 있다. 그것은 자기가 만든 웹 사이트가 아닌 이상(하물며 자기 회사 페이지라도 변경이 잘 통제되지 않는 이상) 언제든지 일어날 수 있는 일들이라고 생각해야 한다.
위의 코드를 10교시때 만들었던 API 호출 코드와 적절히 머지하여 반복적으로 IP를 호출하게 할 수도 있지만 앞에서도 얘기했듯이 크롤링과 같은 웹페이지의 반복적인 호출은 사이트에게 절대 환영받는 행동은 아니기 때문에 가능한 API 를 정식으로 발급 받아 필요한 만큼만 사용하는 것을 권장한다. 혹시 API 등도 없고 개인용도로 사용하면서 대량의 페이지를 조회 할 필요가 있을 때에는 아래와 같은 식으로 약간의 호출시간 간격을 두어, 조회하거나 적정한 숫자로 제한해 여러번 나눠 호출 하는 것이 장기적으로 사이트에나 본인에게 바람직 할 듯 싶다. 과유불급(過猶不及)이란 말을 꼭 생각하며 살자. 좀 의견이 고루 한것도 같으나 대부분의 크롤링 관련 글에서 이런 얘기는 잘 못본 것 같아서 균형을 맞추기 위해서 부득이하게 강조하게 된다.
1
2 |
import time
time.sleep(0.1) |
cs |
참고로 크롤링과 관련된 소송 사례를 두개 링크 한다.
https://www.lawtimes.co.kr/legal-news/Legal-News-View?Serial=98844&kind=AA
http://www.ddaily.co.kr/news/article.html?no=130822
그럼 다음 시간에는 이왕 API, 파싱까지 간거, 웹 자동화에 대한 이런저런 얘기들을 라이트하게 진행 할까 계획하고 있다.
[보충 - 09님 문의에 대한 답변]
네이버 책에서 "구글로 공부하는 파이썬" 책의 제목과 가격을 가져와 Sqlite 에 저장하는 코드 입니다. 참고로 요소 검사기로 보면 "구글로 공부하는 파이썬" 책은 옆에 부제(구글에서 찾는 파이썬 실전 예제 분석)가 달려있는데, 부제가 없는 책들은 46 라인에서 결과가 없어 에러가 나기때문에, 판단을 해서 예외처리를 해야 합니다~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 |
#-*- coding: utf-8 -*-
import sqlite3
import requests
from bs4 import BeautifulSoup
import re
# sqlite 파일을 열음
conn = sqlite3.connect("price.db", isolation_level=None)
cursor = conn.cursor()
# BookInfo 테이블을 생성한다.
cursor.execute("""CREATE TABLE IF NOT EXISTS BookInfo(itemno text, title text, price INTEGER)""")
# url 요청할 세션 만들기
s = requests.session()
# URL 만들기
searchItemno = '13394873'
searchurl = 'http://book.naver.com/bookdb/book_detail.nhn?bid='
# URL 호출
con = s.get(searchurl + searchItemno)
# html 파서 사용
soup = BeautifulSoup(con.text, 'html.parser')
# 책 가격 들어 있는 태그 가져오기
item_price = soup.find("div", class_="lowest")
item_price2 = item_price.find("strong")
# Beautiful Soup 으로 가져오기엔 태그가 묘해서, 정규표현식으로 실제 가격 가져옴
pattern = re.compile("<strong>(.*)<span")
match = re.search(pattern, str(item_price2))
# 책 가격 가져옴
book_price = match.group(1)
# 책 제목 가져옴(서브 제목이 없을 경우는 에러나서 다르게 찾아야함)
title = soup.find("a", class_="N=a:bil.title,i:98000001_000000000000000000CC63B9")
# Beautiful Soup 으로 가져오기엔 태그가 묘해서, 정규표현식으로 실제 제목만 가져옴
pattern = re.compile(">(.*)<span")
match = re.search(pattern, str(title))
book_title = match.group(1)
# "뒤의 \xa0" 문자 제거
book_title = book_title.strip()
# 테이블에 저장하기
sql = "INSERT into BookInfo(itemno, title, price) values (?, ?, ?)"
cursor.execute(sql, (searchItemno, book_title, book_price))
# 테이블에 저장한 값 불러오기
sql = "select itemno, title, price from BookInfo"
cursor.execute(sql)
row = cursor.fetchone()
if row:
while row:
print(row)
row = cursor.fetchone() |
cs |
c:\Python\code>python naver_book.py
('13394873', '구글로 공부하는 파이썬', 37800)
1 |
2017.3.22 by 자유로운설탕 |
cs |
'프로그래밍' 카테고리의 다른 글
구글로 공부하는 파이썬 - 13교시 (윈도우즈 GUI 자동화 with pywinauto) (12) | 2017.04.07 |
---|---|
구글로 공부하는 파이썬 - 12교시 (웹자동화 with Selenium) (6) | 2017.03.26 |
구글로 공부하는 파이썬 - 10교시 (Whois API 사용해 보기) (2) | 2017.03.11 |
구글로 공부하는 파이썬 - 9교시 (GUI 프로그램 만들어보기) (0) | 2017.03.05 |
구글로 공부하는 파이썬 - 8교시 (정규표현식 소개) (0) | 2017.03.04 |