# 파이썬 웹 크롤링

안녕하세요 코드사기꾼입니다.

오늘은 파이썬 urllib을 이용하여 웹상에서 사진을 가져오는 방법에대한 설명을 드리도록 하겠습니다.

 

 


1. 이미지 다운로드 하기

 

크롤링을 하기 전에 알아야 할것은 이미지가 웹상 특정 경로에 존재하는지에 관한 유무입니다. 저는 사진을 크롤링하기 위하여 네이버 영화의 마약왕 영화 리뷰 사이트를 참고하였습니다.

사이트 링크:

https://movie.naver.com/movie/bi/mi/basic.nhn?code=157297

 

마약왕

“애국이 별게 아니다! 일본에 뽕 팔믄 그게 바로 애국인기라!”마약도 수출하면 애국이 되던 1970년대 ...

movie.naver.com

 

사이트에 접속하시면 보이는 바와 같이 사진이 노출되는데요

 

 

왼쪽상단 빨간색 박스에 사진이 존재함

 

개발자 도구를 통하여 저 사진의 URL을 알아보도록 하겠습니다. 먼저 inspect 도구를 켜서 사진을 클릭해줍니다.

 

img 태그에 src에 사진의 url이 담겨있다.

 

그러면 사진과 같이 img태그안에 src 항목에 사진의 원본 url이 담겨있는것을 확인할 수 있습니다.

저 URL을 복사하시고 아래와 같은 소스 코드를 작성합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import urllib.request
from bs4 import BeautifulSoup
 
print('Beginning file download with urllib2...')
 
url = 'https://movie.naver.com/movie/bi/mi/basic.nhn?code=157297'
req = urllib.request.Request(url)
res = urllib.request.urlopen(url).read()
 
soup = BeautifulSoup(res,'html.parser')
soup = soup.find("div",class_="poster")
#img의 경로를 받아온다
imgUrl = soup.find("img")["src"]
 
#urlretrieve는 다운로드 함수
#img.alt는 이미지 대체 텍스트 == 마약왕
urllib.request.urlretrieve(imgUrl, soup.find("img")["alt"]+'.jpg')
cs

 

위 코드를 실행하면 실행된 스크립트가 있는 폴더에 아래와 같이 사진이 생성됩니다!

 


2. 실행 결과

 

사진이 스크립트가 실행된 경로에 저장되었습니다.

 

사진파일 실행 결과

 

사진이 좀 작네요 ㅋㅋㅋ 그래도 확실하게 이미지를 받아올 수 있었습니다.

# 파이썬 웹 크롤링

 

안녕하세요 코드사기꾼입니다.
저번 강의에 이어서 오늘은 다른 사이트를 크롤링해보는 시간을 갖도록 하겠습니다.

오늘의 대상 사이트는 각종 대회 및 공모전이 등록되는 사이트인 씽굿(ThinkGood)입니다. 항상 공모전은 하고 싶은데 매일 찾아보기는 귀찮잖아요? 하지만 공모전은 타이밍이라 자주 모니터링 하지 않으면 적절한 타이밍에 등록을 하지 못할 수도 있습니다. 공모전의 내용을 크롤링해서 띄워주는 프로그램이 있다면 굉장히 유요하게 쓰일 수 있겠죠?

 


 

1. 사이트 구조파악

크롤링을 하기에 앞서 가장먼저 선행되어야 할 것이 무엇이냐고 물어보면 저는 사이트 구조파악이라고 말할 것입니다.
사이트에 관한 구조를 모르면 원하는 url을 순환하면서 크롤링을 할 수 없을 뿐만 아니라 원하는 div에서원하는 콘텐츠를 크롤링해올 수 없기 때문이죠

씽굿에 접속해서 게임/소프트웨어 공모전으로 들어가 봅니다.
사이트 링크:
https://www.thinkcontest.com/Contest/CateField.html?c=12

 

씽굿-대한민국 대표 공모전 미디어 씽굿

부지런과 즐김 나의 삶의 철학은 부지런과 즐김이다. 부지런하고 즐기는 사람은 하늘도 못 말린다고 한다. ‘세 사람이 가면 그 중엔 반드시 나의 스승이 있다(三人行必有我師)’고 했는데, 나 또한 남의 좋은 점을 본받지 않고 스스로 잘난 척 옹고집으로 산 것을 심히 후회하노라. ‘아는 자는 좋아하는 자만 못하고, 좋아하는 자는 즐기는 자만 못하다(知者不如好者 好者不如樂者)’. 필자는 지금 이 나이에도 생물수필을 쓰고 있나니, 누가 시켜서라면 죽어도 안(못)할

www.thinkcontest.com

 


 

 

씽굿 접속 화면

오늘은 간단하게 크롤링 봇을 만들거라서 게임/소프트웨어 공모전 1페이지에 노출되어있는 공모전만 파싱하도록 하겠습니다. 공모전 분류별, 페이지별로 파싱해오고 링크 및 이미지를 파싱하는 방법에 대해서는 다음에 다시 업로드하도록하겠습니다.

 

 

 

개발자도구(f12)를 켜서 가장 첫번째 공모전의 태그를 확인하겠습니다. a태그로 감싸져 있는데요 이 div의 하위 태그로 구성되어 있는 것을 확인 할 수 있습니다. div클래스는 contest-title special이네요 그런데 special이라는 것은 special 아이콘이 붙은 공모전에만 들어가는 클래스이기 때문에 모든 공모전을 파싱하려면 저대로 파싱하면 아래와 같은 결과가 나옵니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import urllib.request
from bs4 import BeautifulSoup
 
 
url = "https://www.thinkcontest.com/Contest/CateField.html?c=12"
req = urllib.request.urlopen(url)
res = req.read()
 
soup = BeautifulSoup(res,'html.parser')
contests = soup.find_all("div",class_="contest-title special")
 
#a 함수는 html포맷데이터에서 a태그만을 파싱한다.
#a 함수를 쓰는 이유는 contests-title div가 다른 태그에 여러 텍스트를 가지고있기 때문이다.
#a를 제거하고 테스트해보는것을 추천드립니다.
keywords = [each_line.a.get_text().strip() for each_line in contests]
print(keywords)
 
cs

 

special 클래스의 공모전만 파싱됨

 

그러면 모든 공모전을 파싱하려면 어떻게 해야할까요? 일반적인 공모전이 담겨있는 div의 클래스명을 확인해보겠습니다.

 

일반 공모전

일반공모전은 contest-title이라고 되어있습니다. 그러면 클래스 이름에 contest-title이라고 적으면 될까요?

네 그것도 됩니다. 왜냐하면 BeautifulSoup은 정규표현식처럼 일정부분 맞게 들어가는 클래스명은 참이라고 판단하기 때문입니다.
하지만 오늘은 다른 방법을 알려드리고자 하기 때문에 새로운 방식으로 시도해보도록 하겠습니다.

먼저 정규표현식 라이브러리인 re를 import할것입니다. 그리고 정규식을 컴파일 하기위해 re.compile함수를 사용하겠습니다. 그러면 정규표현식은 어떻게 구성하면 될까요? contest-title을 포함하고 뒤에 스페셜을 있어도 되고 없어도 된다고 명시하면 될 것 같습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib.request
import re
from bs4 import BeautifulSoup
 
url = "https://www.thinkcontest.com/Contest/CateField.html?c=12"
req = urllib.request.urlopen(url)
res = req.read()
 
soup = BeautifulSoup(res,'html.parser')
#regex: .*(문자의 유형과 상관없이 그것이 반복되면 매칭), ?(그것이 있을 수도 없을 수도 있음)
#.*?(그것은 문자의 반복이지만 있을 수도 없을 수도 있음)
#re.DOTALL == .이 개행문자를 포함한 모든문자를 의미하게함
contests = soup.find_all("div",class_=re.compile("contest-title.*?",re.DOTALL))
keywords = [each_line.a.get_text().strip() for each_line in contests]
print(keywords)
cs

 

최종 결과

 

# 상속과 오버라이딩

 

1. 상속

 

상속은 사전적 정의에 따르면 부모나 친족적 관계에 있는 사람의 유산을 물려 받는 제도입니다. 클래스에서의 상속은 부모의 유산을 자식이 물려 받듯이 부모 클래스의 멤버와 메소드를 자식 클래스가 물려받을 수 있습니다. 이런 경우는 흔히 있습니다. 예를 들어 라이브러리를 제작할 때 특정 자료구조를 만들어서 모든 클래스가 이 자료구조를 이용하게 하고 싶다면 클래스의 상속을 이용하게 하면 됩니다.  추가적으로 파이썬에서 기본 제공되는 자료형 또한 클래스이기 때문에 자신만의 독특한 자료형을 만들 수 도 있겠죠.

 

상속을 이해하고자 할 때 가장 쉬운방법은 기존의 클래스를 이용하되, 내가 원하는 멤버와 메소드를 추가하는 것으로 이해 하는 것입니다.

 

클래스의 상속

먼저 총이라는 간단한 클래스가 있다고 가정해 보겠습니다. 클래스라는 것은 유형, 무형으로 존재하는 어떤 것을 코드로 나타내는 것입니다.  그러면 총(Rifle)이라는 객체는 파괴력, 탄창, 현재 남은 총알 등의 속성을 가지고 있을 테고, 기능으로는 장전이나 발사가 있을 수 있습니다.

 

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
class Rifle:
    def __init__(self,Mag, atk):
        self.__Mag = Mag
        self.__leftMag = Mag
        self.__atk = atk
    
    @property
    def Mag(self):
         return self.__Mag
    
    @property
    def leftMag(self):
        return self.__leftMag
    
    @leftMag.setter
    def leftMag(self, num):
        self.__leftMag = num
    
    @property
    def atk(self):
        return self.__atk
    
    @atk.setter
    def atk(self,num):
        self.__atk = num
        
    def Reload(self):
        self.__leftMag = self.__Mag
        
    def Shoot(self):
        if self.__leftMag:
            self.__leftMag -= 1
        else:
            print("장전하세요")
cs

 

이 클래스는 Rifle입니다. 제가 앞서 언급한 대로 생성자에 탄창, 남은 총알, 파괴력등의 요소를 만들었습니다. 또한 상속을위해 각 클래스멤버의 property와 setter를 작성하여 직접 멤버변수에는 접근하지 못하지만, 메소드를 통하여 가능하게 구현하였습니다. 탄창(Mag)은 처음 인스턴스 생성 이후 오염을 방지하기위해 setter에서 제외했습니다. Rifle클래스의 메소드로는 총이 실제로 가지고있는 기능인 발사(Shoot), 장전(Reload) 등을 포함 시켰습니다. 같은 클래스 안에서는 property나 setter를 이용하여 멤버에 접근할 필요가 없기때문에 직접접근하는 방식으로 구현하였습니다.(장전과 발사에 있는 self.__leftMag)

 

만약 총이라는 클래스를 상속받아 저격총이라는 클래스를 만들고자 하면 총이 가지고 있는 속성은 그대로 유지할 것입니다. 하지만 총이라는 클래스에는 망원경이라는 요소가 없기 때문에 속성을 하나 추가하고 망원경 조준이라는 기능을 추가해야겠죠.  그런 의미에서 멤버 변수에 Zoom을 추가하였고 메소드로는 increaseAtk을 넣어 Zoom과 파괴력을 곱해 다시 파괴력에 저장하는 코드를 작성하였습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ScopeRifle(Rifle):
    def __init__(self,Mag,atk):
        super().__init__(Mag,atk)
        self.__Zoom = 1
    
    @property
    def Zoom(self):
        return self.__Zoom
    
    @Zoom.setter
    def Zoom(self,num):
        self.__Zoom *= num
    
    def increaseAtk(self):
        self.atk *= self.__Zoom
cs

 

__init__을 보면 super().__init__이라는 구문이 있습니다. 이는 부모클래스의 생성자 때문에 필요한 구문입니다. 생성자 규칙에 따라 부모클래스는 탄창과 파괴력이라는 멤버를 필요로 합니다. 이 멤버를 채워주지 않으면 에러가 발생합니다. 

아래의 코드를 실행시키고 각각의 메소드를 테스트 해보겠습니다.

 

1
2
rifle = Rifle(5,10)
sniper = ScopeRifle(5,10)
cs

 

 

Rifle 클래스에 정의되어 있는 내용은 잘 동작하는 것을 확인하였습니다.  rifle을 상속받은 sniper도 잘 동작하는 것을 확인 할 수 있습니다. 

이번에는 sniper클래스의 고유 기능을 테스트 해보도록 하겠습니다.

 

 

제가 의도한 대로 잘 동작하고 있습니다.  atk는 부모클래스의 멤버이면서 property지만 setter를 같이 작성했기 때문에 sniper에서 접근하고 같을 변경시킬 수 있었습니다. public 하게 구현하면 사실 이런 고생은 안해도 됩니다. 하지만 객체지향 프로그래밍에서는 getter setter를 제외하고는 클래스 멤버에대한 외부 클래스의 접근을 금하고 있기때문에 property 설정을 꼭 하셔야 합니다.

 

 


 

 

2. 오버라이딩

 

총과 저격총 사이에는 명확하지 않지만 가끔씩  상속받은 클래스가 부모의 기능을 똑같이 가지고 있지만 구현 방법이 다를 때가 존재합니다. 이런 경우에는 같은 이름의 메소드를 작성하고 다른 기능을 수행하게 해야합니다. 예를 들어 산탄총의 경우를 생각해보겠습니다. 일반 총은 발사당 1개의 총알이 소비되지만 산탄총은 3발이 나간다고 칩시다. 산탄총도Shoot이라는 메소드가 당연히 필요하지만 부모 클래스의 Shoot과 다르게 동작해야 되겠죠? 파이썬에서는 부모 클래스의 메소드와 같은 이름의 메소드를 자식 클래스에 작성하여 부모 클래스의 메소드를 덮어버리는 방법이 있습니다. 이를 우리는 오버라이딩이라고 합니다.

 

1
2
3
4
5
6
7
class ShotGun(Rifle):
    def __init__(self,Mag,atk):
        super().__init__(Mag,atk)
    
    def Shoot(self):
        if self.leftMag:
            self.leftMag -= 3
cs

 

위의 코드는 Rifle클래스를 상속받아 만든 ShotGun클래스입니다.  ShotGun은 Rifle과 모든게 같지만 발사당 3개의 총알이 소비된다고 했었죠? 그 내용을 Shoot이라는 메소드를 정의하고 그 안에 구현하면 됩니다.

부모 클래스에 Shoot이라는 메소드가 있다고 하더라도 오버라이딩을 통해 이를 덮어 쓸 수 있습니다. 실제로 이 코드가 잘 작동하는지 확인 해보겠습니다.

 

 

코드가 정상적으로 작동하는 것을 확인 할 수 있습니다. 상속을 받을 때는 이런 식으로 기능을 고려해서 메소드를 다시 설계 해야할 필요가 있는 경우도 있습니다.

+ Recent posts