<jupyter_start><jupyter_text>Python and Web Tutorial Table of ContentsPython and Web Tutorial웹(Web)CS(클라이언트-서버; Client-Server) ModelHTTP(Hyper Text Transfer Protocol)Markup &amp; Markdown웹 브라우저(Web Browser)요청(Request)URI(Uniform Resource Identifier)URL(Uniform Resource Location)요청 방식(Request Method)Python CodeGET 방식으로 요청하기응답(Response)상태 코드(Status Code)DOMXML Parser지원하는 XML Parser의 종류CSSCSS SelectorPatternAttribute SelectorPattern연습1. 모든 HTML 태그를 가져오기2. 태그명을 통해 특정 태그 가져오기3. 태그 id가 fruit인 태그 가져오기4. 태그 id가 food인 태그의 자손 태그인 p 태그 가져오기5. 태그 id가 food인 태그의 자식 태그인 div 태그 가져오기6. class가 f인 태그 가져오기7. class가 f f a인 태그 가져오기8. p 태그의 class가 f b인 태그 가져오기9. p 태그의 id가 apple이며 class가 f f a인 태그 가져오기10. name이라는 속성을 가진 태그 가져오기11. p 태그 중 name이라는 속성을 가진 태그 가져오기12. p 태그 중 name이라는 속성을 가진 태그 중 bread라는 값을 가진 태그 가져오기실전 예시Header  웹(Web)월드 와이드 웹(World Wide Web)이란 인터넷에 연결된 사용자들이 서로의 정보를 공유할 수 있는 공간을 의미한다. 간단히 줄여서 WWW나 W3라고도 부르며, 간단히 웹(Web)이라고 가장 많이 불린다.인터넷과 같은 의미로 많이 사용되고 있지만, 정확히 말해 웹은 인터넷상의 인기 있는 하나의 서비스일 뿐이다. 하지만 현재에는 인터넷과 웹이라는 단어가 서로 혼용되어 사용될 만큼 인터넷의 가장 큰 부분을 차지하고 있다.  CS(클라이언트-서버; Client-Server) Model클라이언트와 서버가 서로 데이터를 주고 받는 모델이다.서버로부터 클라이언트가 데이터를 요청하면 서버는 클라이언트에게 요청한 방식에 대한 응답을 하여 데이터를 제공하는 형태를 취한다.  HTTP(Hyper Text Transfer Protocol)W3(WWW: World Wide Web) 상에서 정보를 주고받을 수 있는 규약을 HTTP이라 한다.하이퍼텍스트(Hyper Text)란, 독자로 하여금 문서에서 다른 문서로 즉시 접근할 수 있도록 하는 텍스트 형태를 가르킨다. 다시말해 문서 내부에 또 다른 문서로 연결되는 참조를 집어 넣음으로써 웹 상에 존재하는 여러 문서끼리 서로 참조할 수 있는 기술을 의미한다.이때 문서 내부에서 또 다른 문서로 연결되는 참조를 하이퍼링크(hyperlink)라고 부른다. 대표적인 하이퍼 텍스트는 HTML(Hyper Text Markup Language)라는 마크업 형태의 문서가 있다. Markup & Markdown여기서 말하는 마크는 태그(Tag)를 뜻하며, 마크업의 반대말인 마크다운(Markdown)도 존재한다. 마크다운은 마크업과는 달리 단순한 텍스트 형식과 기호로 표현할 수 있어 읽기 편하고 작성하기 쉽다는 장점을 가지고 있다. 현재 읽고 있는 jupyter notebook 문서의 설명문도 이 마크다운 형식으로 작성되었다. 웹 브라우저(Web Browser)웹 브라우저는 멀티미디어 파일등 W3를 기반으로 한 인터넷의 컨텐츠를 검색 및 열람하기 위한 응용 프로그램의 총칭이다.쉽게 말해 마크업 형식으로 작성된 HTML을 해석하는 통역사로 이해하면 된다.  요청(Request)클라이언트가 서버에 데이터를 요청할 때는 URI 및 URL을 통해 요청을 한다.  URI(Uniform Resource Identifier)URI은 인터넷 상의 자원을 식별하기 위한 문자열의 구성이다. URL(Uniform Resource Location)URI의 한 형태인 URL은 인터넷 상에 존재하는 자원 위치를 나타낸다.  요청 방식(Request Method)HTTP 요청 방식은 많지만, 그 중에서 5가지, 5가지 중에서도 가장 많이 쓰이는 방식으로 2가지(GET, POST)가 존재한다.1. GET: 데이터 획득- 웹 서버에 데이터 전송을 요구- 요청한 데이터를 서버로부터 가져오기만 함- URL에 직접적으로 쿼리 문자열(parameter)을 이어 붙이는 방식을 취함- 이 요청에 대한 응답은 캐시가 가능함2. POST: 데이터 전송- 클라이언트 -> 웹 서버로 데이터를 전송- 파일 전송이 가능- 데이터를 요청 메시지의 body에 담아 전송3. PUT: 데이터 갱신- 클라이언트에서 웹 서버로 데이터를 전송- 파일 전송이 가능- 새로운 데이터 전송이 아닌 데이터 갱신을 목적으로 함- 따라서 웹 서버는 클라이언트가 최근에 제출한 URI를 그대로 사용4. DELETE: 데이터 삭제- 웹 서버의 데이터 삭제를 요청- PUT과 상반된 개념5. HEAD: 헤더 획득- 웹 서버에 헤더를 요청- 실제 문서(body)가 아닌 문서에 대한 정보(header)만을 요청- 웹 서버의 상태 점검(health check)이나 웹 서버 정보(version) 등을 얻기 위해 사용됨전부 다 알 필요는 없고 기억해야 하는 가장 많이 쓰이는 GET 방식과 POST 방식 두 가지만 기억하면 된다.  Python Code아래는 웹 서버에 요청을 보내는 파이썬 코드를 작성하는 예시이다. 먼저, 웹 서버에 요청을 보내기 위해 필요한 `requests` 패키지를 import 한다.<jupyter_code>import requests<jupyter_output><empty_output><jupyter_text>GET 방식으로 요청하기GET 방식으로 요청을 보내는 방식과 URL의 형태는 아래와 같다.- protocol://domain_address?key1=value1&key2=value2... 아래와 같이 시험삼아 구글에 GET 방식으로 요청을 보내는 코드를 작성해본다.<jupyter_code>url = 'http://www.google.co.kr'
resp = requests.get(url)<jupyter_output><empty_output><jupyter_text>응답(Response)여기서 응답은 클라이언트가 서버에게 요청을 보내면 서버로부터 응답을 받은 데이터를 의미한다. W3에서 응답의 최종적인 결과는 HTML 문서이다.  상태 코드(Status Code)상태코드는 1xx~5xx까지 존재하는데 이것이 의미하는 것은 다음과 같다.- 1xx: 조건부 응답- 2xx: 성공- 3xx: 리다이렉션 완료- 4xx: 요청 오류, 클라이언트 측 잘못- 5xx: 서버 오류, 서버 측 잘못 아까 구글 서버로부터 전달받은 응답 객체를 확인해본다.<jupyter_code>resp<jupyter_output><empty_output><jupyter_text>결과는 `Response`라는 응답 객체를 반환하였고, 이 응답객체의 상태코드는 200이다. 정상적으로 응답을 받았다는 뜻이다.이제 이 응답받은 HTML 문서를 확인해보자. 문서의 길이가 상당히 길어 우선 300자까지만 출력하였다.<jupyter_code>resp.text[:300]<jupyter_output><empty_output><jupyter_text>DOMDOM이란, Document Object Model의 약어이며 HTML과 XML 문서의 프로그래밍 인터페이스(interface)이다.DOM은 문서의 구조화된 표현을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕는다.  XML Parser현재 대부분의 주요 웹 브라우저는 XML 문서에 접근하고 조작하기 위한 XML 파서를 별도로 내장하고 있다.XML DOM은 XML 문서에 접근하고 조작할 수 있는 다양한 메소드를 포함하고 있다.하지만 이러한 메소드를 이용하려면 우선 XML 문서를 XML DOM 객체로 변환해야만 한다.XML 파서(parser)는 XML 문서의 평문(plain text) 데이터를 읽어 들여, 그것을 XML DOM 객체로 반환해준다. DOM 트리 구조를 만들기 위해서 `bs4` 라이브러리에 있는 `BeautifulSoup` 객체를 사용한다.<jupyter_code>from bs4 import BeautifulSoup<jupyter_output><empty_output><jupyter_text>지원하는 XML Parser의 종류- html.parser: 빠르지만 유연하지 않아 단순한 HTML 문서에 사용- lxml: 매우 빠르고 유연함- xml: XML 파일에만 사용- html5lib: 복잡한 구조의 HTML에 대해서 사용<jupyter_code>dom = BeautifulSoup(resp.text, 'lxml')<jupyter_output><empty_output><jupyter_text>CSSCSS는 Cascading Style Sheet의 약어로써, HTML 등 마크업으로 작성된 문서가 실제 웹 사이트에 표현되는 방법을 정해주는 언어인데, 쉽게 말해서 시각적인 디자인과 레이아웃을 표현하는데 사용된다.이 CSS에도 객체를 만들어 객체의 속성값을 정할 수 있다.  CSS SelectorSelector는 선택자라는 의미로써, 말 그대로 선택을 해주는 요소이다. CSS 객체와 CSS 객체에 대한 요소를 선택한다. Pattern패턴의 종류는 상당히 많은데, 패턴의 종류와 사용 예시는 아래와 같다.- *(asterisk): HTML 페이지 내부의 모든 태그 선택- E: 태그명이 E인 특정 태그를 선택- .my_class: 클래스 속성값이 my_class로 지정된 요소를 선택- my_id: 태그 id 속성값이 my_id로 지정된 요소를 선택- E E2: E 요소의 '자손'인 E2 요소를 선택- E > E2: E 요소의 '자식'인 E2 요소를 선택- E+E2: E 요소를 뒤따르는 E2 요소를 선택 (만약 E와 E2 사이에 다른 요소가 존재하면 선택되지 않음)- E~E2: E 요소보다 앞에 존재하면 E2를 선택 (E가 E2보다 먼저 정의되어있지 않으면 선택되지 않음)  Attribute Selector특정 속성을 가진 요소를 선택하는데 사용한다. Pattern- E[attr]: attr 속성이 포함된 요소 E를 선택- E[attr="val"]: 'attr' 속성의 값이 정확하게 'val'과 일치하는 요소 E를 선택- E[attr~="val"]: 'attr' 속성의 값에 'val'이 포함되는 요소를 선택 (공백으로 분리된 값이 일치해야함)- E[attr^="val"]: 'attr' 속성의 값이 'val'으로 시작하는 요소를 선택- E[attr$="val"]: 'attr' 속성의 값이 'val'으로 끝나는 요소를 선택- E[attr*="val"]: 'attr' 속성의 값에 'val'이 포함되는 요소를 선택- E[attr|="val"]: 'attr' 속성의 값이 정확하게 'val' 이거나 'val-' 으로 시작되는 요소 E를 선택  연습예시를 들기 위해 HTML 문서를 작성하였다.<jupyter_code>html = '''
<!DOCTYPE HTML>
<html>
  <head>
    <meta charset='utf8' />
    <title>사이트명</title>
  </head>
  <body>
    <div id='animal' class='a' name='animal'>
      <p id='cat' class='a c' name='cat'>고양이</p>
      <p id='dog' class='a d' name='dog'>개</p>
    </div>
    <div id='food' class='f' name='food'>
      <p>음식</p>
      <div id='fruit' class='f f'>
        <p id='apple' class='f f a'>사과</p>
        <p id='banana' class='f f b'>바나나</p>
      </div>
      <div id='bread' class='f b'>
        <p id='bread' class='f b' name='bread'>빵</p>
      </div>
    </div>
  </body>
</html>
'''
print(html)<jupyter_output><!DOCTYPE HTML>
<html>
  <head>
    <meta charset='utf8' />
    <title>사이트명</title>
  </head>
  <body>
    <div id='animal' class='a' name='animal'>
      <p id='cat' class='a c' name='cat'>고양이</p>
      <p id='dog' class='a d' name='dog'>개</p>
    </div>
    <div id='food' class='f' name='food'>
      <p>음식</p>
      <div id='fruit' class='f f'>
        <p id='apple' class='f f a'>사과</p>
        <p id='banana' class='f f b'>바나나</p>
      </div>
      <div id='bread' class='f b'>
        <p id='bread' class='f b' name='bread'>빵</p>
      </div>
    </div>
  </body>
</html><jupyter_text>위에서 작성한 HTML 문서를 DOM 객체로 만들고 selector를 이용해 특정 패턴에 해당하는 요소를 가져온다.<jupyter_code>dom = BeautifulSoup(html, 'lxml')<jupyter_output><empty_output><jupyter_text>1. 모든 HTML 태그를 가져오기<jupyter_code>dom.select('*')<jupyter_output><empty_output><jupyter_text>2. 태그명을 통해 특정 태그 가져오기<jupyter_code>dom.select('body')<jupyter_output><empty_output><jupyter_text>3. 태그 id가 `fruit`인 태그 가져오기<jupyter_code>dom.select('#fruit')<jupyter_output><empty_output><jupyter_text>4. 태그 id가 `food`인 태그의 자손 태그인 `p` 태그 가져오기<jupyter_code>dom.select('#food p')<jupyter_output><empty_output><jupyter_text>5. 태그 id가 `food`인 태그의 자식 태그인 `div` 태그 가져오기<jupyter_code>dom.select('#food > p')<jupyter_output><empty_output><jupyter_text>6. class가 `f`인 태그 가져오기<jupyter_code>dom.select('.f')<jupyter_output><empty_output><jupyter_text>7. class가 `f f a`인 태그 가져오기<jupyter_code>dom.select('.f.f.a')<jupyter_output><empty_output><jupyter_text>8. `p` 태그의 class가 `f b`인 태그 가져오기<jupyter_code>dom.select('p.f.b')<jupyter_output><empty_output><jupyter_text>9. `p` 태그의 id가 `apple`이며 class가 `f f a`인 태그 가져오기<jupyter_code>dom.select('p#apple.f.f.a')<jupyter_output><empty_output><jupyter_text>10. `name`이라는 속성을 가진 태그 가져오기<jupyter_code>dom.select('[name]')<jupyter_output><empty_output><jupyter_text>11. `p` 태그 중 `name`이라는 속성을 가진 태그 가져오기<jupyter_code>dom.select('p[name]')<jupyter_output><empty_output><jupyter_text>12. `p` 태그 중 `name`이라는 속성을 가진 태그 중 `bread`라는 값을 가진 태그 가져오기<jupyter_code>dom.select('p[name="bread"]')<jupyter_output><empty_output><jupyter_text>실전 예시구글 사이트에 GET 방식으로 요청을 보낸 뒤, 로고 이미지를 다운로드한다.<jupyter_code>url = 'http://www.google.co.kr'
resp = requests.get(url)
dom = BeautifulSoup(resp.text, 'lxml')
tags = dom.select('img#hplogo')
logo_tag = tags[0]
logo_tag<jupyter_output><empty_output><jupyter_text>로고 이미지 태그를 가져오는데 성공했다. 태그의 속성 중 `src`가 로고 이미지 링크이다.<jupyter_code>logo_tag['src']<jupyter_output><empty_output><jupyter_text>해당 링크에 다시 요청을 보내면 바이너리 형식의 결과값을 얻을 수 있는데, 이미지를 웹에서 표현할 때에는 바이너리 형식으로 표현이 된다. 먼저 `src` 속성값의 링크는 URL이다. 단순 URL만으로는 어떤 도메인에 위치한 리소스인지 알 수 없으므로 이 도메인 주소와 합쳐준다.이 작업을 `URL Parsing`이라고 한다. 아래의 코드는 구글 도메인 주소와 로고 이미지 주소(위치)를 합쳐 최종적인 형태의 URL을 만드는 코드이다.<jupyter_code>from urllib.parse import urljoin
logo_url = urljoin(url, logo_tag['src'])<jupyter_output><empty_output><jupyter_text>이제 실질적인 로고 이미지 데이터를 얻어야한다. 절차는 URL을 통해 요청을 보내고 응답을 받는 동일한 절차이다.<jupyter_code>resp = requests.get(logo_url)<jupyter_output><empty_output><jupyter_text>Header응답받은 객체로부터 헤더(header)를 확인해보자.해더란, 요청과 응답을 주고받기 위해 사용되는 상태들을 정의한 정보들이다.<jupyter_code>resp.headers<jupyter_output><empty_output><jupyter_text>이 응답의 `Content-Type` 즉 내용 형태는 `image`파일의 `png`형식이라는 것을 명시하고 있다.내용을 확인해보자.<jupyter_code>resp.content<jupyter_output><empty_output><jupyter_text>내용을 출력하면 이상한 문자들이 깨져서 나오는 것처럼 보이는 것을 확인할 수 있는데, 이미지는 바이너리 형태의 데이터라서 그렇다.<jupyter_code>type(resp.content)<jupyter_output><empty_output><jupyter_text>이제 로고 이미지를 바이너리 형태로 한번 저장해보자.<jupyter_code>with open('logo.png', 'wb') as image:
    image.write(resp.content)<jupyter_output><empty_output><jupyter_text>이미지가 잘 저장이 되었는지 확인해보자.<jupyter_code>from PIL import Image
Image.open('logo.png')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11630731855450957, 'fraction_numerical': 0.007895535985423625, 'mean_word_length': 4.467625899280575, 'pattern_counts': {'":': 0, '<': 176, '<?xml version=': 0, '>': 179, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 5}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '29', 'dir': 'jupyter-structured-clean-dedup', 'id': '34821', 'license': 'Apache-2.0', 'n_tokens_mistral': 6690, 'n_tokens_neox': 8602, 'n_words': 1349, 'path': 'web-tutorial/00. Python and Web.ipynb', 'repo_name': 'U-Jhin-s-Python-Tutorial/Python-Tutorial', 'size': '9879'}