낼름낼름 동동이

[ASAC 16일차] Crawling 본문

데이터분석/파이썬

[ASAC 16일차] Crawling

인죠인간 2024. 4. 12. 10:51

 

4월 12일의 기록

오전에는 실업 급여 인정을 위해 수업을 가지 못하였다. 오전에 놓쳤던 부분은 점심 시간에 따로 공부를 하면서 실습을 따라해보았다. 오히려 혼자서 블로그에 정리해가면서 실습 내용을 따라가니 빠른 시간에 고효율을? 낼 수 있는 것 같아서 좋았다! 다음주부터는 수업 진도가 살짝 느리니까… 코테 문제나 자격증 공부를 좀 해두고 점심시간에 혼자 정리해봐야겠다. 물론 너무 어려우면 수업을 들어야겠지만..

 

 

1. Crawling


크롤링의 목적:

  • 기본적으로 명확하게 데이터를 주고 받으려면 API를 활용해야 한다.
  • 단, 하루 할당량, 주간 할당량이 제한되어 있고 일반적인 사이트에서는 API를 제공하지 않는 곳도 존재함.

기능 체크 방법

  1. 숨겨져 있는 주소는 어떻게 찾지? ⇒ 브라우저 개발도구
  2. 일반적인 html에서 정보를 찾는 방법은? ⇒ 페이지의 정확한 주소 + 태그 체크
  3. 전체 페이지를 돌리는 방법은? (롤링 방법)
# ---> 웹 브라우저에서 개발자도구 & 네트워크
#      우리가 찾으려는 페이지의 정확한 주소!!!!!
#      + 되는지 안되는지 체크
#      : Dart는 주소만 찾아내면 코드로 접속해도 금지는 안 당함!!
# ---> 찾은 주소의 주요한 인자를 보면
# <https://dart.fss.or.kr/dsac001/mainAll.do?selectDate=2024.04.08&sort=&series=&mdayCnt=0¤tPage=9>
# <https://dart.fss.or.kr/dsac001/mainAll.do>? : BaseURL
# selectDate : 요청하려는 날짜 YYYY.MM.DD
# currentPage : 내가 그 날의 어느 페이지 정보를 접근하려는지

실습 1

 

1. 필요한 패키지 import

# 필요한 패키지들
# 1) 통신 : urllib, requests etc
import requests
# 2) html --> tag중심의 언어
#    html의 테그를 중심으로 정보 추출 : BeautifulSoup
from bs4 import BeautifulSoup
# 3) 데이터 처리
import pandas as pd
# 4) +++ 데이터 처리를 하는데 있어서.....
#    ===> 정규식, 시간에 대한 지연처리(일반 사이트)->실험
import re
import time

 

2. 요청하려는 URL 세팅

# Step1) 내가 요청하고자 하는 URL을 완성해야 함!!!!!
# --> API는 문서보고 하면 되는데,,
# --> 일반 사이트는 내가 열심히 필요한 정보를 요청하는 url탐색!!

# 예시 날짜 : 2024.03.05
# --> 698건의 공시 정보를 요청을 하려고 함...
# --> 1페이지가 아니라 여러 페이지를 롤링해야 함...
date = "2024.03.05" # <--- selectDate=
page = "1"          # <--- currentPage=
url = f"<https://dart.fss.or.kr/dsac001/mainAll.do?selectDate={date}&sort=&series=&mdayCnt=0¤tPage={page}>"
url

 

3. request 패키지로 요청

# Step2) requests 패키지로 요청

res = requests.get(url)
res

# 참고) requests 패키지로 받은 정보를 열어보는 방식 2가지...
#      1) res.text : 알아서 자기가 인코딩/디코딩을 처리를 함!!!
#                    특별하게 신경쓸 부분이 거의 없음!!!!
#      2) res.content : 이미지, 동영상 --> 바이트값으로 들어옴...
# --> 일반적으로 특수한(이미지, 동영상) .text를 주로 활용

 

4. Tag에 접근하기 위해 BeautifulSoup패키지 사용

# 위의 요청한 웨사이트의 내용을 필요한 정보에 쉽게
# Tag중심으로 접근하기 위해서는 BeautifulSoup패키지로 변환!!!!
# ==> 모든 텍스트로 필요 정보를 접근하는게 아니라
#     Tag중심으로 정보를 접근하자!!!
soup = BeautifulSoup(res.text, "html.parser")
soup

 

5. Tag + Tag의 속성값 사용

# 방법1) dict로 상세 속성을 지정하는 방식!!!!!
len(soup.find_all("div"))

soup.find_all("div", {"class":"headerTop"})

# 방법2) tag중에서 bs4가 이미 미리 파라미터로 만들어둔 것!!!
#        ex) class 파라미터의 경우에는 : class_
soup.find_all("div",class_="headerTop" )
실습 2) 공시 자료의 수를 체크
# Q) 전체 그 날의 공시 자료의 수를 체크!!!!
#    --> 내가 몇 개의 페이지를 돌려야 할지!!!
#        (그냥 1페이지부터 쭉 돌리면서 ,체크를 하는 방법도 있음.)

temp = soup.find_all("div", {"class","pageInfo"})[0].text
# 총 몇 페이지인지 확인

# + 참고 정규식)
# --> 정규식에서 특정 문자 패턴을 찾는 :
#                  re.findall(패턴,어디서)
# --> 정규식에서 특정 문자 패턴을 변경/제거 :
#                  re.sub(패터,무엇으로변경, 어디서)

 

1. 찾을 패턴 : 숫자 + 건

# Try1) 찾을 패턴 : 숫자들+건

temp = re.findall(r"\\d+건", temp)[0]  # 698건
temp = re.sub(r"건","", temp)         # 698 ---> 숫자...

# 만약, 페이지 계산을 하겠다하면 1페이지 최대 100건이다.
#      그러면 100으로 나눠서 몫을 올림!!
#      100건 --> 1페이지 : 100/100 =1
#      102건 --> 2페이지 : 102/100 = 1.XXX
#       ===> 몫에 대한 올림
if int(temp) % 100 == 0:
    tot_page = int(temp) // 100
else:
    tot_page = int(temp) // 100 + 1
# 참고) 파이썬의 올림 연산자/함수를 사용해도 된다.

 

1. 페이지 정보 추출

# Try2) 이번에는 아래 정보에서 /???] --> ???페이지 정보추출
temp = soup.find_all("div", {"class","pageInfo"})[0].text
temp # ex: [1/7] [총 698건]

# 정규식에 패턴에 구별 : 정규식의 미리 지정된 문자
#                      or 특수문자
# [ ] : 정규식에서 묶음
# [ ]위의 문자열에서는 정규식의 []가 아니라 그냥 단순 괄호[]
# ==> 파이썬  문자열에서 단순 문자 그자체로 인식해줘 \\
temp = re.findall(r"/\\d+\\]", temp)[0]
temp # ex: /7]

re.findall(r"[0-9]+",temp) # ]정규식 규칙을 나타태는 ]

# /, ] 지우고 싶다
# temp = re.sub(r"\\/","", temp)
# temp = re.sub(r"\\]","", temp)
temp = re.sub(r"\\/|\\]","", temp)
temp # ex: 7

정리

  • 일반적인 사이트에서는 내가 필요한 정보를 알아서 잘 찾아야 한다.
  • 규칙화와 정규식에 대한 기본적인 사용이 중요하다.

 

실습 3 공시(Dart) 관련 추출
  1. 1번 공시 정보에 대한 tr 태그 찾아보기
# Q) 1개 공시 정보에 대한 tr 테그를 찾아보자!!!
len(soup.find_all("tr"))

# -> 정확하게 공시 정보들에 대해서 접근을 하기 위해서...
#     tbody 속에 있는 tr로 접근하는게 좀 더 나아보인다.
# why? thead에도 tr이 포함되어 있으니...
#     tbody속에 있는 tr만 다 빼기! ==> 공시들만

len(soup.find("tbody").find_all("tr"))

# --> 1번 공시에 대한 정보 접근...
soup.find("tbody").find_all("tr")[0]

idx = 0 # --> 보려는 공시 위치..
temp = soup.find("tbody").find_all("tr")[idx]
temp.find_all("td")[0] # <td>

temp.find_all("td")[0].text

# 출력 결과: \\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t... 

# + 참고) 원하는 정보가 있지만,,,눈에 안 보였던
#         공백문자들이 존재를 함!!!: strip():양쪽 or 정규식
temp.find_all("td")[0].text.strip()
# "  19:      29    " ---> strip()
# "19:      29"       ---> 정규식으로 공백처리를 해야함!!!

t_str = "\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t19\\r\\t:\\t\\t29\\r\\n\\t\\t\\t\\t\\t\\t\\t"
t_str.strip()

re.sub(r"\\r|\\n|\\t|\\s", "", t_str) # --> 어디라도 공백은 싹 제거.
# 공백을 제거 하는 정규식!

# 최종 출력 결과 : 19:29

 

 

1. 1번 공시 회사가 속한 시장

#    예) 코 : 코스닥, 유 : 유가증권, 넥: 코넥스, 기:기타법인

temp.find_all("td")[1].find_all("span")[1]

temp.find_all("td")[1].find_all("span")[1].get("title")
#출력 : 코스닥시장

 

2. 1번 공시 회사의 이름

temp.find_all("td")[1].text.strip()
# ---> 처리할 수는 있는데,,,좀 더 정확히 타켓팅을 해보자!!!!

temp.find_all("td")[1].text.strip()[1:].strip()
# --> 이러면 처리는 가능은 함....

temp.find_all("td")[1].find("a").text.strip()
# a 태그를 최종 찾아서 그 뒤에 값을 바로 가져오기

 

3. 회사 코드값 출력

temp.find_all("td")[1] # --> 01375822에 대한 코드값 추출

temp.find_all("td")[1].find("a").get("href")
# 출력 결과
# javascript:openCorpInfoNew('01375822', 
# 'winCorpInfo', '/dsae001/selectPopup.ax');

re.findall(r"\\d{8}",
           temp.find_all("td")[1].find("a").get("href"))[0]

#정규식을 사용해서 숫자 8개만 뽑아내기
# 출력 결과 : 01375822

공시의 고유값 : rcpNo

temp.find_all("td")[2].find("a").get("href")

re.findall(r"\\d+", temp.find_all("td")[2].find("a").get("href"))
# 출력: ['001', '20240305901186']

re.findall(r"\\d{14}",
           temp.find_all("td")[2].find("a").get("href"))[0]
# 출력: 20240305901186

 

4. 공시 이름 출력

temp.find_all("td")[2].find("a").text

# 출력 : [기재정정]매출액또는손익구조30%(대규모법인은15%)이상변동\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t  \\t\\t\\t\\t\\t\\t\\t\\r\\n\\t\\t\\t\\t\\t\\t\\t\\t

temp.find_all("td")[2].find("a").text.strip()
# ??--> 중간에 혹시 보다고 공시 정보 사이에 공백/줄바꿈이 있으면
#        re.sub(r"\\n|\\t|\\r","", ~~~)
# ---> 나중에 공백/줄바꿈에 대한 정규식도...
# 출력: [기재정정]매출액또는손익구조30%(대규모법인은15%)이상변동

 

5. 공시에 대한 요청 일자

temp.find_all("td")[4].text
# 출력 : 2024.03.05

temp.find_all("td")[4].text.strip()
# 출력 : 2024.03.05

 

실습과제

 

# Q1) for 문을 이용해서 지금 페이지의 100개 공시 정보를 출력!!!
#     ==> 제출 시간, 회사가 속한시장(fullname),회사이름
#         회사 코드, 공시 이름, 공시의 고유값, 요청일자
# ----> 12시 10분에 1번은 같이 보겠습니다!!!!!!!!!

# Q2) 1번에서 출력한 내용을 DF에 저장을 해보세요!!
#     ==> 컬럼 이름을 스스로 정해보세요!!!!!!
# ------------------------------------------------------------\\
# Q3) 2024년 3월 5일 같은 경우에는 7페이지를 다 돌아서
#     698건의 공시 정보에 대한 위의 Q1에서 추출한 정보들을
#     1개의 DF에 담아보세요!!!

# -----------------------------------------------------
# + opt) 이것을 함수로 만들어 보는 것!!!!
#           입력 : 조회시작일자, 종료일자
#           출력 : 입력 기간 동안의 모든 공시 처리
#                  DF/csv/ excel etc
# -----------------------------------------------------

# 1) 날짜에 대해서 몇 페이지를 돌릴지?

date = "2024.03.05"
page = "1"
url = f"<https://dart.fss.or.kr/dsac001/mainAll.do?selectDate={date}&sort=&series=&mdayCnt=0¤tPage={page}>"

res = requests.get(url)
soup = BeautifulSoup(res.text, "html.parser")

tot_page = soup.find_all("div", {"class" : "pageInfo"})[0].text
tot_page = re.findall(r"/\\d+\\]", tot_page)[0]
tot_page = re.sub(r"/|\\]","",tot_page )
tot_page = int(tot_page)

tot_data = []

for page in range(1,tot_page+1):
    url = f"<https://dart.fss.or.kr/dsac001/mainAll.do?selectDate={date}&sort=&series=&mdayCnt=0¤tPage={page}>"
    # 직접 요청 후 필요한 정보 처리하고 담아두기
    res = requests.get(url)
    soup = BeautifulSoup( res.text, "html.parser")
    # ---> 그 날의 공시 page에서 할 일: page의 공시 롤링!!!
    #       100개로 하드코딩하면 마지막에서 문제가 생김!!!
    table_row = soup.find("tbody").find_all("tr")
    # 그냥 table_row 그냥 값 자체로 롤링하면 됨!!!!!
    for temp in table_row:
        # --> temp는 기본적으로 1개 공시에 대한 정보!! : 샘플단위!
        d_time = temp.find_all("td")[0].text.strip()
        # --> 회사의 속한 시장
        d_market = temp.find_all("td")[1].find_all("span")[1].get("title")
        # --> 회사의 코드값
        d_co_id = re.findall(r"\\d{8}",temp.find_all("td")[1].find("a").get("href"))[0]
        # --> 회사의 이름
        d_co_name= temp.find_all("td")[1].find("a").text.strip()
        # --> 공시 내용의 고유값
        d_rcp = re.findall(r"\\d{14}",temp.find_all("td")[2].find("a").get("href"))[0]
        # --> 공시 내용에 대한 이름
        d_rcp_name = temp.find_all("td")[2].find("a").text.strip()
        # --> 공시 요청 일자
        d_req = temp.find_all("td")[4].text
        tot_data.append( [d_time, d_market, d_co_id,d_co_name,
                        d_rcp,d_rcp_name,d_req])
    print( str(page)+"Done!!!!")
print("Done!!!!")    
추가 과제

4월 9일의 유가증권 시장에 공시된 정보 DF으로 만들기
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import re

# 1) 날짜에 대해서 내가 몇 페이지를 돌려야할지
date = "2024.04.09" # <--- selectDate=
page = "1"          # <--- currentPage=
url = f"<https://dart.fss.or.kr/dsac001/mainY.do?selectDate={date}&sort=&series=&mdayCnt=0¤tPage={page}>"
res = requests.get(url)
soup = BeautifulSoup( res.text, "html.parser")
# --> 앞에서 한 부분대로 내가 몇 페이지를 해야할지 찾아야 함!!!
tot_page = soup.find_all("div", {"class":"pageInfo"})[0].text
tot_page = re.findall(r"/\\d+\\]", tot_page)[0]
tot_page = re.sub(r"/|\\]","",tot_page )
tot_page = int(tot_page)

# 2) 전체 page에 대해서 요청
tot_data = [] # <----- 그 날의 모든 공시정보를 담을 리스트

for page in range(1,tot_page+1):
    url = f"<https://dart.fss.or.kr/dsac001/mainY.do?selectDate={date}&sort=&series=&mdayCnt=0¤tPage={page}>"
    # ---> 직적 요청하고,필요 정보 처리하고,담아두면 됨!!!
    res = requests.get(url)
    soup = BeautifulSoup( res.text, "html.parser")
    # ---> 그 날의 공시 page에서 할 일: page의 공시 롤링!!!
    #       100개로 하드코딩하면 마지막에서 문제가 생김!!!
    table_row = soup.find("tbody").find_all("tr")
    # 그냥 table_row 그냥 값 자체로 롤링하면 됨!!!!!
    for temp in table_row:
        # --> temp는 기본적으로 1개 공시에 대한 정보!! : 샘플단위!
        d_time = temp.find_all("td")[0].text.strip()
        # --> 회사의 속한 시장
        d_market = temp.find_all("td")[1].find_all("span")[1].get("title")
        # --> 회사의 코드값
        d_co_id = re.findall(r"\\d{8}",temp.find_all("td")[1].find("a").get("href"))[0]
        # --> 회사의 이름
        d_co_name= temp.find_all("td")[1].find("a").text.strip()
        # --> 공시 내용의 고유값
        d_rcp = re.findall(r"\\d{14}",temp.find_all("td")[2].find("a").get("href"))[0]
        # --> 공시 내용에 대한 이름
        d_rcp_name = temp.find_all("td")[2].find("a").text.strip()
        # --> 공시 요청 일자
        d_req = temp.find_all("td")[4].text
        tot_data.append( [d_time, d_market, d_co_id,d_co_name,
                        d_rcp,d_rcp_name,d_req])
    print( str(page)+"Done!!!!")
print("Done!!!!")

df_tot = pd.DataFrame(
    data = tot_data,
    columns = ["time","market","co_id","co_name",
               "report_code","report_name","req_date"]
)
df_tot

 

참고) 웹에 있는 데이터 수집 방법

  1. API
    1. 메뉴얼 보고 따라하면 됨
  2. 일반적인 사이트
    1. 숨겨진 주소를 잘 찾아서 크롤링
    2. 숨겨진 주소 찾아도 안되면,
      1. referer, user-agent ect…
      2. header에 필요 정보를 실어서 요청
    3. 웹 드라이버
      1. 브라우저를 통해 접근
      2. 패키지 : 셀리니움으로

2. 셀레니움 Selenium

  • 네이버에 접속해서 bts를 검색한 뒤, 화면을 끄는 것까지 진행한 실습이다.
  • 단, mac에서 진행 할 때는 ‘개인정보 보호 및 보안’ 의 설정을 진행 해야 정상적으로 작동하게 된다

설정을 잘보자

import selenium
import urllib.request

url = "<https://finance.daum.net/>"
res = urllib.request.urlopen(url)
res.read().decode("utf-8")

# ---> 403 접근 금지가 나오는 경우에는
#      http통신에서 header 다른 정보도 실어서 보내야 함!!!!
#      간단한 경우들은 : referer, user-agent 고려!!!
url = "<https://finance.daum.net/api/search/ranks?limit=10>"
# 부가적인 정보들 바탕으로 FM적은 통신 내용 작성..
req = urllib.request.Request(
    url,
    data = None,
    headers = {
        "referer": "<https://finance.daum.net/>",
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
    }
)
res = urllib.request.urlopen(req).read().decode("utf-8")

# 브라우저를 통해서 컨트롤 하기 위한 작업...
s = Service(path)
driver = webdriver.Chrome( service=s)

# 브라우저에서 할 수 있는 여러가지 일들
# ---> 간단하게 창 크기를 조절
driver.set_window_size( 1000,1000)
# 내가 접속하려는 사이트
url ="<https://naver.com>"
driver.get(url)

# --> 검색창에 검색어를 넣고 싶은 것
element = driver.find_element( By.XPATH, '//*[@id="query"]')

element.send_keys("bts")

# 검색 버튼을 찾아서 클릭 해줘
driver.find_element(By.XPATH, '//*[@id="search-btn"]').click()

# 종료를 하기 위해서는

driver.quit()

#네이버 검색창 개발자 도구에서 찍으면
# //*[@id="query"] # xpath copy 했을 때 
# /html/body/div[2]/div[1]/div/div[3]/div[2]/div/form/fieldset/div/input # full xpath 하면 이렇게