본문 바로가기
프로젝트/봉사왕심봉사

specup 네이버 카페 크롤링 - selenium, beatifulsoup

by 윤싱찬 2018. 8. 8.

 

 

기계학습으로 봉사활동 추천시스템를 구상하고 있다.

 

그래서 가장 기초가 되는 데이터 모으기부터 시작

 

1
2
3
4
5
6
from selenium import webdriver
from bs4 import BeautifulSoup
import pandas as pd
import time
import re
import csv
cs

 

필요한 라이브러리는 요정도.

 

selenium으로 mobile 버전에서 더보기를 눌러주고 각 내용을 가져오려고 한다.

사실 selenium을 안쓰고 requests로만 구현을 했었는데 pc버전에서 왜이리 값을 다 못받아 오는지 모르겠다.

삽질 끝에 mobile 버전이 조금 더 단순한 형태기도해서 사용한다.

 

 

1
2
driver = webdriver.Chrome()
driver.implicitly_wait(3)
cs

 

 

webdriver로 크롬 열어주고, 카페 게시글을 읽으려면 로그인이 유지되어야 한다.

 

 

 

1
2
3
4
5
6
7
# 로그인 전용 화면
driver.get('https://nid.naver.com/nidlogin.login?svctype=262144&url=http://m.naver.com/aside/')
# 아이디와 비밀번호 입력
driver.find_element_by_name('id').send_keys('tjdcks9243')
driver.find_element_by_name('pw').send_keys('********')
# 로그인 버튼 클릭
driver.find_element_by_css_selector('#frmNIDLogin > fieldset > input').click()
cs

 

f12 눌러서 소스코드를 확인해보면

아이디 값의 input name는 id, 비밀번호 값의 input name은 pw이다. 자동화로 잘 넣어줄 것이고

로그인 버튼의 path를 가져오면 저렇게 되므로 눌러주면 로그인 완료

pc버전에서 진행할 때는 session을 이용해서 쭉 유지시켜주면 끝인데

mobile 버전은 로그인 때 타이밍 안맞아서도 가끔나고 좀 귀찮아서 그냥 수동으로 로그인하고 카페들어감ㅋ

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
base_url = 'https://m.cafe.naver.com/ArticleList.nhn?search.clubid=15754634&search.menuid=33'
test_list = []
 
driver.get(base_url)
# iframe으로 프레임 전환
#     driver.switch_to_frame('cafe_main')
for page_num in range(1,50):
    # 더보기 버튼 50번 클릭
    driver.find_element_by_xpath('//*[@id="btnNextList"]/a').click()
    # 로딩 시간이 있으므로 타이밍 맞추기 위해 sleep(0.5)
    time.sleep(0.5)
# # href 속성을 찾아 url을 리스트로 저장한다.
article_list = driver.find_elements_by_css_selector('li.board_box a.txt_area')
article_urls = [ i.get_attribute('href'for i in article_list ]
 
for link in article_urls:
    test_list.append(link)
cs

 

base_url은 스펙업의 국내 봉사 url

link들을 저장할 변수 test_list를 만들고 driver.get으로 접속한다.

pc버전을 크롤링 할 때는 네이버 카페가 iframe 형식이라서 꼭 프레임 전환을 해야 먹는다고 함

 

selenium의 좋은점은 xpath를 그냥 가져올 수 있다는 것이다.

귀찮게 tag들 따져가며 하는 것 보다 우클릭 -> copy xpath 하면 끝이라.. 좋다.

무튼 더보기로 50번 클릭을 해줘서 1000개 정도 띄워놓고 각 게시글의 url을 긁어와서 test_list에 저장한다.

 

1
2
len(test_list)
# 1000
cs

 

 

 

 

1
2
3
4
5
6
7
8
9
total_list=['제목','작성 날짜','주최사','활동내용','모집 기간','모집 인원','모집 대상',
            '참가 비용 유/무','우대 사항','활동 지역','활동 기간']
 
= open('mobile_spec_up3.csv''w', encoding='ansi', newline='')
wr = csv.writer(f)
wr.writerow([total_list[0], total_list[1], total_list[2],total_list[3],total_list[4],total_list[5],
             total_list[6],total_list[7],total_list[8],total_list[9],total_list[10]])
f.close()
 
cs

 

저장될 csv의 제목을 따로 넣어주고 엑셀에서 바로 읽기 쉽게 ansi로 encoding

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
total_lists = []
for i in range(len(test_list)):
    driver.get(test_list[i])
    html = BeautifulSoup(driver.page_source , 'html.parser')
    content_tags = html.select('#postContent')
    # 본문 내용을 줄바꿈 기준으로 다 붙이기
    # 각 항목마다 쓸데없이 \xa0▷ 요게 붙여져
    content = ' '.join([tags.get_text().replace("\xa0","").replace("▷",""for tags in content_tags])
    # 제목가져오기
    titles = html.select("h2.tit")
    # 날짜가져오기
    dates = html.select("span.date.font_l")
    # 전체 개수를 알아보기 위해 t 변수 저장
    # find_location : 크롤링한 content를 처리해주기 위해서 만듬
    t = find_location('mobile_spec_up3.csv',titles, dates, content)
    total_lists.append(t)
cs

 

위에서 링크들을 저장한 test_list에서 한개씩 접속해서 내용들을 싹 긁는다

content를 가져와서 join으로 다 묶어버리고 반복되서 나오는 쓸데없는 문자처리좀 해주면 깔끔스

저장한 content, title, date를 find_location 함수로 인자로 넘겨버린다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def find_location(name,title,dates,test):
    
    fl=[]
    temp_list = []
    new_list= []
    
    
    ############################################
    #
    # 여기에 조건을 주자.
    #
    ############################################
 
    # 주최사 / 활동명 -> activity_name + 9
    activity_name = test.find("주최사")
    # 활동내용 -> activity_content + 4
    activity_content = test.find("활동내용")
    # 모집 기간 -> recruit_date + 5
    recruit_date = test.find("모집 기간")
    # 모집 인원 + 5
    recruit_people = test.find("모집 인원")
    # 모집 대상 + 16
    recruit_target = test.find("모집 대상(+졸업생 가능여부)")
    # 참가 비용 유/무 + 9 
    fee = test.find("참가 비용 유/무")
    # 우대 사항 + 5
    preferential_treatment = test.find("우대 사항")
    # 활동 지역 + 5
    activity_nation = test.find("활동 지역")
    # 활동 기간 + 5
    activity_period = test.find("활동 기간")
    # 활동 혜택 + 5
    activity_bonus = test.find("활동 혜택")
    
    #저장된 제목 값이 없으면, 저장 안 함
    if len(title) != 0:
        # 제목 처리
        titles = title[0].get_text()
#         title_sub = titles.find("(")
#         if title_sub != -1:
#             titles = titles[:title_sub]
        dates = dates[0].get_text()
        dates_ = re.sub(r"[.]","",dates)
        dates = dates_[:8]
        # find의 값이 없으면, -1을 리턴한다.
        # 활동 이름이 없으면,
        if activity_name != -1:
            fl.append(activity_name)
            fl.append(activity_content)
            fl.append(recruit_date)
            fl.append(recruit_people)
            fl.append(recruit_target)
            fl.append(fee)
            fl.append(preferential_treatment)
            fl.append(activity_nation)
            fl.append(activity_period)
            fl.append(activity_bonus)
 
            a1 = re.sub(r"[-]","",test[fl[0]+9:fl[1]].strip())
            a2 = re.sub(r"[-]","",test[fl[1]+4:fl[2]].strip())
            a3 = re.sub(r"[-]","",test[fl[2]+5:fl[3]].strip())
            a4 = re.sub(r"[-]","",test[fl[3]+5:fl[4]].strip())
            a5 = re.sub(r"[-]","",test[fl[4]+16:fl[5]].strip())
            a6 = re.sub(r"[-]","",test[fl[5]+9:fl[6]].strip())
            a7 = re.sub(r"[-]","",test[fl[6]+5:fl[7]].strip())
            a8 = re.sub(r"[-]","",test[fl[7]+5:fl[8]].strip())
            a9 = re.sub(r"[-]","",test[fl[8]+5:fl[9]].strip())
 
            if a1.find(','is not -1:
                a1 = a1[ :a1.find(',')].strip()
            if a1.find('/'is not -1:
                a1 = a1[ :a1.find('/')].strip()
 
#             a1_sub = a1.find("(")
#             if a1_sub != -1:
#                 a1 = a1[:a1_sub]
 
        #         temp_list=[titles,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13]
 
            temp_list=[titles,dates,a1,a2,a3,a4,a5,a6,a7,a8,a9]
            try:
                f = open(name, 'a+', encoding='ansi',newline='')
                wr = csv.writer(f)
                wr.writerow(temp_list)
                f.close()
            except:
                pass
 
    return temp_list
cs

 

content를 join으로 붙여놔서 형식이 이렇게 나올 것이다.

 

 

====================================

주최사 / 활동명한국 청소년 문화진흥협회, 토론 공교육 문화운동본부 대학생 연합 토론교육봉사 단체 "대학생 풍뎅이 봉사단 7기"활동내용-서울지역 3개 지부(수유, 홍대, 건대) 지역아동센터에서 토론교육 봉사를 실시(주 1회)-전국토론대회에서 심사위원이나 토론 진행을 맡는 퍼실리테이터로 위촉되어 활동(매 학기 1회)-프로그램 제작팀, 기획팀, 인프라 구축팀에 소속되어 팀 활동 진행-매월 마지막 주 토요일 총회 진행-토론활동지도사 3급 자격증 취득을 위한 자격증 교육 이수모집 기간7월 16일~8월 10일모집 인원00명모집 대상토론을 좋아하고 교육을 사랑하는 휴학생, 재학생.........

=====================================

 

 

그래서 주최사가 나오는 자릿수를 찾고, 그 다음 활동 내용이 등장하는 자릿수를 찾아서 그 사이의 값은 주최사구나 라고 판단하여 처리를 함

그 중에 주최사 / 활동명 중에서 필요한 컬럼은 주최사기 때문에 / 나 , 를 기준으로 앞에 거를 주최사로 생각하여 a1에 다시 저장한다.

 

정규식과 요로코롬 작업을 해주고 아까 만들었던 csv에 a+ 옵션을 줘서 덮어쓰기로 싹 쓰면 끝

temp_list를 return 하는건 몇 개 들어갔는지 확인하기 위함이므로 return을 안해도 된다.

 

 

결과물

 

카페에서 개인이 글을 쓰다보니 형식에 맞지 않는 거는 어쩔 수 없다.

이미지만 있는 것은 아예 배제

이제는 자유분방하게 작성하신 모집 기간이랑 활동 기간을 잘.......처리할 일만 남았다.