호쌤
호쌤 Just For Fun

[Python 데이터 수집] 네이버 뉴스목록 수집

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Python 데이터 수집] 네이버 뉴스목록 수집≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

[Python 데이터 수집] 네이버 뉴스목록 수집

네이버 뉴스 메인 페이지에는 각 분야별로 최신 기사로 이동할 수 있는 링크가 노출되어 있습니다. 이 링크들을 통해 뉴스기사 본문에 대한 URL들을 수집하여 리스트를 만들고 리스트에 저장된 URL들에 대한 반복문을 수행하면서 개별 컨텐츠를 수집하면 뉴스기사들을 일괄적으로 수집하는 것이 가능합니다.

jpg

#01. 필요한 모듈 참조

1
2
3
4
import requests                     # -> 웹 페이지 요청 모듈
import os                           # -> 운영체제 기능 접근 모듈(내장모듈)
import datetime as dt               # -> 날짜 처리 모듈(내장모듈)
from bs4 import BeautifulSoup       # -> 웹 페이지 소스코드 분석 모듈

#02. 수집 준비

1) 접속을 수행하기 위한 session 객체 생성

웹 페이지에서 데이터를 수집할 때 가장 처음에 위치해야 하는 코드입니다.

1
2
3
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
session = requests.Session()
session.headers.update( {'User-agent': user_agent, 'referer': None} )

2) 접근할 페이지 주소 (네이버 뉴스)

1
content_url = "https://news.naver.com"

3) 수집된 기사들이 텍스트로 저장될 폴더 생성

1
2
3
4
5
6
7
# 뉴스 기사가 저장될 폴더이름 구성
datetime = dt.datetime.now().strftime("%y%m%d_%H%M%S")
dirname = "뉴스기사_%s" % datetime

# 뉴스기사를 텍스트 파일로 저장할 폴더 만들기
if not os.path.exists(dirname):
    os.mkdir(dirname)

#03. 컨텐츠의 주소들을 수집하기

1) 네이버 뉴스 메인 소스코드 가져오기

1
2
3
4
5
r = session.get(content_url)

if r.status_code != 200:
    print("%d 에러가 발생했습니다." % r.status_code)
    quit()

2) 컨텐츠에 대한 태그 수집

CSS 선택자가 여러 개 지정될 경우 콤마로 구분할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 인코딩 설정
r.encoding = "euc-kr"

# 웹 페이지의 소스코드를 사용하여 HTML 분석 객체 생성
soup = BeautifulSoup(r.text, 'html.parser')

# CSS 선택자를 활용하여 가져오기를 원하는 부분 지정
# -> 여러 개의 선택자를 사용해야 할 경우 콤마(,)로 구분
selector = soup.select('.hdline_flick_item a, .hdline_article_tit a, .mtype_img a, .mlist2 a, .section ul li a')

if not selector:   # 가져온 내용이 없다면?
    print("크롤링 실패")
    quit()

print(len(selector))
selector
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
45

[<a class="lnk_hdline_main_article nclicks('mai.image', '88000114_000000000000000010814362', 'airsGParam', '0', 'news_qm_v2.0', 'bLRtlqhIdwuRAMYq')" href="/main/read.nhn?mode=LSD&amp;mid=shm&amp;sid1=102&amp;oid=056&amp;aid=0010814362">
<img alt="정부, ‘고강도 사회적 거리두기’ 2주 연장 결정" height="190" onerror="javascript:this.src='https://ssl.pstatic.net/static.news/image/news/2009/noimage_300x190.png';" src="https://imgnews.pstatic.net/image/056/2020/04/04/0010814362_001_20200404121421347.jpg?type=nf300_190" width="300"/>
<div class="hdline_flick_mask">
<p class="hdline_flick_tit">정부, ‘고강도 사회적 거리두기’ 2주 연장 결정</p>
</div>
</a>,
<a class="lnk_hdline_cluster_more nclicks('mai.clu', '88000114_000000000000000010814362', 'airsGParam', '0', 'news_qm_v2.0', 'bLRtlqhIdwuRAMYq')" href="/main/clusterArticles.nhn?id=c_202004041110_00000005&amp;mode=LSD&amp;mid=shm&amp;oid=056&amp;aid=0010814362">
<span class="blind">관련기사 개수</span>
<span class="cluster_more_icon_num">46</span>
</a>,
<a class="lnk_hdline_main_article nclicks('mai.image', '880000D8_000000000000000011522535', 'airsGParam', '0', 'news_qm_v2.0', 'bLRtlqhIdwuRAMYq')" href="/main/read.nhn?mode=LSD&amp;mid=shm&amp;sid1=100&amp;oid=001&amp;aid=0011522535">
<img alt=" 창원성산 다자대결로 가나…진보 후보단일화 무산[총선 D-11]" height="190" onerror="javascript:this.src='https://ssl.pstatic.net/static.news/image/news/2009/noimage_300x190.png';" src="https://imgnews.pstatic.net/image/001/2020/04/04/PYH2020040223210005200_P4_20200404100611701.jpg?type=nf300_190" width="300"/>
<div class="hdline_flick_mask">
<p class="hdline_flick_tit"> 창원성산 다자대결로 가나…진보 후보단일화 무…</p>
</div>
</a>,
...생략...
<a class="nclicks('hom.airscont','880003A5_000000000000000000027497', 'airsGParam', '0', 'news_sec_v2.0', 'PupUMYxoXNjzG9Wg')" href="https://news.naver.com/main/read.nhn?mode=LSD&amp;mid=shm&amp;sid1=105&amp;oid=092&amp;aid=0002185299">
<strong>초연결시대와 안 맞는 '망 분리', 규제 완화 공감대 형성</strong>
</a>]

3) 수집한 태그에서 URL만 추출

추출한 URL을 리스트에 저장하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 뉴스기사의 본문 URL을 저장할 리스트
url_list = []

# 리스트의 원소들에 대한 반복 처리
for item in selector:
    #print(type(item.attrs))
    #print(item.attrs)
    #print("-" * 30)
    
    # 각 원소(링크)에 속성들(attrs) 중에 href 속성이 있다면 그 값을 별도로 준비한 리스트에 추가
    if "href" in item.attrs:
        url_list.append(item['href'])

url_list

일부 URL은 사이트 도메인이 적용되어 있지 않음을 볼 수 있다.

▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
['/main/read.nhn?mode=LSD&mid=shm&sid1=102&oid=056&aid=0010814362',
'/main/clusterArticles.nhn?id=c_202004041110_00000005&mode=LSD&mid=shm&oid=056&aid=0010814362',
'/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011522535',
'/main/clusterArticles.nhn?id=c_202003291200_00000583&mode=LSD&mid=shm&oid=001&aid=0011522535',
'/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=009&aid=0004550730',
'/main/read.nhn?mode=LSD&mid=shm&sid1=102&oid=023&aid=0003520861',
'/main/read.nhn?mode=LSD&mid=shm&sid1=104&oid=001&aid=0011522519',
'/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=011&aid=0003719536',
'/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=277&aid=0004655692',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=422&aid=0000422606',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=056&aid=0010814359',
...생략...
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=104&oid=009&aid=0004550729',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=293&aid=0000027497',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=081&aid=0003079735',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=030&aid=0002876336',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=018&aid=0004612832',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=032&aid=0003001821',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=092&aid=0002185299']

앞 부분에 도메인이 제외된 경우 적용하기

1
2
3
4
5
6
7
8
9
10
11
12
news_url_list = []

for url in url_list:
    if "read.nhn" in url:
        if content_url not in url:
            url = content_url + url
        else:
            print(url)
            
        news_url_list.append(url)
        
news_url_list
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
['https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=102&oid=056&aid=0010814362',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011522535',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=009&aid=0004550730',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=102&oid=023&aid=0003520861',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=104&oid=001&aid=0011522519',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=011&aid=0003719536',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=277&aid=0004655692',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=422&aid=0000422606',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=056&aid=0010814359',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=003&aid=0009795825',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=009&aid=0004550730',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011522714',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=047&aid=0002264576',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=081&aid=0003079749',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=031&aid=0000532894',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=018&aid=0004612860',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=011&aid=0003719548',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=101&oid=001&aid=0011522713',
... 생략 ...
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=081&aid=0003079735',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=030&aid=0002876336',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=018&aid=0004612832',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=032&aid=0003001821',
'https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=092&aid=0002185299']

#04. 뉴스기사들 수집

전체적으로 반복문 안에서 수행되어야 하기 때문에 하나의 코드 블록에서 전 과정을 모두 구현해야 한다.

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
# URL 목록만큼 반복
for i, url in enumerate(news_url_list):
        
    print("%d번째 뉴스기사 수집중... >> %s" % (i+1, url))
    
    #-------------------------------------------------
    # 1) 뉴스 상세 페이지에 접속하여 HTML 소스코드 가져오기
    #-------------------------------------------------
    r = session.get(url)

    # 접속에 실패했다면 다음 항목으로 반복문의 제어를 이동
    if r.status_code != 200:
        print("%d 에러가 발생했습니다." % r.status_code)
        continue

    # 컨텐츠의 인코딩 형식 설정
    r.encoding = "euc-kr"
    
    #-------------------------------------------------
    # 2) 기사 영역 추출
    #-------------------------------------------------
    # HTML 코드 분석을 위한 객체 생성
    soup = BeautifulSoup(r.text, 'html.parser')
    
    #-------------------------------------------------
    # 3) 기사 영역에서 제목 추출
    #-------------------------------------------------
    # 제목영역 추출
    title = soup.select("#articleTitle")
    
    # 고유한 영역이므로 리스트의 0번째 항목에 직접 접근하여 공백 제거하고 추출
    title_str = title[0].text.strip()
    
    # 기사 제목에서 파일이름으로 사용할 수 없는 특수문자 제거
    title_str = title_str.replace("'", "")
    title_str = title_str.replace('"', "")
    title_str = title_str.replace("?", "")
    title_str = title_str.replace("/", "")
    title_str = title_str.replace(">", "")
    title_str = title_str.replace("<", "")
    
    # 둥근따옴표 제거
    # "`ㄴ`>한자" 입력후 표시되는 팝업에서 선택 가능함
    title_str = title_str.replace("‘", "") # `ㄴ`입력 후 `한자키` --> 1페이지에서 8번
    title_str = title_str.replace("’", "") # `ㄴ`입력 후 `한자키` --> 1페이지에서 9번
    title_str = title_str.replace("“", "") # `ㄴ`입력 후 `한자키` --> 2페이지에서 1번
    title_str = title_str.replace("”", "") # `ㄴ`입력 후 `한자키` --> 2페이지에서 2번
    
    print(title_str)
    
    #-------------------------------------------------
    # 4) 기사 영역에서 내용 추출
    #-------------------------------------------------
    # 본문 가져오기
    article = soup.select('#articleBodyContents')
    article_item = article[0]
    
    # 불필요한 태그 제거, 치환
    for target in article_item.find_all('script'): target.extract()
    for target in article_item.find_all('a'):      target.extract()
    for target in article_item.find_all('span'):   target.extract()
    for target in article_item.find_all('div'):    target.extract()
    for target in article_item.find_all('br'):     target.replace_with("\n")
    
    # 본문 텍스트 추출
    article_str = article_item.text.strip()
    
    #-------------------------------------------------
    # 5) 제목과 내용을 텍스트 파일로 저장
    #-------------------------------------------------
    # 제목과 내용이 모두 존재한다면?
    if title_str and article_str:
        # 기사 제목을 파일명으로 지정하여 내용을 텍스트로 저장한다.
        fname = dirname + "/" + title_str + ".txt"
        with open(fname, 'w', encoding='utf-8') as f:
            f.write(article_str)
            print(" >> 파일저장 성공: " + fname)
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
1번째 뉴스기사 수집중... >> https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=102&oid=056&aid=0010814362
정부, 고강도 사회적 거리두기 2주 연장 결정
>> 파일저장 성공: 뉴스기사_200404_123207/정부, 고강도 사회적 거리두기 2주 연장 결정.txt
3번째 뉴스기사 수집중... >> https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=001&aid=0011522535
[총선 D-11] 창원성산 다자대결로 가나…진보 후보단일화 무산
>> 파일저장 성공: 뉴스기사_200404_123207/[총선 D-11] 창원성산 다자대결로 가나…진보 후보단일화 무산.txt
5번째 뉴스기사 수집중... >> https://news.naver.com/main/read.nhn?mode=LSD&mid=shm&sid1=100&oid=009&aid=0004550730
황교안 미워하지 않겠다는 이낙연…손 내밀며 황 대표 칼끝 무디게 하기
>> 파일저장 성공: 뉴스기사_200404_123207/황교안 미워하지 않겠다는 이낙연…손 내밀며 황 대표 칼끝 무디게 하기.txt
... 생략 ...

수집이 완료되면 아래와 같이 뉴스기사들이 각각 테스트 파일로 저장되어 있는 것을 확인할 수 있습니다.

jpg

Rating:

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Python 데이터 수집] 네이버 뉴스목록 수집≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

comments powered by Disqus