브런치 크롤링 하기

허준현·2021년 8월 4일
0
post-thumbnail

이번에 소마 프로젝트를 진행함에 있어서 일기에 대한 데이터를 수집을 해야 했습니다. 네이버 블로그나 브런치, 벨로그를 둘러보던중 자신의 감정에 솔직하게 작성하고 다양한 주제가 있어서 브런치를 선택하게 되었습니다.

저의 크롤링 과정은 다음과 같습니다.

  1. 브런치 메인 화면에서 돋보기를 눌러 키워드를 검색한다.
  2. 무한 스크롤이 되는 화면에서 각 게시물을 가져온다.
  3. 게시물 내부에 제목, 내용, 이미지를 수집한다.

1. 브런치 메인 화면에서 키워드 검색하기

우선적으로 selenium을 활용하여 해당 화면까지 넘어가는 코드입니다.

webdriver_options = webdriver.ChromeOptions()
webdriver_options.add_argument('headless')
driver = webdriver.Chrome('chromedriver', options=webdriver_options)
driver.maximize_window()
driver.implicitly_wait(2)

driver.get(url='https://brunch.co.kr/')
search_box = driver.find_element_by_xpath(
    '//*[@id="btnServiceMenuSearch"]')
search_box.click()
search_bar = driver.find_element_by_xpath(
    '//*[@id="txt_search"]')
search_bar.send_keys(keyword)
search_bar.send_keys(Keys.RETURN)

webdriver 옵션을 활용하여 웹이 실제로 돌아가는 것이 아닌 백그라운드로 돌아가는 방식을 선택하였고 해당 창이 화면에 가득 차지 않게 되면 게시물의 타이틀을 찾지 못하는 경우가 있어서 maximize를 사용했습니다.
나머지 부분은 해당 브런치 링크에 들어가 돋보기 Search를 누르고 키워드를 검색하는 코드입니다.

2.무한 스크롤에서 게시물 가져오기

우선적으로 무한스크롤을 처음 수집해봐서 여러 블로그를 참고하던 중 도움을 가장 많이 받은 블로그입니다.
https://velog.io/@devmin/selenium-crawling-infinite-scroll-click

last_height = driver.execute_script("return document.body.scrollHeight")
cnt = 0
while cnt < 100:
    cnt += 1
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(SCROLL_PAUSE_TIME)
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height
    print("brunch-{} ~ {}".format(start_page, start_page+19))

driver 안에 있는 함수 중에서 execute_script를 사용하여 문서의 전체 길이를 가지고 있게 됩니다.
내리고 나서 바로 다른 코드를 실행하게 되면 스크롤 내리는 것이 안먹히는 경우가 있어 time.sleep()을 사용하여 시간을 둡니다.
또 한 현재 높이와 그 전의 높이가 같다는 것은 더이상 스크롤 할 수 없다고 간주하여 멈추게 됩니다. 원래에는 while True: 를 사용하였으나 만개의 데이터를 수집하고도 계속 수집하게 되어 제약을 두었습니다.

3.게시물 내부에 제목, 내용, 이미지를 수집

게시물 내부에 제목 , 내용 , 이미지를 수집하고 새 창을 클릭하였으므로 새로 열린창에 대해서 닫아주는 것까지 하였습니다. 2번에서 했던 코드와 이어집니다!

while cnt < 100:
    cnt += 1
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(SCROLL_PAUSE_TIME)
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height
    print("brunch-{} ~ {}".format(start_page, start_page+19))
    for page in range(start_page, start_page+19):
        try:
            acting_point = driver.find_element_by_xpath(
                f'//*[@id="resultArticle"]/div/div[1]/div[2]/ul/li[{page}]/a/div[1]/strong')
            driver.execute_script("arguments[0].click();", acting_point)
        except:
            pass
        driver.switch_to.window(driver.window_handles[1])
        driver.get_window_position(driver.window_handles[1])
        driver.implicitly_wait(2)
        diary_content = ''
        try:
            div_p_elems = driver.find_elements_by_xpath(
                '/html/body/div[3]/div[1]/div[2]/div[1]/p[@class="wrap_item item_type_text"] | /html/body/div[3]/div[1]/div[2]/div[1]/h4[@class="wrap_item item_type_text"]')
            for item in div_p_elems:
                diary_content += item.text
            diary_content = diary_content.replace('\n', "")
        except:
            pass

        a = driver.page_source
        bs = BeautifulSoup(a, "html.parser")
        c = bs.find_all('div', {'class': 'wrap_img_float'})
        diary_img = []
        for i in c:
            x = re.findall(r'(?=src)src=\"(?P<src>[^\"]+)', str(i))
            diary_img += x

        diary_title = ''
        try:
            diary_title = driver.find_element_by_xpath(
                '/html/body/div[3]/div[1]/div[1]/div/div[3]/h1'
            )
            diary_title = diary_title.text.replace('\n', "")
        except:
            pass

        diary_date = ''
        try:
            diary_date = driver.find_element_by_xpath(
                '//*[@id="wrapArticleInfo"]/span[4]'
            )
        except:
            pass

        time.sleep(SCROLL_PAUSE_TIME)

        diary.append({
            "title": diary_title,
            "date": diary_date.text,
            "content": diary_content,
            "img": diary_img,
        })
        driver.close()
        driver.switch_to.window(driver.window_handles[0])
        driver.get_window_position(driver.window_handles[0])

    start_page += 19
    if cnt % 10 == 0:
        f = open("hobby.csv", "a", encoding="UTF-8", newline='')
        csvWriter = csv.writer(f)
        for i in diary:
            csvWriter.writerow([i["title"], i["date"], i["content"], i['img']])
        f.close()
        diary = []

현재 브런치에서 무한 스크롤을 내리게 된다면 게시물이 20개씩 전달 됩니다. 하지만 200개 정도 데이터를 수집하고 나서 멈추는 현상이 있었습니다. 원인을 찾아보니 처음에는 데이터 20개씩 전달하고 그 이후에는 19개씩 전달하는 것을 알 수 있었습니다.

첫번째 문제점으로 브런치에서 이미지를 수집하기 위해서 패턴을 찾던 중에서 이미지를 감싸고 있는 컴포넌트는 wrap_img_float 로 동일하였지만 상위 컴포넌트는 사진 레이아웃에 따라서 변경되기 때문에 하나의 틀로 잡기가 어려웠습니다. 따라서 bs4를 사용하여 해당 wrap_img_float 태그 안에 있는 이미지 주소를 정규식을 사용하여 뽑아내었습니다

또 한 내용이 없는 글 또한 있었고 중간에 크롤링이 멈추지 않도록 try except를 사용하였습니다. 또 한 중간중간 크롤링 한 내용을 저장하기 위해 데이터 190개를 긁을 떄마다 파일에 저장하도록 하였습니다.

전체 코드는 깃허브를 참고하시면 되겠습니다 :)
https://github.com/denhur62/python_crwaling

profile
best of best

0개의 댓글