사전형과 문자열

이 장에서는 자료 분석을 할 때 자료를 저장하고 불러오고 가공하는데 편리하게 사용할 수 있는 파이썬 객체들에 대해서 알아봅니다.

사전형(Dictionary)

사전(dictionary)은 다양한 종류의 정보들을 저장하는데 매우 유연한 객체입니다. 특히 파일을 읽어들여서 처리할 때 유용합니다.

리스트는 인덱스를 정수를 이용해 접근할 수 있었지만 사전은 문자열을 이용하여 접근할 수 있습니다.

사전 만들기

사전은 {키:값} 형태로 만들어집니다. 는 일반적으로 문자열이 많이 쓰이고 불변하는 객체가 올 수 있습니다. 은 어떤 파이썬 객체도 가능합니다.

리스트를 이용하여 오슬로, 런던, 파리의 온도를 저장하는 한다면 다음과 같이 만들 수 있을 겁니다.

temps = [13, 15.4, 17.5]

하지만 이것은 순서대로 오슬로, 런던, 파리라는 것을 기억하고 있어야 합니다.

사전을 이용해서 온도를 저장할 때는 다음과 같이 합니다. 여기서는 리스트의 인덱스에 해당하는 것이 도시 이름이 되어 따로 기억할 필요가 없습니다.

In [1]: temps = {'Oslo': 13, 'London': 15.4, 'Paris': 17.5}

또는 다음과 같이 dict 함수를 이용해서 만들수도 있습니다.

In [2]: temps = dict(Oslo=13, London=15.4, Paris=17.5)

추가할 항목이 생기면 다음과 같이 하면 됩니다.

In [3]: temps['Seoul'] = 17

따라서 temps 사전은 다음과 같이 4개의 항목을 가지는 것을 알 수 있습니다.

In [4]: temps
Out[4]: {'Oslo': 13, 'London': 15.4, 'Paris': 17.5, 'Seoul': 17}

사전에 접근하려면 를 이용합니다.

In [5]: temps['Seoul']
Out[5]: 17

사전 연산

사전에서 인덱스에 해당하는 것이 입니다. 사전의 키들을 순회하기 위해서는 for 문을 사용합니다.

In [6]: for city in temps:
   ...:   print('{}의 온도는 {}'.format(city, temps[city]))
   ...: 
Oslo의 온도는 13
London의 온도는 15.4
Paris의 온도는 17.5
Seoul의 온도는 17

가 사전에 있는지 확인하기 위해서는 in을 사용할 수 있습니다.

In [7]: 'Berlin' in temps
Out[7]: False
In [8]: 'Seoul' in temps
Out[8]: True

에 대한 리스트는 keys()values() 메소드를 각각 사용합니다.

In [9]: temps.keys()
Out[9]: dict_keys(['Oslo', 'London', 'Paris', 'Seoul'])
In [10]: temps.values()
Out[10]: dict_values([13, 15.4, 17.5, 17])

사전의 순서는 예측할 수 없기 때문에 특정한 순서를 원하면 sorted 함수를 사용해야 합니다.

In [11]: for city in sorted(temps):
   ....:   print(city)
   ....: 
London
Oslo
Paris
Seoul

파이썬은 지정된 순서로 사전을 만들 수 있는 OrderedDict 객체가 있습니다.

del를 이용해서 사전의 항목을 제거할 수 있습니다.

In [12]: del temps['Oslo']
   ....: temps
   ....: 
Out[12]: {'London': 15.4, 'Paris': 17.5, 'Seoul': 17}

copy() 메소드를 이용해서 새로운 복사 사전을 만들 수 있습니다.

In [13]: temps_copy = temps.copy()
   ....: del temps_copy['Paris']
   ....: print(temps_copy)
   ....: print(temps)
   ....: 
{'London': 15.4, 'Seoul': 17}
{'London': 15.4, 'Paris': 17.5, 'Seoul': 17}

사전형을 이용한 다항함수

int, float, complex, str, 과 tuple 등은 내용을 변경할 수 없는 객체로 불변(immutable) 객체라고 합니다. 리스트와 사전은 원소들을 바꿀 수 있는 가변(mutable) 객체입니다. 앞에서 말했듯이 사전의 키는 문자열 뿐아니라 불변 객체들이 올 수 있습니다.

정수형 를 사용해 다항함수를 사전으로 만들 수 있습니다.

\[p(x) = -1 + x^2 + 3x^7\]

다항함수 \(p(x)\)를 차수를 키로 계수를 값으로 설정해서 만들 수 있습니다.

In [14]: p = {0: -1, 2: 1, 7: 3}

물론 리스트를 이용해서도 다항함수를 만들수 있습니다. 리스트의 인덱스를 차수로 놓고 대응되는 원소를 계수로 설정할 수 있습니다.

In [15]: p = [-1, 0, 1, 0, 0, 0, 0, 3]

해당하는 차수가 없는 경우는 계수를 0으로 채워야 합니다. 따라서 \(1 + x^{100}\)인 경우는 101개의 원소로 이루어진 리스트를 만들어야합니다.

다음과 같이 다항함수값을 구하는 함수를 만들 수 있습니다.

In [16]: def eval_poly_dict(poly, x):
   ....:   s = 0
   ....:   for pow in poly:
   ....:     s += poly[pow] * x ** pow
   ....:   return s
   ....: 

poly 인자는 위에서 정의한 사전형이어야 하고 poly[pow]는 계수를 의미합니다.

리스트축약과 sum 함수를 이용해서 더 간단히 표현할 수 있습니다.

In [17]: def eval_poly_dict2(poly, x):
   ....:   return sum([poly[pow] * x ** pow for pow in poly])
   ....: 

사전을 사용하면 음수 차수에 대한 함수도 표현할 수 있습니다. 예를 들면 \(\frac{1}{2}x^{-3} + 2x^4\) 은 다음과 같이 표현할 수 있습니다.

p = {-3: 0.5, 4: 2}

기본값 설정 및 순서

\(2x^{-3} -1.5x^{-1} -2x^2\) 에 대한 사전

In [18]: p1 = {-3: 2, -1: -1.5, 2: -2}
   ....: 

이 있다고 할 때 \(p1[1]\) 을 접근하려고 하면 키가 없어서 에러가 발생합니다. 이것을 방지하려면 다음과 같이 해야 합니다.

if key in p1:
  value = p1[key]

또는

value = p1.get(key, 0)

p1.get()은 키가 존재하면 그 값을 반환하고 그렇지 않으면 두번째 인자를 반환합니다.

순서있는 사전

파이썬 버전 3.7 미만에서는 사전은 기본적으로 입력 순서와 상관없이 출력되었습니다. 하지만 3.7 이상에서는 입력된 순서대로 키들을 순회할 수 있게 되었습니다. 순서가 있는 사전을 위해서 collections 모듈에 OrderedDict 객체가 마련되어 있습니다.

In [19]: from collections import OrderedDict
   ....: p2 = OrderedDict({-3: 2, -1: -1.5, 2: -2})
   ....: for key in p2:
   ....:   print(key, p2[key])
   ....: 
-3 2
-1 -1.5
2 -2

파일을 사전으로 저장

다음은 여러 물질의 밀도들을 나타내는 표입니다. src/densities.dat 파일로 저장하세요.

air 0.0012
gasoline 0.67
ice 0.9
pure water 1.0
seawater 1.025
human body 1.03
limestone 2.6
granite 2.7
iron 7.8
silver 10.5
mercury 13.6
gold 18.9
platinium 21.4
Earth mean 5.52
Earth core 13
Moon 3.3
Sun mean 1.4
Sun core 160
proton 2.3E+14

이 파일을 읽어 키: 값 형태의 사전으로 저장하려고 합니다.

In [20]: def read_densities(filename):
   ....:   with open(filename, 'r') as infile:
   ....:     densities = {}
   ....:     for line in infile:
   ....:       words = line.split()
   ....:       density = float(words[-1])
   ....:       if len(words[:-1]) == 2:
   ....:         substance = words[0] + ' ' + words[1]
   ....:       else:
   ....:         substance = words[0]
   ....: 
   ....:       densities[substance] = density
   ....: 
   ....:   return densities
   ....: 

함수를 이용해서 파일을 읽어옵니다.

In [21]: densities = read_densities('src/densities.dat')
   ....: densities
   ....: 
Out[21]: 
{'air': 0.0012,
 'gasoline': 0.67,
 'ice': 0.9,
 'pure water': 1.0,
 'seawater': 1.025,
 'human body': 1.03,
 'limestone': 2.6,
 'granite': 2.7,
 'iron': 7.8,
 'silver': 10.5,
 'mercury': 13.6,
 'gold': 18.9,
 'platinium': 21.4,
 'Earth mean': 5.52,
 'Earth core': 13.0,
 'Moon': 3.3,
 'Sun mean': 1.4,
 'Sun core': 160.0,
 'proton': 230000000000000.0}

파일을 중첩 사전으로 저장

다음은 A, B, C, D 이름을 가진 성질의 측정값입니다. prop_table.dat 라는 이름으로 저장하세요.

A B C D
1 11.7 0.035 2017 99.1
2 9.2 0.037 2019 101.2
3 12.2 no no 105.2
4 10.1 0.031 no 102.1
5 9.1 0.033 2009 103.3
6 8.7 0.036 2015 101.9

파일을 읽어 data['A'][i] 형식으로 중첩 사전으로 저장을 하여 각 성질의 평균도 함께 계산해서 저장하려고 합니다.

In [22]: with open('src/prop_table.dat', 'r') as infile:
   ....:   lines = infile.readlines()
   ....: 
   ....: data = {} # data[property][measurement_no] = propertyvalue
   ....: first_line = lines[0]
   ....: properties = first_line.split()
   ....: for p in properties:
   ....:   data[p] = {}
   ....: 
   ....: for line in lines[1:]:
   ....:   words = line.split()
   ....:   i = int(words[0]) # measurement number
   ....:   values = words[1:] # values of properties
   ....:   for p, v in zip(properties, values):
   ....:     if v != 'no':
   ....:       data[p][i] = float(v)
   ....: 
   ....: # Compute mean values
   ....: for p in data:
   ....:   values = data[p].values()
   ....:   data[p]['mean'] = sum(values) / len(values)
   ....: 
   ....: for p in sorted(data):
   ....:   print('Mean value of property {} = {:.2f}'.format(p, data[p]['mean']))
   ....: 
Mean value of property A = 10.17
Mean value of property B = 0.03
Mean value of property C = 2015.00
Mean value of property D = 102.13

pprint 모듈의 pprint 함수를 이용해 data를 보기좋게 출력해봅니다.

In [23]: import pprint
   ....: 
   ....: pprint.pprint(data)
   ....: 
{'A': {1: 11.7,
       2: 9.2,
       3: 12.2,
       4: 10.1,
       5: 9.1,
       6: 8.7,
       'mean': 10.166666666666666},
 'B': {1: 0.035, 2: 0.037, 4: 0.031, 5: 0.033, 6: 0.036, 'mean': 0.0344},
 'C': {1: 2017.0, 2: 2019.0, 5: 2009.0, 6: 2015.0, 'mean': 2015.0},
 'D': {1: 99.1,
       2: 101.2,
       3: 105.2,
       4: 102.1,
       5: 103.3,
       6: 101.9,
       'mean': 102.13333333333334}}

문자열

많은 프로그램들이 문자열을 다루는 것이 필요합니다.

파이썬 문자열은 열거형으로 인덱스를 이용해서 문자 하나 하나에 접근할 수 있습니다.

문자열에 많이 쓰이는 연산들

부분 문자열

문자열은 슬라이싱을 이용해 부분 문자열을 만들 수 있습니다.

In [24]: s = 'Seoul: 18.4 C at 4 pm'
   ....: s[2:]
   ....: 
Out[24]: 'oul: 18.4 C at 4 pm'
In [25]: s[8:-1]
   ....: 
Out[25]: '8.4 C at 4 p'

문자열 검색

find 메소드를 이용해서 찾고자 하는 문자열의 시작 인덱스를 찾을 수 있습니다.

In [26]: s.find('Seoul')
   ....: 
Out[26]: 0
In [27]: s.find('pm')
   ....: 
Out[27]: 19

검색 문자열이 존재하지 않으면 -1을 반환합니다.

In [28]: s.find('Berlin')
   ....: 
Out[28]: -1

특정 문자열이 속해있는지를 알고 싶으면 in 을 사용할 수 있습니다.

In [29]: 'Berlin' in s
   ....: 
Out[29]: False
In [30]: 'Seoul' in s
   ....: 
Out[30]: True

startswith, endswith 메소드를 이용하여 시작하는 문자열 또는 끝나는 문자열을 찾을 수 있습니다.

In [31]: s.startswith('Seoul')
   ....: 
Out[31]: True
In [32]: s.endswith('am')
   ....: 
Out[32]: False

문자열 대체

replace 메소드를 이용해서 문자열을 대체할 수 있습니다.

In [33]: s.replace(' ', '_')
   ....: 
Out[33]: 'Seoul:_18.4_C_at_4_pm'
In [34]: s.replace('Seoul', 'Jochiwon')
   ....: 
Out[34]: 'Jochiwon: 18.4 C at 4 pm'

여러 연산을 합쳐서 사용할 수 있습니다. 다음은 콜론 앞에 있는 문자열을 대체하는 코드입니다.

In [35]: s.replace(s[:s.find(':')], 'Jochiwon')
   ....: 
Out[35]: 'Jochiwon: 18.4 C at 4 pm'

문자열 분리

split 메소드를 이용해서 문자열을 리스트로 분리할 수 있습니다.

In [36]: s.split()
   ....: 
Out[36]: ['Seoul:', '18.4', 'C', 'at', '4', 'pm']

구분자를 지정해서 구분자를 기준으로 분리할 수 있습니다.

In [37]: s.split(':')
   ....: 
Out[37]: ['Seoul', ' 18.4 C at 4 pm']

splitlines 메소드를 이용해서 줄 단위로 분리할 수 있습니다.

In [38]: t = '1st line\n2nd line\n3rd line'
   ....: t.splitlines()
   ....: 
Out[38]: ['1st line', '2nd line', '3rd line']

또는 \n 특수 문자를 이용해서도 분리해도 됩니다.

In [39]: t.split('\n')
   ....: 
Out[39]: ['1st line', '2nd line', '3rd line']

대/소문자 변환

In [40]: s.lower()
   ....: 
Out[40]: 'seoul: 18.4 c at 4 pm'
In [41]: s.upper()
   ....: 
Out[41]: 'SEOUL: 18.4 C AT 4 PM'

문자열은 불변 객체

문자열은 불변 객체이기 때문에 한번 정해지면 내용을 바꿀 수 없습니다.

In [42]: s[18] = 5
   ....: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-42-99e183842b4b> in <module>
----> 1 s[18] = 5

TypeError: 'str' object does not support item assignment

앞에서 했던 replace 메소드에 의해서 변경된 문자열은 새로 만들어진 문자열입니다.

숫자 문자열

isdigit 메소드는 문자열이 오직 숫자들만 포함하고 있는지를 판단합니다.

In [43]: '214'.isdigit()
   ....: 
Out[43]: True
In [44]: ' 214'.isdigit()
   ....: 
Out[44]: False

다음은 소수점을 포함하는 문자열이기때문에 False가 반환됩니다.

In [45]: '2.14'.isdigit()
   ....: 
Out[45]: False

공백 문자열

isspace 메소드는 문자열이 공백 문자열로만 이루어졌는지를 판단합니다. 공백문자열은 스페이스(space), 새로운 줄(new line), 탭(tab) 문자등이 있습니다.

In [46]: '   '.isspace()
   ....: 
Out[46]: True
In [47]: '  \n'.isspace()
   ....: 
Out[47]: True
In [48]: '   \t'.isspace()
   ....: 
Out[48]: True

다음은 빈문자열(empty string)이기때문에 False 입니다.

In [49]: ''.isspace()
   ....: 
Out[49]: False

isspace 를 이용하면 쉽게 빈 줄을 확인할 수 있습니다. 빈 줄인 것을 확인하는 방법으로 다음과 같은 것도 사용할 수 있습니다.

In [50]: line = '     \n'
   ....: line.strip() == ''
   ....: 
Out[50]: True

strip 메소드는 문자열의 시작과 끝 부분의 공백문자들을 모두 제거한 문자열을 반환합니다.

In [51]: '  \n\t 앞 뒤의 공백 문자열을 제거합니다. \n  '.strip()
   ....: 
Out[51]: '앞 뒤의 공백 문자열을 제거합니다.'

앞 부분 또는 뒷 부분의 공백 문자열을 제거하려면 lstrip 또는 rstrip 메소드를 이용합니다.

In [52]: '  \n\t 앞 뒤의 공백 문자열을 제거합니다. \n  '.lstrip()
   ....: 
Out[52]: '앞 뒤의 공백 문자열을 제거합니다. \n  '
In [53]: '  \n\t 앞 뒤의 공백 문자열을 제거합니다. \n  '.rstrip()
   ....: 
Out[53]: '  \n\t 앞 뒤의 공백 문자열을 제거합니다.'

문자열 합치기

join 메소드를 이용해서 문자열 리스트를 구분 문자열과 함께 합칠 수 있습니다.

In [54]: s = ['Newton', 'Secant', 'Bisection']
   ....: ', '.join(s)
   ....: 
Out[54]: 'Newton, Secant, Bisection'

split 메소드와 join 메소드를 이용해서 문자열 중에서 원하는 단어들만 뽑아낼 수도 있습니다.

In [55]: line = 'This is a line of words separated by space'
   ....: words = line.split()
   ....: line2 = ' '.join(words[2:])
   ....: line2
   ....: 
Out[55]: 'a line of words separated by space'

예제: 순서쌍 읽기

다음과 같은 내용의 파일을 읽어 float 형의 숫자 순서쌍 튜플들의 리스트로 저장하는 프로그램을 작성하려고 합니다. 아래 내용을 read_pairs1.dat 파일에 저장하세요.

(1.3,0) (-1,2) (3,-1.5)
(0,1) (1,0) (1,1)
(0,-0.01) (10.5,-1) (2.5,-2.5)

풀이

  • 줄단위로 파일을 읽습니다.

  • 각 줄에서 공백 기준으로 문자열을 분리합니다.

  • 각 단어에서 소괄호를 제거합니다.

  • 소괄호가 제거된 문자열에서 쉼표를 기준으로 두 숫자를 분리하여 튜플로 저장합니다.

  • 튜플을 리스트에 추가합니다.

In [56]: # 파일에서 줄단위로 읽어옵니다.
   ....: with open('src/read_pairs1.dat', 'r') as infile:
   ....:   lines = infile.readlines()
   ....: 
   ....: # 각 라인별 내용 분석
   ....: pairs = [] # (n1, n2) 순서쌍 리스트
   ....: for line in lines:
   ....:   words = line.split()
   ....:   for word in words:
   ....:     word = word[1:-1] # 소괄호 제거
   ....:     n1, n2 = word.split(',')
   ....:     n1 = float(n1)
   ....:     n2 = float(n2)
   ....:     pair = (n1, n2)
   ....:     pairs.append(pair) # 리스트에 추가
   ....: 
   ....: pairs
   ....: 
Out[56]: 
[(1.3, 0.0),
 (-1.0, 2.0),
 (3.0, -1.5),
 (0.0, 1.0),
 (1.0, 0.0),
 (1.0, 1.0),
 (0.0, -0.01),
 (10.5, -1.0),
 (2.5, -2.5)]

위 코드는 데이터의 소괄호 안에 공백 문자가 들어있으면 제대로 작동을 하지 않습니다. 이럴 때는 각 줄에서 먼저 공백 문자열을 모두 제거하고 )( 구분자를 이용하여 문자열을 분리하고 처음과 마지막에 남아있는 괄호를 제거하는 방법으로 진행할 수 있습니다.

다음 내용을 read_pairs2.dat 파일에 저장합니다.

  (1.3 , 0) (-1 , 2 ) (3, -1.5)
(0 , 1)   ( 1, 0)   ( 1 , 1 )
(0,-0.01)   (10.5,-1)   (2.5, -2.5)
In [57]: # 파일에서 줄단위로 읽어옵니다.
   ....: with open('src/read_pairs2.dat', 'r') as infile:
   ....:   lines = infile.readlines()
   ....: 
   ....: # 각 라인별 내용 분석
   ....: pairs = [] # (n1, n2) 순서쌍 리스트
   ....: for line in lines:
   ....:   line = line.strip() # new line 문자 제거
   ....:   line = line.replace(' ', '') # 공백 제거
   ....:   words = line.split(')(') # )( 구분 분리
   ....:   words[0] = words[0][1:] # 첫 ( 제거
   ....:   words[-1] = words[-1][:-1] # 마지막 ) 제거
   ....:   for word in words:
   ....:     n1, n2 = word.split(',')
   ....:     n1 = float(n1)
   ....:     n2 = float(n2)
   ....:     pair = (n1, n2)
   ....:     pairs.append(pair) # 리스트에 추가
   ....: 
   ....: pairs
   ....: 
Out[57]: 
[(1.3, 0.0),
 (-1.0, 2.0),
 (3.0, -1.5),
 (0.0, 1.0),
 (1.0, 0.0),
 (1.0, 1.0),
 (0.0, -0.01),
 (10.5, -1.0),
 (2.5, -2.5)]

정규표현식

문자열 검색 및 변경을 위해서 정규표현식(regular expression)이라는 강력한 도구도 있습니다. 여기서는 예제를 통해서 간단한 소개만 합니다.

위의 코드를 정규표현식을 이용하면 다음과 같이 구할 수 있습니다.

In [58]: with open('src/read_pairs2.dat', 'r') as infile:
   ....:   lines = infile.read()
   ....: 
   ....: import re
   ....: s = re.sub('\s+', '', lines)
   ....: s1 = re.findall('\(([-\d\.]*),([-\d\.]*)?\)', s)
   ....: [(float(x), float(y)) for x, y in s1]
   ....: 
Out[58]: 
[(1.3, 0.0),
 (-1.0, 2.0),
 (3.0, -1.5),
 (0.0, 1.0),
 (1.0, 0.0),
 (1.0, 1.0),
 (0.0, -0.01),
 (10.5, -1.0),
 (2.5, -2.5)]
  • import re 를 이용하여 정규표현식 모듈을 부릅니다.

  • re.sub 메소드는 세번째 인자의 문자열에서 첫번째 인자 문자열 패턴을 찾아 두번째 인자 문자열로 바꿉니다.

  • re.findall 메소드는 두번째 인자 문자열에서 첫번째 문자열 패턴에 해당하는 문자열을 검색하여 리스트로 반환합니다.

  • \s+ : 공백 문자열 1개 이상 매치

  • \( : ( 문자 매치

  • [-\d\.]* : 음수부호 -, 숫자 또는 소숫점 .이 0개 이상 매치

  • ?\) : 가장 가까운 ) 문자 매치

HTML

웹페이지는 HTML이라는 언어로 작성되어 있습니다. HTML(Hyper Text Markup Language)이란 웹 페이지 작성에 사용되는 마크업(markup) 언어입니다. 마크업 언어란 태그(tag)를 이용하여 문서에 다양한 정보를 표현할 수 있게 해주는 언어입니다. 글자색을 바꾼다든지 그림, 소리를 삽입한다든지 링크를 삽입하는 등 여러 가지 기능들을 가능하게 해줍니다. 웹페이지는 구글 크롬(Google Chrome), 파이어폭스(Firefox), MS 엣지(edge), 사파리, 오페라, 인터넷 익스플로러(Internet Explorer) 등의 웹브라우저를 이용해서 볼 수 있습니다. 태그란 미리 약속된 문자열로 HTML에서는 화살표괄호 <> 사이에 위치합니다. HTML도 계속 발전되어 현재는 HTML5가 사용되고있습니다. HTML 문서의 기본 구조는 다음과 같습니다.

<!DOCTYPE html>
<html>
    <head>
        <title>웹 페이지 제목</title>
        ...
    </head>
    <body>
        <h1>첫번째 제목</h1>
        <p>첫번째 단락</p>
        ...
    </body>
</html>
  • <!DOCTYPE html>은 문서의 제일 위에 한번만 사용하며 HTML5를 사용한다는 표시입니다.

  • <html>은 HTML 문서의 시작을 알리는 태그입니다.

  • <head>는 몸체(<body>)의 메타 정보를 포함합니다. 자바스크립트, 스타일, 제목 등이 들어갑니다.

  • <title>은 웹 페이지 제목을 표시합니다.

  • <body>는 브라우저를 통해 보는 내용들이 들어갑니다.

  • <h1>은 가장 큰 글씨의 제목입니다.

  • <p>는 단락을 나타냅니다.

태그

HTML 태그는 각각의 태그이름이 화살표괄호 <>에 의해 둘러 싸여 있습니다.

<태그이름>태그 안에 들어갈 내용들 ...</태그이름>
  • 대부분의 태그는 <p></p> 같이 쌍으로 이루어져 있습니다.

  • 앞에 있는 태그를 시작 태그(또는 여는 태그)라 하고 뒤에 있는 태그를 종료 태그(또는 닫는 태그)라고 합니다.

  • 닫는 태그는 태그 이름 앞에 슬래시 /를 붙입니다.

HTML 페이지 구조를 도식화하면 다음과 같습니다.

<html>

<head>

<title>페이지 제목</title>

</head>

<body>

<h1>가장 큰 제목</h1>

<p>단락</p>

<p>또 다른 단락</p>

</body>

</html>

HTML 페이지 만들기

  1. 문서 편집기(메모장 또는 notepad++)를 엽니다.

  2. 새로운 문서를 열고 다음과 같이 입력합니다.

    <!DOCTYPE html>
    <html>
    <body>
    
    <h1>첫번째 제목</h1>
    
    <p>이것은 첫번째 단락입니다.</p>
    
    </body>
    </html>
    
  3. MyFirstWebPage.html이라고 저장합니다.

  4. 저장된 MyFirstWebPage.html 파일을 두번 클릭하여 엽니다.

그러면 다음 그림과 같이 보일 것입니다.

기본 태그들

  • 헤딩(heading): 제목을 나타내는 헤딩은 <h1>, <h2>,…, <h6>까지 있습니다. 숫자가 커질수록 글자의 크기가 작아집니다.

  • 링크(link): 다른 곳을 가리키는 링크 태그는 <a>를 사용합니다. 여기서 href를 태그 <a>의 속성(attribute)이라고 합니다.

    <a href="https://www.google.com">구글 링크</a>
    
  • 이미지: 그림을 삽입하는 태그는 <img>를 사용합니다. 여기서 src, alt, width, height<img> 태그의 속성이라고 합니다.

    <img src="images/koala.jpg" alt="cute koala" width="104" height="142">
    

<img> 태그에서 보는 바와 같이 <img> 태그는 종료 태그가 없습니다. 태그 사이에 내용이 없을 경우는 종료 태그가 필요 없습니다. 새로운 줄로 이동하는 태그인 <br> 태그도 종료 태그가 없습니다.

HTML 성분(element)

HTML 성분(element) 또는 요소는 시작 태그, 태그 안의 내용과 종료 태그로 이루어집니다. 즉, 시작 태그부터 종료 태그까지를 태그의 성분이라고 합니다.

<태그이름> 태그 안의 내용 ... </태그이름>

다음은 태그 <p> 성분의 예입니다. 시작태그는 <p>, 내용은 이것은 단락을 나타냅니다., 종료태그는 </p> 입니다.

<p>이것은 단락을 나타냅니다.</p>

HTML 성분은 중첩될 수 있습니다. 다음 예에서 <html> 성분은 <body> 성분 전체로 이루어져 있습니다. <body> 성분은 시작 태그 <body>로부터 종료 태그 </body>까지 전체를 의미합니다.

<!DOCTYPE html>
<html>
<body>

<h1>첫번째 제목</h1>
<p>첫번째 단락</p>

</body>
</html>

HTML 태그는 대소문자를 구분하지 않습니다. 즉, 대문자 <P>와 소문자 <p>는 같은 의미입니다. XHTML에서는 소문자 태그만 유휴하므로 호환을 위해서 소문자 태그를 사용하는 것을 권합니다.

HTML 속성(attribute)

HTML 성분은 속성을 가질 수 있습니다. 속성은 성분의 추가 정보를 제공합니다. 속성은 시작 태그 안에 위치해 있어야 합니다. 속성은 속성이름="속성값" 형식으로 이루어집니다.

  • 링크 태그 <a>는 다음과 같이 태그 내용물이 가리키는 주소 href 속성을 갖습니다.

    <a href="http://www.google.com">구글 링크</a>
    
  • 이미지 태그 <img>는 이미지 파일의 위치를 가리키는 속성 src 및 가로, 세로의 크기를 나타내는 width, height를 갖습니다. 크기의 기본 단위는 픽셀입니다.

    <img src="images/koala.jpg" width=500 height=500>
    
  • 단락 태그 <p>의 속성 style을 이용해 폰트, 글자색, 크기등을 지정할 수 있습니다.

    <p style="color:red">여기는 빨간색의 단락입니다.</p>
    
  • html 태그의 속성 중 lang을 이용하면 작성된 문서의 언어를 지정할 수 있습니다.

    <html lang="ko">
        ....
    </html>
    
  • <p> 태그의 속성 중 title은 마우스를 단락 위에 올려 놓았을 때 보이는 문구입니다. 이것을 툴팁(tooltip)이라고도 부릅니다.

    <p title="간단한 단락 설명">이것은 단락입니다.</p>
    

CSS

CSS는 Cascading Style Sheets로 HTML 성분들이 보여지는 모습을 지정합니다. CSS를 이용하면 웹 페이지의 모습을 일관성있게 변경할 수 있으며 성분들을 개별적으로 수정하는 시간을 줄일 수 있습니다. 여기서는 CSS가 어떤 것이지에 대한 맛보기 정도로만 다룹니다. 자세한 것은 w3schools.com을 참조합니다.

CSS 접근

CSS를 구현하는 방법은 크게 3가지로 나눌 수 있습니다.

  • 인라인 CSS: HTML 태그 속성으로 구현합니다.

  • 내부 CSS: HTML head 안에 구현합니다.

  • 외부 CSS: CSS 파일을 이용합니다.

인라인 CSS

HTML 성분의 속성으로 설정하는 것입니다. 다음과 같이 h1 태그의 속성 style을 이용해 글자색을 파란색으로 변경합니다.

<h1 style="color:blue;">파란색 제목</h1>

파란색 제목

내부 CSS

HTML 성분 <head> 안에 <style> 성분을 지정해서 <body> 전체에 영향을 미치게 할 수 있습니다.

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {background-color: powderblue;}
      h1   {color: blue;}
      p    {color: red;}
    </style>
  </head>
  <body>
    <h1>제목</h1>
    <p>이 부분이 단락입니다.</p>
  </body>
</html>
  • <style> 태그를 이용해서 CSS를 설정합니다.

  • body {background-color: powderblue;} body 태그의 속성 background-color의 값을 powderblue로 지정해서 배경색을 변경합니다. 다른 태그의 속성도 마찬가지로 적용합니다.

외부 CSS

외부 CSS를 이용하면 웹 사이트 전체의 외관을 변경할 수도 있습니다. 다음과 같이 head 태그 안에 link 태그를 이용해서 CSS 파일의 위치를 href 속성을 이용해서 알려줍니다.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="styles.css">
  </head>
  <body>
    <h1>제목</h1>
    <p>이 부분이 단락입니다.</p>
  </body>
</html>

style.css 파일을 HTML 페이지 파일과 같은 곳에 하나 만들고 다음과 같이 CSS를 입력합니다.

body {
    background-color: powderblue;
}
h1 {
    color: blue;
}
p {
    color: red;
}

이렇게 하면 위에서 내부 CSS와 같은 외관을 갖게됩니다.

CSS 선택자

css 구문은 다음과 같습니다. 선택자 다음에 중괄호를 열고 변경하고자 하는 속성이름과 속성값들을 나열한 후 중괄호를 닫으면 됩니다. 중괄호 안에서 속성들을 구분하는 것으로 세미콜론 ;을 사용합니다.

선택자 {속성1: 속성값1; 속성2: 속성값2; ....}

예를 들면 <p> 태그 글자를 빨간색으로 크기는 large로 변경하려면 다음과 같이 하면 됩니다.

p {color: red; font-size: large}

선택자로는 태그, 클래스, 아이디, 태그 속성 등을 사용할 수 있습니다.

CSS id 속성

HTML 성분을 유일한 id를 설정하여 접근할 수 있습니다. id 속성은 다음과 같이 합니다.

<p id="p01">I am different</p>

특정 id 성분의 스타일 설정은 id 속성값 앞에 #을 붙이면 됩니다.

#p01 {
    color: blue;
}

class 속성

원하는 이름의 스타일의 이름을 지정하기 위해서 class 속성을 지정합니다.

<p class="error">I am different</p>

스타일 시트에서는 태그 이름과 .을 붙여서 사용합니다. 태그 이름을 붙이지 않아도 되는데 이때는 지정된 클래스 이름을 갖는 모든 성분에 적용이 됩니다.

p.error {
    color: red;
}

CSS 폰트(Font)

다음과 같이 각 성분의 폰트와 글자색, 글자 크기를 조정할 수 있습니다.

<!DOCTYPE html>
<html>
<head>
<style>
h1 {
    color: blue;
    font-family: verdana;
    font-size: 300%;
}
p  {
    color: red;
    font-family: courier;
    font-size: 160%;
}
</style>
</head>
<body>

<h1>This is a heading</h1>
<p>This is a paragraph.</p>

</body>
</html>

CSS 테두리

border 속성을 이용해서 각 성분의 테두리를 설정합니다.

p {
    border: 1px solid powderblue;
}

CSS 패딩

padding 속성을 이용해서 텍스트와 테두리 사이의 간격(패딩)을 설정합니다.

p {
    border: 1px solid powderblue;
    padding: 30px;
}

CSS 마진

margin 속성을 이용해서 성분의 테두리 바깥 간격(마진)을 설정합니다.

p {
    border: 1px solid powderblue;
    margin: 50px;
}

웹페이지 읽기

웹페이지에는 많은 양의 정보들이 담겨있습니다. 이러한 정보를 활용하는 방법에 대해서 알아봅니다.

웹페이지 접근

웹페이지는 HTML로 이루어졌기 때문에 파이썬을 이용해서 파일로 저장하고 처리할 수 있습니다.

파일로 저장하는 방법은 urllib.request 모듈의 urlretrieve 함수를 사용할 수 있습니다.

In [59]: from urllib import request
   ....: 
   ....: url = 'http://sejong.korea.ac.kr'
   ....: request.urlretrieve(url, filename='sejong.html')
   ....: 

다른 방법으로 파일 객체로 읽어서 처리할 수 있습니다. urlopen 함수를 이용하면 스트림으로 파일을 읽어 들입니다. urlopen 함수는 Request 객체를 인자로 넘겨서 Response 객체를 반환합니다. Response 객체는 파일같은 객체로 read() 메소드를 사용할 수 있습니다.

In [60]: from urllib import request
   ....: 
   ....: url = 'http://sejong.korea.ac.kr'
   ....: with request.urlopen(url) as resp:
   ....:   print(resp.read(100).decode('utf-8'))
   ....: 

read 함수는 바이트형을 반환하므로 decode 함수를 이용해서 유니코드로 디코딩을 해줘야 한글을 볼 수 있습니다.

예제: HTML에서 자료 추출

다음은 www.worldclimate.com 에서 제공하는 1954년부터 1980년 사이의 서울 평균 강수량 자료입니다.

http://www.worldclimate.com/cgi-bin/data.pl?ref=N37E126+2100+4710801G1

여기서 테이블에 있는 평균 기온 자료들을 불러오도록 합니다. 우선 seoul.html 파일로 저장합니다.

In [61]: from urllib import request
   ....: 
   ....: url = 'http://www.worldclimate.com/cgi-bin/data.pl?ref=N37E126+2100+4710801G1'
   ....: request.urlretrieve(url, filename='src/seoul.html')
   ....: 

urllib.parse.quote를 이용하여 한글을 url 인코딩해주어야 합니다. url 인코딩은 아스키 문자만 가능합니다.

seoul.html 파일 중에서 다음과 같이 시작하는 부분이 표부분입니다.

<p>Weather station <strong>SEOUL INT. AIRPORT</strong> ...
<table width=90% border=1>
<tr><th align=right><th>  Jan<th>  Feb<th>  ... <br>
<tr><td> mm <td align=right> 20.7 <td align=right> 27.9 ... <br>
<tr><td>inches <td align=right>0.8<td align=right>1.1 ... <br>
In [62]: with open('src/seoul.html', 'r') as infile:
   ....:   rainfall = []
   ....:   for line in infile:
   ....:     if 'Weather station' in line:
   ....:       station = line.split('</strong>')[0].split('<strong>')[1]
   ....:     if '<td> mm <td' in line:
   ....:       data = line.split('<td align=right>')
   ....: 
   ....: data
   ....: 
Out[62]: 
['<tr><td> mm ',
 ' 20.7 ',
 ' 27.9 ',
 ' 49.0 ',
 '104.9 ',
 ' 88.4 ',
 '150.8 ',
 '383.5 ',
 '262.7 ',
 '160.4 ',
 ' 48.5 ',
 ' 43.0 ',
 ' 24.4 ',
 '1364.8<br> \n']

data 에서 <br>... 부분을 제거합니다.

In [63]: data[-1] = data[-1].split('<br>')[0]
   ....: data
   ....: 
Out[63]: 
['<tr><td> mm ',
 ' 20.7 ',
 ' 27.9 ',
 ' 49.0 ',
 '104.9 ',
 ' 88.4 ',
 '150.8 ',
 '383.5 ',
 '262.7 ',
 '160.4 ',
 ' 48.5 ',
 ' 43.0 ',
 ' 24.4 ',
 '1364.8']

그리고 실수형으로 변경하여 저장하면 됩니다.

In [64]: data = [float(x) for x in data[1:]]
   ....: data
   ....: 
Out[64]: 
[20.7,
 27.9,
 49.0,
 104.9,
 88.4,
 150.8,
 383.5,
 262.7,
 160.4,
 48.5,
 43.0,
 24.4,
 1364.8]

더 전문적인 웹페이지 분석 라이브러리로는 Beautifulsoup, lxml, Selenium 등이 있습니다.

requests 모듈 이용 웹 페이지 읽기

requests 모듈을 이용하여 웹 페이지를 읽어 올 수 있습니다. requests 모듈은 http 요청및 응답을 처리하는 편리한 방법들을 제공합니다.

다음(daum) 홈페이지 출력

다음(daum) 홈페이지에 접속해서 HTML 문서를 가져와 화면에 출력하는 예입니다.

import requests

resp = requests.get('http://daum.net') # 웹 사이트 접속

if (resp.status_code == requests.codes.ok): # 응답이 정상
    html = resp.text # 웹 페이지 읽기
    print(html.split('\n')[0:10]) # 웹 페이지 10줄 출력
['<!DOCTYPE html>', '<html lang="ko" class="">', '<head>', '<meta charset="utf-8"/>', '<title>Daum</title>', '<meta property="og:url" content="https://www.daum.net/">', '<meta property="og:type" content="website">', '<meta property="og:title" content="Daum">', '<meta property="og:image" content="//i1.daumcdn.net/svc/image/U03/common_icon/5587C4E4012FCD0001">', '<meta property="og:description" content="나의 관심 콘텐츠를 가장 즐겁게 볼 수 있는 Daum">']
  • requests.get(사이트주소)은 요청 메시지의 get 메소드를 이용하여 사이트 주소의 페이지를 요청합니다.

  • resp.status_code는 응답 객체의 상태를 나타내는 것으로 정상이면 200을 반환합니다.

  • requests.codes.ok는 정상 코드 200을 나타내는 상수입니다.

  • resp.text은 웹 페이지의 html 페이지를 반환합니다.

  • 클라이언트의 잘못된 요청에 대해 서버는 여러 가지 에러를 반환할 수 있습니다.(에러 코드 4xx, 5xx) 이러한 응답에 대해서 Response.raise_for_status() 메소드를 이용해 예외를 발생시킬 수 있습니다.

구글 검색 결과 출력

구글에 접속해서 원하는 단어를 검색하여 출력할 수 있습니다. 구글에서 검색어를 입력하면 주소창에 search?q=검색어와 같은 문자열이 입력되어 있는 것을 확인할 수 있습니다. 이것을 이용해 다음과 같이 직접 검색어를 사이트 주소와 함께 입력해 주면 검색 결과를 얻을 수 있습니다.

import requests

resp = requests.get('https://google.co.kr/search?q=인공지능')
if (resp.status_code == requests.codes.ok):
    html = resp.text
    print(html[:100], '...중간 생략...', html[-100:], sep='\n')
<!doctype html><html itemscope="" itemtype="http://schema.org/SearchResultsPage" lang="ko"><head><me
...중간 생략...
"/client_204?&atyp=i&biw="+a+"&bih="+b+"&ei="+google.kEI);}).call(this);})();</script></body></html>

인공지능이란 단어를 검색한 결과를 출력한 것입니다. 내용이 너무 길어 중간 생략을 했습니다.

파일로 저장하려면 다음과 같이합니다.

with open('g.html', 'w', encoding='utf-8') as f:
  f.write(resp.text)

직접하기

  • 다음(daum) 사이트에서 “날씨”를 검색하여 결과를 출력하시오.

  • 검색 결과를 파일로 저장하여 웹 브라우저로 열어 보세요.

파싱(Beautiful Soup)

Beautiful Soup 모듈을 이용해서 웹 페이지에서 필요한 정보들을 찾아낼 수 있습니다. 포털 사이트에서 주요 뉴스 제목을 찾아내거나 검색 사이트에서 원하는 단어를 검색한 결과를 볼 수 있습니다.

설치

pip install beautifulsoup4 # 또는
conda install -c anaconda beautifulsoup4 # 아나콘다를 이용할 경우

BeautifulSoup 웹페이지 파싱

웹 문서를 입력받아 bs객체를 만든다. bs 객체를 이용하여 필요한 정보들에 접근해서 원하는 것들을 수집할 수 있습니다. 원하는 성분으로 접근하는 방법은 여러 가지가 있으나 select() 메소드를 이용하는 방법이 있습니다. select 메소드의 인자는 CSS(Cascading Style Sheets) selector 조합 문자열을 사용합니다. css selector에 대한 자세한 설명은 W3 Schools CSS Selector Reference를 참조합니다. 다음은 몇 가지 예를 보여줍니다.

html 성분(element 또는 tag)은 다음과 같은 형식으로 이루어져 있습니다.

<tag_or_element attribute="value">text</tag_or_element>

다음은 html 예제의 일부입니다.

<div class="intro"> <!-- div는 성분, class는 속성, "intro"는 class 속성값입니다.-->
<p>My name is Donald <span id="Lastname">Duck.</span></p>

<p id="my-Address">I live in Duckburg</p>

<p>I have many friends:</p>
</div>

Selector

예제

설명

.class

.intro

class="intro"인 모든 성분 선택

#id

#firstname

id="firstname"인 모든 성분

*

*

모든 성분 선택

element *

div *

div 안에 있는(자손) 모든 성분 선택. 중복하면서 선택됩니다.

element

p

<p> 성분 모두 선택됩니다

element1, element2

div, p

<div> 또는 <p>를 갖는 모든 성분 선택. 중복을 허락하지 않습니다.

element1 element2

div p

<div> 성분 안에(자손) 모든 <p> 성분 선택

element1 > element2

div > p

부모가 <div>인 모든 <p> 성분 선택

element1 + element2

div + p

<div>와 형제이며 <div> 바로 아래쪽에 붙어 있는 <p> 성분 선택

element1 ~ element2

p ~ ul

<p> 와 형제이며 <p> 아래쪽에 있는 모든 <ul> 성분들 선택

[attribute]

[target]

속성이 target인 모든 성분 선택

[attribute=value]

[target=_blank]

속성이 target이고 target의 값이 _blank인 모든 성분 선택

[attribute~=value]

[title~=flower]

title 속성을 갖고 속성값이 flower를 포함하는 모든 성분들 선택

[attribute|=value

[lang|=en]

속성이 lang이고 속성의 값이 en 또는 en- 모든 성분 선택 | |

:nth-of-type(n)

p:nth-of-type(2)

<p>의 부모 아래에 있는 두번째 <p>성분 선택

import bs4

html = "<html><head><title>제목</title></head><body>...생략...</body></html>"
bs = bs4.BeautifulSoup(html, 'html.parser')
import bs4

html = """
<html>

<head>
</head>

<body>
<h1>Welcome to My Homepage</h1>
<div class="intro">
<p>My name is Donald <span id="Lastname">Duck.</span></p>

<p id="my-Address">I live in Duckburg</p>

<p>I have many friends:</p>
</div>

<ul id="Listfriends">
<li>Goofy</li>
<li>Mickey</li>
<li>Daisy</li>
<li>Pluto</li>
</ul>

<p>All my friends are great!<br>
But I really like Daisy!!</p>

<p lang="it" title="Hello beautiful">Ciao bella</p>

<h3>We are all animals!</h3>

<p><b>My latest discoveries have led me to believe that we are all animals:</b></p>

<table>
<thead>
<tr>
<th>Name</th>       <th>Type of Animal</th>
</tr>
</thead>
<tr>
<td>Mickey</td>     <td>Mouse</td>
</tr>
<tr>
<td>Goofey</td>     <td>Dog</td>
</tr>
<tr>
<td>Daisy</td>      <td>Duck</td>
</tr>
<tr>
<td>Pluto</td>      <td>Dog</td>
</tr>
</table>

</body>
</html>
"""

bs = bs4.BeautifulSoup(html, 'html.parser')
bs.select('table')
[<table>
 <thead>
 <tr>
 <th>Name</th> <th>Type of Animal</th>
 </tr>
 </thead>
 <tr>
 <td>Mickey</td> <td>Mouse</td>
 </tr>
 <tr>
 <td>Goofey</td> <td>Dog</td>
 </tr>
 <tr>
 <td>Daisy</td> <td>Duck</td>
 </tr>
 <tr>
 <td>Pluto</td> <td>Dog</td>
 </tr>
 </table>]

다음 html 문서를 이용해서 예제들을 살펴봅니다.

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister-act" id="link3">Tillie</a>;
<a href="https://example.com/tillie" class="sister">Secure Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
<div class="box clever">
    <p>Box Office</p>
    <p>Clever guy</p>
    <p>Gorgeous actress</p>
</div>
</body>
</html>
"""

성분들을 찾습니다.

soup = bs4.BeautifulSoup(html_doc, 'html.parser')

태그 이름이 title인 모든 성분을 찾습니다.

soup.select('title')
[<title>The Dormouse's story</title>]

p의 부모의 자식 중 3번째 p를 찾습니다.

soup.select("p:nth-of-type(3)")
[<p class="story">...</p>]

원래는 <p>Gorgeous actress</p>도 찾아야 하는데 버그인 것 같습니다. CSS에서는 정상적으로 작동하는 것을 알 수 있습니다.

직접하기

  • soup.select("p:nth-of-type(3)")를 CSS에서 확인해봅니다.

성분 밑의 성분 찾기

body의 자손 중 a 성분을 모두 찾습니다.

soup.select("body a")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

html 자손으로 head 자손 중 title 성분을 모두 찾습니다.

soup.select("html head title")
[<title>The Dormouse's story</title>]

성분 바로 밑의 성분 찾기

head 성분의 자식 중 title 성분을 모두 찾습니다.

soup.select("head > title")
[<title>The Dormouse's story</title>]

p의 자식 중 a인 성분 모두 찾습니다.

soup.select("p > a")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

p의 자식 중 a 성분들 중에서 2번째 성분을 찾습니다.

soup.select("p > a:nth-of-type(2)")
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

p의 자식 중 idlink1인 성분을 찾습니다.

soup.select("p > #link1")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

body 자식 중 a 성분을 찾지만 없으므로 빈 리스트가 됩니다.

soup.select("body > a")
[]

같은 수준의 성분들 찾기

idlink1인 태그의 형제들 중 class 값이 sister인 모든 성분들을 찾습니다.

soup.select("#link1 ~ .sister")
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

idlink1인 태그의 형제들 중 idlink1인 성분 바로 아래 붙어있는 class 값이 sister인 성분을 찾습니다.

soup.select("#link1 + .sister")
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

CSS 클래스에 의한 성분 찾기

클래스 값이 sister인 성분들 모두 찾습니다.

soup.select(".sister")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

클래스 속성값이 단어 sister를 포함하는 모든 성분을 찾습니다.

soup.select("[class~=sister]")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

|는 뒤에 하이픈(-)이 있어도 찾습니다.

soup.select("[class|=sister]")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

클래스 속성값이 si를 포함하는 모든 성분을 찾습니다. ~와 다른 점은 ~는 단어 전체를 찾고 *는 부분 문자열이 포함된 것을 찾습니다.

soup.select("[class*=si]")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

ID에 의한 성분 찾기

idlink1인 성분을 찾습니다.

soup.select("#link1")
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

아이디가 link2이며 태그가 a인 성분을 찾습니다.

soup.select("a#link2")
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

속성에 의해 찾기

href 속성을 갖는 모든 a 태그들을 찾습니다.

soup.select('a[href]')
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

속성값에 의한 찾기

a 태그의 속성 href의 값이 http://example.com/elsie인 모든 성분을 찾습니다.

soup.select('a[href="http://example.com/elsie"]')
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

href 속성값 중 http로 시작하는 a 태그들 모두를 찾습니다.

soup.select('a[href^="http"]')
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
 <a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

태그 a의 속성이 href이고 href의 속성값이 tillie로 끝나는 성분들을 찾습니다.

soup.select('a[href$="tillie"]')
[<a class="sister-act" href="http://example.com/tillie" id="link3">Tillie</a>,
 <a class="sister" href="https://example.com/tillie">Secure Tillie</a>]

속성 href의 값이 .com/el을 포함하는 태그 a들을 모두 찾습니다.

soup.select('a[href*=".com/el"]')
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

네이버 금융 사이트에서 헤드라인 뉴스 제목 발췌

import requests, bs4

resp = requests.get('http://finance.naver.com/')
resp.raise_for_status()

resp.encoding='euc-kr'
html = resp.text

bs = bs4.BeautifulSoup(html, 'html.parser')
print(bs.prettify()[0:100], "\n.\n.\n.\n", bs.prettify()[-100:])

tags = bs.select('div.news_area h2 a') # 헤드라인 뉴스 제목
title = tags[0].getText()
print("헤드라인 제목: ", title)
<html lang="ko">
 <head>
  <title>
   네이버 금융
  </title>
  <meta content="text/html; charset=utf-8" h
.
.
.
 , 이미지 리플레시
jindo.$Fn(mainPageDomReadyFn).attach(document, "domready");
  </script>
 </body>
</html>
헤드라인 제목:  "한국 기업 이익 증가 추세 지속"…유..

직접하기

  • 다음(daum) 사이트의 실시간 이슈 검색어를 추출해 보시오.

  • 네이버 사이트에서 코스피 실시간 지수를 출력하시오.

  • 네이버 환율 사이트에서 엔화 현찰 살때 팔때 환율을 출력하시오. iframe으로 연결되어 있어서 사이트 주소를 정확히 입력해야 합니다.

연습문제

  1. 다음 내용을 파일 human_evolution.txt로 저장하세요.

    Species              Lived when      Adult        Adult       Brain volume
                         (mill. yrs)     height (m)   mass (kg)   (cm**3)
    -------------------------------------------------------------------------------
    H.habilis           2.2-1.6          1.0-1.5      33-55       660
    H.erectus           1.4-0.2          1.8            60        850(early)-1100(late)
    H.ergaster          1.9-1.4          1.9            NA        700-850
    H.heidelbergensis   0.6-0.35         1.8            60        1100-1400
    H.neanderthalensis  0.35-0.03        1.6          55-70       1200-1700
    H.sapiens sapiens   0.2-present      1.4-1.9      50-100      1000-1850
    H.floresiensis      0.10-0.012       1.0            25        400
    -------------------------------------------------------------------------------
    
    Source: http://en.wikipedia.org/wiki/Human_evolution
    

    저장된 파일을 읽어서 중첩된 사전 humans에 저장하고 출력해보세요. humans의 키(key)는 인류의 종류로 하고 대응되는 값은 해당하는 행의 정보들(when, height, weight, brain)을 키로하는 사전으로 하세요. 예를 들어 humans['H.erectus']['weight']60이 되어야 합니다.