본문 바로가기

Python/Crawler

PYTHON 크롤링을 이용한 수산식품 분석하기 - 3

API를 제공하지 않는 정보는 어떻게 크롤링 하여야 할까?

SNS 크롤링 도중 비정형 데이터의 처리가 어렵다는 것을 깨닫고 정형 데이터들을 먼저 모으기로 결심했다.

사용한 웹 사이트는 국립수산과학원(https://www.nifs.go.kr)의 해양수산물성분표이다.

수산물 성분표는 다음과 같이 어류, 패류, 연체류, 갑각류, 해조류, 기타 분류로 나뉘어져 있고

10개 단위로 목록을 보여준다.

처음에는 셀레니움을 사용해서 동적 컨트롤을 할 까 생각했지만 주소를 보니 간단하게 되어있어 Beautifulsoup로만 진행하였다.


1. 먼저 검색표의 주소를 분석한다.

목록 : https://www.nifs.go.kr/page?id=aq_seafood_1_01&type=search&searchStr=&searchField=&sortField=fim_kor_name&sortDirection=asc&class_code=CL01&chk=&pageNo=1

내용 : https://www.nifs.go.kr/page?id=aq_seafood_2_7&type=tot&from=totList&fim_col_id=2009-MF0006689-6-D01

목록의 경우 class_code=<변수>로 분류를 나타낸다. CL01~05까지 존재하며 CL09는 기타인 것을 알 수 있다.

기타자료는 분류가 애매한 장기류였기 때문에 제외하고 크롤링했다. 필요하면 주소만따로 입력해서 가져오면 된다.

그리고 pageNo로 page를 넘길 수 있다. 셀레니움을 사용하지 않은 가장 큰 이유다.

보통 정부기관의 경우 일괄적인 웹페이지(이전의 세이프티코리아같이)로 이루어져 있어 크롤링에 제한이 있는데 이 사이트의 경우 존재하지 않아 편했다.

내용의 경우 전부 똑같고 fim_col_id로만 구분하는데 검색해보니 자체적으로 사용하는 id라서 어디서 구할 방법도 없고 딱히 내용 주소에서 얻은 건 없었다.


2. 한 페이지를 크롤링해본다.

페이지 하나를 먼저 크롤링 해보는 것이 좋다.

단위는 한 페이지 -> 1개의 목록 -> 분야별 목록 업 -> 모든 분야

로 나가는 것이 함수를 만들기에도 좋고 중간 중간 체크하기 편하다.


수산물 상세보기 페이지를 보면 분류가 무지 많다.

일반 성분만 필요로 하기 때문에 별다른 조작없이 전체보기의 테이블을 읽어와 td값을 가져와주었다.

def urlCrawler(url): req = urllib.request.Request(url) sourcecode = urllib.request.urlopen(url).read() soup = BeautifulSoup(sourcecode, "html.parser") try : res = soup.find_all('table','table02') name = soup.find('table','table02') name = name.find_all('td') #name = name[1].get_text() #name을 얻기 위해서 temp = res[2].find_all('td') #일반성분 테이블은 3번째 table02이므로 res[2]가 원하는 자료 ingre = [] ingre.append(name[1].get_text()) if(len(temp) != 10): #개복치같은 물고기는 일반 성분을 가지고 있지 않기 때문에 제외해야한다. return ingre for i in temp: ingre.append(i.get_text()) # if(len(ingre) > 10): # break #일반성분이 1개 이상일 경우 1개만 입력한다(ex 고등어) #td값만 원하므로 return ingre except (IndexError, TypeError, AttributeError): pass #한 페이지 크롤링 #name, kcal, kj, moisture, protein, fat, ash, non-fibrous, fiber, edibleportion, remarks ###이 과정이 한 페이지안에서 한 번씩 일어나는 과정이다.

하지만 진행하다보면 문제점이 있는데 크게 4가지를 발견하였다.

1. 페이지가 아예 존재하지 않는 경우

2. 페이지는 있지만 값이 없는 경우

3. 일반 성분 테이블은 있으나 값이 없는 경우

4. 일반 성분 테이블이 존재하지 않으며 다른 테이블이 존재하는 경우

1,2 번의 경우 try - except로 페이지 값이 존재하지 않으면 pass로 빠져나오게 해주었다.

3번 또한 예외처리 해주면된다.

문제는 4번인데 홈페이지내에 테이블의 이름이 모두 똑같고 특이사항을 걸어주지 않았기 때문에 3번째의 테이블을(일반적인 경우 모두 두번째다) 일반 성분으로 생각하고 크롤링 해왔다.

하지만 이름도 모르겠는 개복치를 보자.


일반 성분 없이 다른 성분 분석으로 2번째 테이블이 차있다.

페이지를 쭉 본 결과 들어가지 않는 값은 Null이나 0으로 표시한 것을 봐서 영양 성분 형식이 정해진 것으로 추측할 수 있다.

그래서 과감하게 열이 10개인 애들만 고정해서 찾도록 했다. 아닌 애들은 제외했다.

그리고 구분을 위해 이름을 같이 크롤링해왔다. 이렇게 하면 한 페이지에서 이름 + 일반 성분을 크롤링할 수 있다.


3. 한 개의 목록 크롤링

한 개의 목록은 1~10개의 게시글로 이루어져 있다.

처음에는 하나하나 눌러서 진행할까 했지만 고맙게도 전부 A 태그가 달려있어 Table안에 A태그를 가져오면 쉽게 주소를 얻을 수 있다.



def pageLink(url): req = urllib.request.Request(url) sourcecode = urllib.request.urlopen(url).read() soup = BeautifulSoup(sourcecode, "html.parser") link_list = soup.find('table','table02') link_list = link_list.find_all('a') link_list_all = [] for i in link_list: link_list_all.append(i.get('href')) return link_list_all

주의해야 할 점은 여기서 가져오는 주소 앞에 'https://www.nifs.go.kr/' 를 입력해주어야 한다는 것이다.

포문으로 간단하게 만들 수 있다.

link_list = pageLink(url) for i in link_list: i = 'https://www.nifs.go.kr/' + i print(i)

4. 목록의 끝 찾기

이 문제가 생각보다 복잡했다.


보면 << 와 >> 는 첫 페이지, 끝 페이지로 향하는 것이고

> 는 1에서 2 2에서 3이 아닌 1에서 11로 점프해주는 버튼이다.

그래서 >는 거의 못쓰고 >>를 사용해야 한다.

문제는 >와 >>가 이름이 같다는 것이다.


첫번째 든 생각이 앞에 주소 분석한 page에 변수를 넣어 1씩 증가한 후 값이 나타나지 않으면 끝점이라고 판단하게 할까 했다.

하지만 목록 인덱스에 벗어나는 값을 넣어도 아무렇지 않게 페이지가 나타났다.

물론 A코드를 검사해 코드가 더이상 나타나지 않을 때 값의 -1이 끝 점이라고 알 수 있겠지만 한 번에 처리하고 싶었다.

그래서 결국 노가다를 했다.

def pageNum(url): req = urllib.request.Request(url) sourcecode = urllib.request.urlopen(url).read() soup = BeautifulSoup(sourcecode, "html.parser") page_list = soup.find_all(title='next') end_page_list = len(page_list) if end_page_list == 0 : return 1 else : tmp = str(page_list[end_page_list-1]) tmp = tmp.split('"') tmp = tmp[1] return tmp[161:] #페이지 목록 갯수 알아오는 함수

next 타이틀을 검색해 마지막 값을 택해주었다. 페이지를 전부 살펴보니 >>는 항상 마지막 태그였기 때문이다.

그리고 화살표가 없는 경우는 1페이지밖에 없는 것이기 때문에 바로 return 1을 하게 하였다.

가져오는 next는 이렇게 된다.


이걸 그냥 전부 " 단위로 나눠주고 pageNo의 위치를 찾아 가리키는 페이지를 리턴할 수 있게 해주었다.

엄청 불안정하지만 분류별로 다 잘 돌아가고 주소를 계속 확인하지 않아도 되기 때문에 장단점이 있는 것 같다.

물론 다시 짠다면 이렇게 안할거다.

그냥 3번에 while(1) 돌려서 A코드가 나오지 않을 때까지 +1 해주는게 더 안정성있다.


5. 코드 종합

이제 코드를 종합하여 실행해본다.

만든 코드는 GIT 에 있다.

실행해보면 이렇게 예쁘게 나온다.