반응형

 

짜잔~

 오늘은 셀레니움을 이용한 나라장터 데이터 수집하기 마지막 포스팅이에요~~

마지막 포스팅 이후로 추가로 필요한 정보들을 수집하고 싶으신 분들께서는

댓글로 말씀해 주시면, 제가 포스팅하도록 할게요!

(너무 귀찮을 거 같으면 포스팅 말고 방법만 알려줄 거예요.. ㅜ)

 

 

오늘은~ 이전 포스팅에서 받아온 자료에 없었던

'입찰방법', '배정예산', '추정가격' 등 이 담겨있는 용역입찰공고 상세 부분을 추가할 예정입니다!!

 

(잡설이 불필요하신 분들은 포스팅 맨 하단의 코드만 가져다 이용하셔요 ^^)

 

이전 포스팅과 이어지는 내용이니, 당연히 이전 포스팅을 참고하고 오셔야겠죠?

(이렇게 다른 포스팅 조회수 +1 추가하기 ㅋㅋ)

https://rogios-story.tistory.com/entry/selenium-g2b-Crawling-3-4 (저번 포스팅)

 

셀레니움(selenium) 나라장터 데이터 수집하기 3/4 (requests 호출 및 데이터 파싱)

저번 포스팅에서 셀레니움을 이용해서, 필요한 frame으로 이동하고~ 원하는 버튼 클릭하고 보이는 화면의 데이터들을 수집하고, 필터링하는 방법에 대해 알아보았는데요 https://rogios-story.tistory.com

rogios-story.tistory.com

 

 

저번 포스팅에서 말씀드렸지만, 셀레니움을 이용한 크롤링 보다 requests를 이용한 크롤링이 훨씬 더 빨랐어요

 

따라서, 이번에도 requests를 이용해서 데이터를 추가할 예정이고요, 저번 코드에 이어서 진행하도록 하겠습니다.

 

#저번 코드

# -*- coding: utf-8 -*-
"""
Created on Fri Sep 29 21:12:23 2023

@author: 진연녹
"""
import pandas as pd
import requests
from bs4 import BeautifulSoup

#%% 
# 처음 URL에서 ? << 물음표 뒷 부분은 모두 제거하고 남은 url
url = "https://www.g2b.go.kr:8101/ep/tbid/tbidList.do?"

payload = {
    "bidSearchType": "1",
    "fromBidDt": "2023/04/04",
    "radOrgan": "1",
    "recordCountPerPage": "100",    # 한 페이지에 몇개 노출인가
    "regYn": "Y",
    "searchDtType": "1",
    "searchType": "1",              # 몰라도 되는 듯
    "setMonth1": "3",               # 최근1달 : 1, 최근2달:2, 최근6달:3
    "taskClCds": "5",               # 물품:1, 공사:3, 용역:5, 리스:6, 외자:2, 비축:11, 기타:4, 민간:20
    "toBidDt": "2023/10/05",        
    "currentPageNo": "1",           # 현재 페이지 번호
    "maxPageViewNoByWshan": "2"
}
res = requests.get(url, params = payload)

html = res.text
soup = BeautifulSoup(html, 'html.parser')
입찰공고목록_요소 = soup.find('table')
입찰공고목록_table = pd.read_html(str(입찰공고목록_요소))[0]

#%%

# 나중에 쓸 변수 미리 생성
입찰공고목록_합 = pd.DataFrame()

# 더보기 10회 진행하기
i = 1
반복횟수 = 10
while i <= 반복횟수:
    payload['currentPageNo'] = i
    res = requests.get(url, params = payload)
    if res.status_code  == 200:
        print(payload['currentPageNo'], '불러옴')
        html = res.text
        soup = BeautifulSoup(html, 'html.parser')
        입찰공고목록_요소 = soup.find('table')
        입찰공고목록_table = pd.read_html(str(입찰공고목록_요소))[0]
        입찰공고목록_합 = pd.concat([입찰공고목록_합,입찰공고목록_table])
    else:
        print(i,' 번째 페이지 에러 확인해주세요')
    i = i+1



# 필터링 하기 (공고명에 "건설관리", "실시설계", "기술진단" 이 포함된 내용만 추출)
keywords = ["건설관리", "실시설계", "기술진단"]
입찰공고목록_최종 = 입찰공고목록_합[입찰공고목록_합['공고명'].str.contains('|'.join(keywords))]

입찰공고목록_최종.columns

 

위 코드를 실행시키면

아래와 같은 내용이 나오는데요

담겨있는 정보에 가장 중요한 계약방법, 입찰방법, 입찰자격, 배정예산 등의 내용이 빠져있어요

이 내용들은 '용역 입찰공고 상세'를 들어가야 있답니다

 

오늘은, 저번 포스팅에서 받았던 자료에, 상세 내용을 추가하는 방법에 대해 포스팅해 볼게요

 

 

1. requests를 이용하여 용역 입찰공고 상세 페이지 불러오는 방법 알아내기

 

먼저 아래 링크에 접속하면 

https://www.g2b.go.kr/pt/menu/selectSubFrame.do?framesrc=/pt/menu/frameTgong.do?url=https://www.g2b.go.kr:8101/ep/tbid/tbidFwd.do

 

이런 화면이 나올 거예요

 

여기에서, 용역 하나하나 눌러보며, 어떤 방법으로 requests 되는지 확인해봐야 합니다

 

첫 번째 공고의 결과인데, 제 느낌에 tbidno: 20231006119는 입찰공고번호-차수와 매치될 줄 알았는데

 

그렇지 않았네요...

 

 

다음 방법으로 소스 보기에서 Elements 안에서 검색을 해보았는데도... 검색되지 않았어요 ㅜ.ㅜ

 

이럴 땐?! 

 

이 전 페이지에서 정보를 찾을 수 없는지 살펴보는 것도 좋아요!!

 

 

이 전 페이지에서, href 링크로 담겨있었네요?!

그럼 이전 포스팅 내용에서 href까지 추가해서 불러오면 해결되겠죠??

초보자 분들께는, 이런 식으로 찾아가는 게 매우 중요한 꿀팁이니까 반드시 기억하세요!!!!!!!!!

도움 됐으면, 댓글도 남겨주시고요!!!

 

기존 코드의 일부분을, 아래 코드로 변경하면

# 나중에 쓸 변수 미리 생성
입찰공고목록_합 = pd.DataFrame()

# 더보기 10회 진행하기
i = 1
반복횟수 = 10
while i <= 반복횟수:
    payload['currentPageNo'] = i
    res = requests.get(url, params = payload)
    if res.status_code  == 200:
        print(payload['currentPageNo'], '불러옴')
        html = res.text
        soup = BeautifulSoup(html, 'html.parser')
        입찰공고목록_요소 = soup.find('table')
        입찰공고목록_table = pd.read_html(str(입찰공고목록_요소))[0]
        
        '''=========================================================
        소스보기 내용을 살펴보면, table 안의 tbody 안의, tr 안의 2번째 td 안의 a요소의 href값임
        ========================================================='''
        
        # 빈 데이터프레임 생성
        rows = []
        a요소 = 입찰공고목록_요소.tbody.select('td:nth-of-type(2) div a')
        for a in a요소:
            # print(a)
            if a:
                공고번호_차수_링크 = a['href']
                공고번호_차수_번호 = a.text

                # 새로운 행을 데이터프레임에 추가
                
                row = {'공고번호-차수': 공고번호_차수_번호, '공고번호-링크': 공고번호_차수_링크}
                rows.append(row)
                
        링크들 = pd.DataFrame(rows)
                
        '''=========================================================
        혹시 모르니까, 검토를 위해서, 공고번호와 링크를 같이 불러와서 매치시키는게 좋아요!
        ========================================================='''
        
        merged_df = 입찰공고목록_table.merge(링크들, on='공고번호-차수', how='inner')
        
        
        입찰공고목록_합 = pd.concat([입찰공고목록_합,merged_df])
        
        
        
    else:
        print(i,' 번째 페이지 에러 확인해주세요')
    i = i+1



# 필터링 하기 (공고명에 "건설관리", "실시설계", "기술진단" 이 포함된 내용만 추출)
keywords = ["건설관리", "실시설계", "기술진단"]
입찰공고목록_최종 = 입찰공고목록_합[입찰공고목록_합['공고명'].str.contains('|'.join(keywords))]

입찰공고목록_최종.columns

 

공고번호 링크가 추가돼요!!

 

이제 각 링크를 request로 호출받아서, 필요한 내용을 추가하면 되겠죠??

 

#%% 테리이블에 각 내용 추가하기
입찰방법_list = []
계약방법_list = []
배정예산_list = []
추정가격_list = []
for i in 입찰공고목록_최종['공고번호-링크']:
    # print(i)
    
    res = requests.get(i)
    html = res.text
    soup = BeautifulSoup(html, 'html.parser')    
    
    입찰방법_element = soup.find('th', string=lambda t: t and ('입찰방법' in t or '입찰방식' in t)).find_next('td').find('div')
    입찰방법 = 입찰방법_element.get_text(strip=True)
    입찰방법_list.append(입찰방법)

    계약방법_element = soup.find('th', string='계약방법').find_next('td').find('div')
    계약방법 = 계약방법_element.get_text(strip=True)
    계약방법_list.append(계약방법)

    배정예산_element = soup.find('th', string='배정예산').find_next('td').find('div')
    배정예산 = 배정예산_element.get_text(strip=True)
    배정예산_list.append(배정예산)


    추정가격_element = soup.find('th', string='추정가격').find_next('td').find('div')
    추정가격 = 추정가격_element.get_text(strip=True)
    추정가격_list.append(추정가격)

# 데이터프레임에 새로운 열로 추가
입찰공고목록_최종['입찰방법'] = 입찰방법_list
입찰공고목록_최종['계약방법'] = 계약방법_list
입찰공고목록_최종['배정예산'] = 배정예산_list
입찰공고목록_최종['추정가격'] = 추정가격_list

입찰공고목록_최종.to_excel('your_output_file.xlsx', index=False, encoding='utf-8')

 

이렇게 하면, 엑셀로 저장까지 완벽 그 잡채!

 

수집한 내용은 이렇네요!

여기에서 중요한 내용 하나!!!!!!!!!

 

다년감의 짬으로 미루어볼 때, 위 방식대로 안 하면 표가 달라질 때마다 에러가 발생할 수 있습니다!!!!

 

제 코드도 에러날 수 있는데 그나마 가능성을 줄였으니, 잘 확인하시고 이용하셔요!!

 

 

이렇게, 셀레니움과 requests를 이용한 나라장터 크롤링을 알아보았어요~ ㅎㅎ

 

중간중간 스킵한 설명들이 꽤 있긴 하지만, 코드 한 줄 한 줄 따라 하시면 크게 어렵지 않을 거라 생각해요

 

혹시 잘 모르시는 부분 있으시면 언제든 댓글! 남겨주셔요 ~~

 

 

#전체 코드

# -*- coding: utf-8 -*-
"""
Created on Fri Sep 29 21:12:23 2023

@author: 진연녹
"""
import pandas as pd
import requests
from bs4 import BeautifulSoup

#%% 
# 처음 URL에서 ? << 물음표 뒷 부분은 모두 제거하고 남은 url
url = "https://www.g2b.go.kr:8101/ep/tbid/tbidList.do?"

payload = {
    "bidSearchType": "1",
    "fromBidDt": "2023/04/09",
    "radOrgan": "1",
    "recordCountPerPage": "100",    # 한 페이지에 몇개 노출인가
    "regYn": "Y",
    "searchDtType": "1",
    "searchType": "1",              # 몰라도 되는 듯
    "setMonth1": "3",               # 최근1달 : 1, 최근2달:2, 최근6달:3
    "taskClCds": "5",               # 물품:1, 공사:3, 용역:5, 리스:6, 외자:2, 비축:11, 기타:4, 민간:20
    "toBidDt": "2023/10/10",        
    "currentPageNo": "1",           # 현재 페이지 번호
    "maxPageViewNoByWshan": "2"
}
res = requests.get(url, params = payload)

html = res.text
soup = BeautifulSoup(html, 'html.parser')
입찰공고목록_요소 = soup.find('table')
입찰공고목록_table = pd.read_html(str(입찰공고목록_요소))[0]

#%%

# 나중에 쓸 변수 미리 생성
입찰공고목록_합 = pd.DataFrame()

# 더보기 10회 진행하기
i = 1
반복횟수 = 10
while i <= 반복횟수:
    payload['currentPageNo'] = i
    res = requests.get(url, params = payload)
    if res.status_code  == 200:
        print(payload['currentPageNo'], '불러옴')
        html = res.text
        soup = BeautifulSoup(html, 'html.parser')
        입찰공고목록_요소 = soup.find('table')
        입찰공고목록_table = pd.read_html(str(입찰공고목록_요소))[0]
        
        '''=========================================================
        소스보기 내용을 살펴보면, table 안의 tbody 안의, tr 안의 2번째 td 안의 a요소의 href값임
        ========================================================='''
        
        # 빈 데이터프레임 생성
        rows = []
        a요소 = 입찰공고목록_요소.tbody.select('td:nth-of-type(2) div a')
        for a in a요소:
            # print(a)
            if a:
                공고번호_차수_링크 = a['href']
                공고번호_차수_번호 = a.text

                # 새로운 행을 데이터프레임에 추가
                
                row = {'공고번호-차수': 공고번호_차수_번호, '공고번호-링크': 공고번호_차수_링크}
                rows.append(row)
                
        링크들 = pd.DataFrame(rows)
                
        '''=========================================================
        혹시 모르니까, 검토를 위해서, 공고번호와 링크를 같이 불러와서 매치시키는게 좋아요!
        ========================================================='''
        
        merged_df = 입찰공고목록_table.merge(링크들, on='공고번호-차수', how='inner')
        
        
        입찰공고목록_합 = pd.concat([입찰공고목록_합,merged_df])
        
    else:
        print(i,' 번째 페이지 에러 확인해주세요')
    i = i+1

# 필터링 하기 (공고명에 "건설관리", "실시설계", "기술진단" 이 포함된 내용만 추출)
keywords = ["건설관리", "실시설계", "기술진단"]
입찰공고목록_최종 = 입찰공고목록_합[입찰공고목록_합['공고명'].str.contains('|'.join(keywords))]

#%% 테리이블에 각 내용 추가하기
입찰방법_list = []
계약방법_list = []
배정예산_list = []
추정가격_list = []
for i in 입찰공고목록_최종['공고번호-링크']:
    # print(i)
    
    res = requests.get(i)
    html = res.text
    soup = BeautifulSoup(html, 'html.parser')    
    
    입찰방법_element = soup.find('th', string=lambda t: t and ('입찰방법' in t or '입찰방식' in t)).find_next('td').find('div')
    입찰방법 = 입찰방법_element.get_text(strip=True)
    입찰방법_list.append(입찰방법)

    계약방법_element = soup.find('th', string='계약방법').find_next('td').find('div')
    계약방법 = 계약방법_element.get_text(strip=True)
    계약방법_list.append(계약방법)

    배정예산_element = soup.find('th', string='배정예산').find_next('td').find('div')
    배정예산 = 배정예산_element.get_text(strip=True)
    배정예산_list.append(배정예산)
    
    추정가격_element = soup.find('th', string='추정가격').find_next('td').find('div')
    추정가격 = 추정가격_element.get_text(strip=True)
    추정가격_list.append(추정가격)

# 데이터프레임에 새로운 열로 추가
입찰공고목록_최종['입찰방법'] = 입찰방법_list
입찰공고목록_최종['계약방법'] = 계약방법_list
입찰공고목록_최종['배정예산'] = 배정예산_list
입찰공고목록_최종['추정가격'] = 추정가격_list

입찰공고목록_최종.to_excel('your_output_file.xlsx', index=False, encoding='utf-8')
반응형

+ Recent posts