강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

최지훈님의 프로필 이미지
최지훈

작성한 질문수

[신규 개정판] 이것이 진짜 크롤링이다 - 실전편 (인공지능 수익화)

셀레니움 환경설정

마지막 페이지 오류

작성

·

931

1

 

안녕하십니까, 크롤링 강의 잘 듣고 있습니다.

마지막 페이지를 찾는 과정에 궁금한 점이 있어 질문드립니다.

 

첫째,

마지막 페이지에서 비활성화된 버튼의 html을 보시면, 강의 내용과 다르게

a태그가 아니라 button에 들어가있습니다.

 

둘째,

islastpage = ........... 부분을 넣지 않으면 크롤링이 제대로 되는데,

해당 부분을 추가하면 세번째 사진의 오류가 발생합니다.

 

셋째,

no attribute 'attrs'를 어떻게 해결할지 방법이 궁금합니다.

 

아래는 작성된 명령어입니다.

from sqlite3 import Row
import requests
from bs4 import BeautifulSoup
import time
import pyautogui
from openpyxl import Workbook
from openpyxl.styles import Alignment

# 여러 페이지 가져오기
# 키워드
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?")) # pyautogui.prompt는 문자열인 결과를 뽑아내는데, 페이지는 숫자니까 int로 묶어서 문자를 숫자로 바꿔주자.

# 엑셀 생성하기
wb = Workbook()
# 엑셀 시트 생성
ws = wb.create_sheet(keyword)

# 열 너비 조절: 크롤링한 결과에 비해 각 행이 너무 좁으니까 조절하자
ws.column_dimensions["A"].width = 60
ws.column_dimensions["B"].width = 60
ws.column_dimensions["C"].width = 120

# 행 번호
row = 1

page_num = 1 # 첫번째 반복을 돌 때는 page_num가 1이다.
for i in range(1, lastpage*10, 10): # 네이버 기사는 10 단위로 변한다!
    print(f"{page_num}페이지 크롤링 중입니다============================")
    response = requests.get(f"https://search.naver.com/search.naver?sm=tab_hty.top&where=news&query={keyword}&start={i}") # 페이지 파라미터는 query 뒤에 &start={i}를 넣어주면 된다.
    html = response.text
    soup = BeautifulSoup(html, "html.parser")
    articles = soup.select("div.info_group") # 뉴스 기사 중 div가 info_group인 것을 선택해라.
    # info_group이 총 몇개인지 확인하니까, word에 나와 있듯이 총 기사가 10개임을 확인했다.

    for article in articles:
        links = article.select("a.info") # article의 a태그 중 class가 info인 녀석들을 가져오자.
        if len(links) >= 2: # 가져온 결과값은 리스트 형태이고, word에 나와 있듯이 네이버 뉴스는 a태그 2개로 구성되어 있다. 따라서 links의 리스트 중 len함수를 통해 리스트가 몇개인지 파악하고, 링크가 2개 이상인지 확인한다.
            url = links[1].attrs["href"] # 우리가 가져올 건 link 중 2번째 요소니까, [1]을 입력한다(리스트 인덱스는 0부터 시작하니까!)
    # 2. 네이버 뉴스 본문 내용 가져오기
            response = requests.get(url, headers={'User-agent' : 'Mozila/5.0'}) # 방금 만든 url에 다시 requests를 사용!, 봇으로 인식되는걸 피하기 위한 header도 넣자.
            html = response.text
            soup_sub = BeautifulSoup(html, "html.parser")
       
            # 만약 연예 뉴스 라면
            if "entertain" in response.url: # 이때, response.url이 아니라 그냥 url만 적으면 "리다이렉션"으로 인한 오류가 발생한다. 그러니까, 위에 만들어 놓은 response 객체의 url을 가져오자.
                title = soup_sub.select_one(".end_tit")  # 연예 뉴스 타이틀은 class="end_tit"로 표시됨
                content = soup_sub.select_one("#articeBody") # 연예뉴스 본문의 id는 articeBody
            elif "sports" in response.url:
                title = soup_sub.select_one("h4.title")
                content = soup_sub.select_one("#newsEndContents")
                # 본문 내용 안의 불필요한 div와 p 삭제
                divs = content.select("div") # select의 결과는 리스트 형태니까 for문을 쓸 수 있다.
                for div in divs:
                    div.decompose() # div를 없애라는 명령어!
                paragraphs = content.select("p")
                for paragraph in paragraphs:
                    paragraph.decompose()
            else: # 연예 기사가 아니라면 기존에 작성한 뉴스의 id를 사용해라!
                title = soup_sub.select_one(".media_end_head_title")
                content = soup_sub.select_one("#newsct_article") # 네이버뉴스의 본문을 모두 포함하는 것은 id ct 이다.
            # 그냥 print(content.text)만 입력해도 결과가 나오지만, 좀 정리해보자
            print("==================링크==================\n", url) # \n은 한줄 띄운다는 뜻이다.
            print("==================제목==================\n", title.text.strip()) # 문자열 양쪽의 공백은 strip()로 없앨 수 있다.
            print("==================본문==================\n", content.text.strip())
            # 출력결과를 저장하자.
            # 그런데, 출력결과는 기사1은 A1행, 기사2는 A2행 등 뒤의 숫자가 자꾸 변해야 한다
            # 따라서, 크롤링을 시작하기 전에, 행 번호를 변수로 처리해주자.
            ws[f"A{row}"] = url
            ws[f"B{row}"] = title.text.strip()
            ws[f"C{row}"] = content.text.strip()
            # 자동 줄바꿈 기능(엑셀에서 한줄로만 길게 나타나는게 아니라, 자동으로 줄을 바꿔준다)
            ws[f"C{row}"].alignment = Alignment(wrap_text=True)
            row = row + 1
            time.sleep(0.3)
    # 페이지를 추가하기 전에, 마지막 페이지 여부를 확인하자
    islastpage = soup.select_one(".btn_next dimmed").attrs["aria-disabled"]
    if islastpage == "true":
        print("마지막 페이지 입니다")
        break
    page_num = page_num + 1

wb.save(f"{keyword}_result.xlsx")

 

답변 1

1

스타트코딩님의 프로필 이미지
스타트코딩
지식공유자

다시 한번 천천히 살펴 보시면

a태그로 되어 있는 것을 확인할 수 있습니다.

 

아래 정삭적으로 동작하는 코드를 첨부해 드립니다.

 

import requests
from bs4 import BeautifulSoup
import pyautogui
from openpyxl import Workbook
from openpyxl.styles import Alignment

# 사용자 입력
keyword = pyautogui.prompt("검색어를 입력하세요")
lastpage = int(pyautogui.prompt("몇 페이지까지 크롤링 할까요?"))
pageNum = 1

# 엑셀 생성하기
wb = Workbook()

# 워크 시트 생성하기
ws = wb.create_sheet(f"{keyword}")

# 열 너비 조절
ws.column_dimensions["A"].width = 60
ws.column_dimensions["B"].width = 60
ws.column_dimensions["C"].width = 80

# 행 번호
row = 1

for i in range(1, lastpage * 10, 10):
    print(f"{pageNum}페이지 크롤링 중입니다 ================ ")
    response = requests.get(f"https://search.naver.com/search.naver?where=news&sm=tab_jum&query={keyword}&start={i}")
    html = response.text
    soup = BeautifulSoup(html, 'html.parser')
    articles = soup.select("div.info_group") # 뉴스 기사 div 10개 추출
    for article in articles:
        links = article.select("a.info")
        if len(links) >= 2: # 링크가 2개 이상이면
            url = links[1].attrs['href'] # 두번째 링크의 href 추출
            # 다시 request를 날려 준다
            response = requests.get(url, headers={'User-Agent' : 'Mozila/5.0'})
            html = response.text
            soup_sub = BeautifulSoup(html, 'html.parser')
            # 연예뉴스 또는 스포츠뉴스는 사이트의 생김새가 다르다
            # 즉, 오류가 날 수 있다.

            if "entertain" in response.url:
                title = soup_sub.select_one(".end_tit")
                content = soup_sub.select_one("#articeBody")
            elif "sports" in response.url:
                title = soup_sub.select_one("h4.title")
                content = soup_sub.select_one("#newsEndContents")
                # 본문 내용안에 불필요한 div 삭제
                divs = content.select("div")
                for div in divs:
                    div.decompose()
            else:
                title = soup_sub.select_one(".media_end_head_headline")
                content = soup_sub.select_one("#newsct_article")

            print("=======링크======= \n", url)
            print("=======제목======= \n", title.text.strip())
            print("=======본문======= \n", content.text.strip())

            # 엑셀에  링크, 제목, 본문 저장
            ws[f'A{row}'] = url
            ws[f'B{row}'] = title.text.strip()
            ws[f'C{row}'] = content.text.strip()

            # 자동 줄바꿈
            ws[f'C{row}'].alignment = Alignment(wrap_text=True)
            row = row + 1
    # 마지막 페이지 여부 확인 (두번째 soup 변수 이름 변경)      
    isLastPage = soup.select_one('a.btn_next').attrs['aria-disabled']
    if isLastPage == 'true':
        print("마지막 페이지 입니다.")
        break
    pageNum = pageNum + 1

# 워드 문서 저장하기
wb.save(f"{keyword}_result.xlsx")
최지훈님의 프로필 이미지
최지훈

작성한 질문수

질문하기