반응형

안녕하세요.
분석할 csv 파일을 읽어올 때 시계열 데이터도 들어있는 경우도 많을 텐데요,
이때 전처리나 기본 분석을 할 때 도움이 될 수 있도록
날짜 및 시간 데이터를 pandas에서 다루는 방법에 대해 포스팅 하려고 합니다.
그럼 시작하겠습니다!

데이터 준비

데이터로는 공공데이터 API를 통해 생성한 코로나 확진자 관련 csv 파일을 준비해 보았습니다.
데이터 파일 만드는 방법은 아래의 링크를 참고해 주세요 :)
https://wonhwa.tistory.com/16?category=996518

[python] 공공데이터 OPEN API의 xml 을 DataFrame으로 변환하기(feat. 코로나 확진자 수)

안녕하세요~! 오늘은 공공데이터 openAPI의 xml을 Pandas DataFrame으로 변환하여 보도록 하겠습니다. json에서 DataFrame으로의 변환은 여기를 클릭해서 확인해 주세요 :) step1. 데이터 활용신청하기 공공데

wonhwa.tistory.com


저는 전에 만들어 둔 2020년 01월 20일부터 2022년 02월 23일까지의 시도별 코로나 확진자 현황 파일을 사용하였습니다.

Covid19Korea(200120220223).csv
1.75MB

파일이 필요하신 분은 위에서 다운받아 실습 진행하시면 됩니다!

csv 불러오기 및 데이터 타입 확인하기

그럼 위에서 다운받은 csv파일을 불러와 보도록 하겠습니다.
그리고, dtypes를 사용하여 파일 전체 열에 대해 데이터 타입이 어떻게 설정되어 있는지 확인합니다.

#데이터 불러오기
import pandas as pd
covid_df = pd.read_csv('Covid19Korea(200120220223).csv')
covid_df

14454행 x 14열 을 가진 데이터 프레임이 불러와졌습니다.
데이터 열에 관련해서는 아래를 참고해 주세요.

출처: 공공데이터 API 문서(코로나바이러스감염증_시도발생_현황조회서비스)
#데이터 타입 확인
covid_df.dtypes

데이터 타입을 확인해 보면, createDt(등록일시분초), std_day(기준일시) 등은 날짜형인데도 object로 설정되어있는 것을 확인할 수 있습니다.
(눈으로 볼때는 날짜형으로 보여도 실제 데이터 타입은 다를 수 있으니 항상 사전에 확인해 보시길 바랍니다.)
pandas에서 날짜 계산이나 추출을 위해서는 해당 열을 datetime 형으로 바꿔주어야 올바른 계산이 가능합니다.
때문에 날짜데이터로 형변환을 해 주도록 하겠습니다.

datetime으로 데이터 타입 변경하기

날짜 데이터로 변경할 때
1. to_datetime으로 변경하는 방법

2. parse_dates=[col_idx]
의 2가지 방법으로 변경이 가능합니다.
createDt열을 datetime으로 변경해 보도록 하겠습니다.
아래는 pd.to_datetime을 사용하여 변경하는 방법입니다.

#createDt열 날짜 데이터 타입으로 변경하기
covid_df['createDt'] = pd.to_datetime(covid_df['createDt'])

다음으로는 csv를 불러올 때 parse_dates = [열인덱스]를 사용하여 변경하는 방법입니다.
코로나csv 파일에서 createDt가 0번째 열이므로 [0] 을 입력해 줍니다.
그후 데이터 타입을 출력해 봅니다.

#createDt열 날짜 데이터 타입으로 변경방법 2: parse_dates=[col_idx]사용
covid_df2 = pd.read_csv('Covid19Korea(200120220223).csv', parse_dates=[0])
covid_df2.dtypes

그럼 아까 object였던 createDt열이 datetime으로 변경된 것을 확인할 수 있습니다.
추가로, stdDay도 datetime으로 바꿔보도록 하겠습니다.
다만, stdDay는

위처럼 한글이 섞여 있어 '년','월','일','시'를 제거 후 datetime으로 변경해 주겠습니다.

# stdDay열은 한글이 섞여 있으므로 한글 제거 후 datetime으로 변경
covid_df['stdDay'] = covid_df['stdDay'].replace(['년','월','일','시'],"",regex=True)
covid_df[['stdDay']]

replace를 사용하여 한글을 제거해 주었습니다.
그후 데이터 타입을 변경해 주는데,
pandas에서는 format으로 시간형식 지정자를 입력하여 데이터 타입 변경도 가능합니다.
format='%Y-%m-%d %H:%M'
등으로 지정 가능하며 의미는 아래와 같습니다.

출처: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

예시 부분은 영어를 한국어로 번역한 것이라 내용이 어색할 수 있습니다.
때문의 자세한 내용은 아래 링크에서 확인해 주세요.
https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior

datetime — Basic date and time types — Python 3.10.7 documentation

datetime — Basic date and time types Source code: Lib/datetime.py The datetime module supplies classes for manipulating dates and times. While date and time arithmetic is supported, the focus of the implementation is on efficient attribute extraction for

docs.python.org

stdDay는 시간까지의 정보가 있으므로 시간단위까지 format으로 지정해 주겠습니다.

covid_df['stdDay'] = pd.to_datetime(covid_df['stdDay'],format='%Y-%m-%d %H')

이렇게 stdDay열도 datetime으로 변경되었습니다.

세부날짜(년, 월, 일) 추출하기

datetime형의 데이터는 년, 월, 일 등을 각각 뽑아낼 수 있습니다.
해당 날짜 데이터 셀.year을 사용하여 아래처럼 년도를 추출할 수 있습니다.

covid_df['stdDay'][0].year

주의할 점은 df프레임에서 바로 .year을 하면 오류가 나고, covid_df['stdDay'][0] 처럼 개별 데이터로 접근 후 .year을 해야
원하는 정보를 찾을 수 있습니다.
열별로 한번에 데이터를 얻고 싶으면 dt를 사용하여 한번에 추출할 수 있습니다.
예시로,
createDt에서 년, 월, 일을 추출해 각각 새로운 컬럼을 만들어 거기에 저장해 주도록 하겠습니다.

데이터프레임열.dt.year
데이터프레임열.dt.month
데이터프레임열.dt.day

를 사용하여 추출할 수 있습니다.

# 년월일 추출하여 새로운 열로 추가하기
covid_df['Year'],covid_df['Month'],covid_df['Day'] = covid_df['createDt'].dt.year, covid_df['createDt'].dt.month, covid_df['createDt'].dt.day
covid_df

이렇게 끝에 년, 월, 일 3개의 열이 추가가 되었습니다.

날짜의 계산

이번에는 날짜 계산을 해보도록 하겠습니다.
코로나 확진자가 한국에 처음 발생한 날짜를 찾아, 발생일로부터 얼마나 지났는지 계산해 보겠습니다.
코로나 확진자가 최초로 발생한 날은 createDt열의 .min()함수를 사용하여 알 수 있습니다.

# 코로나 최초 발병일로 부터 며칠이 지났는지 계산하기
## 날짜의 계산 ##
# 최초 발생자가 있는 날: 2020년 1월 20일(인천 1명)
covid_df['createDt'].min()

이것을 기준으로 하여 각 행마다 며칠이 지났는지 계산해 보겠습니다.
각 행의 createDt에서 최초 발생일을 빼서 구할 수 있습니다.
outbreak_day = 등록일시 - 최초발생일

# outbreak_day 열을 만들어 발병일로 부터 며칠 지났는지 계산하기
covid_df['outbreak_day'] = covid_df['createDt'] - covid_df['createDt'].min()
covid_df

맨 끝 열에 outbreak_day가 추가되었습니다.

특정 기간의 데이터 추출

이번에는 특정 날짜의 데이터를 loc을 사용하여 추출해 보도록 합시다.

df.loc[df.날짜데이터열이름.dt.(year,month,day) == 해당 (년,월,일)]

위처럼 조건을 지정하여 추출이 가능합니다.
예시로 코로나 데이터의 2021년 데이터만 추출해 보도록 하겠습니다.

## 특정 기간의 데이터 추출
# 2021년 데이터 추출
test1_df = covid_df.loc[covid_df.createDt.dt.year == 2021]
test1_df

두 가지 이상의 조건도 설정 가능합니다.
이때 '&'을 사용하여 and조건을 사용할 수 있습니다.

df.loc[df(조건1) & df( 조건 2 ) ]
#2022년 1월 데이터만 추출(and 조건은 & 로 표시)
test2_df = covid_df.loc[(covid_df.createDt.dt.year == 2021) & (covid_df.createDt.dt.month == 1)]
test2_df

or 조건은 | (Shift+\) 를 사용하여 설정할 수 있습니다.

df.loc[df(조건1) | df( 조건 2 ) ]
#2020년도 또는 2021년도 데이터 추출(or 조건은 | 로 표시)
test3_df = covid_df.loc[(covid_df.createDt.dt.year == 2020) | (covid_df.createDt.dt.year == 2021)]
test3_df

이렇게 데이터를 추출 할 수 있습니다.
다만, 위 사진을 보면 맨 첫행 인덱스가0 이아닌 1026으로 되어 있습니다. 때문에 추후 분석할 때는 인덱스를 0부터 재세팅해주는 과정이 필요합니다.
인덱스 재세팅 방법은 아래 날짜별 값 계산에서 확인해 보도록 하겠습니다.

날짜별 값 계산하기(groupby)

처음 언급했듯이 사용되고 있는 데이터는 2020년부터 2022년 2월까지의 데이터를 담고 있습니다.
그리고 분석 시 년도별 코로나 발생 인원이 몇명인지 알고 싶을 때 groupby를 사용하여 계산을 해 보겠습니다.
우선, 데이터를 보면 시도 데이터 뿐만이 아니라 합계, 검역 같은 데이터 행도 있습니다.
여기서 전체 발생인원을 파악하여야 하니까
'합계' 행만 추출하여 계산하도록 하겠습니다.
우선 원본 df에서 gubun이 '합계'인 행만 추출해 줍니다.

# groupby를 사용하여 년도별 코로나 확진자 수 구하기
# '합계' 를 기준으로 하여 전국 년도별 코로나 확진자 수 구하기
#1. gubun 이 합계인 행만 추출하기
total_df = covid_df.loc[covid_df['gubun']=='합계']
total_df

이렇게 766행이 선택되었습니다.
그런데, 맨 첫 행이 0이 아니라 18 입니다. 때문에 인덱스를 0부터 다시 설정해 줍니다.

# 2. index 재세팅하기
new_idx = [i for i in range(len(total_df))]
total_df.index = new_idx
total_df

그럼 0부터 765까지 전체 행의 인덱스가 잘 정리되었습니다.
그 후 groupby를 사용하여 'Year'을 기준으로 합계를 구해 줍니다.
우리가 필요한 정보는 코로나 확진자 정보이므로 'incDec' (전일 대비 코로나 환자 수) 열의 합계만을 출력해 보겠습니다.

#groupby로 년도별 확진자 합계 확인하기(incDec: 전일대비 증감수)(2022년도는2월 23일까지)
defCnt_sum = total_df.groupby(['Year']).sum()
defCnt_sum[['incDec']]

그럼 2020년, 2021년, 2022년(1월~2월 23일까지)의 확진자 수가 집계되었습니다.
위의 데이터를 보면, 2022년도는 1,2월 두달 치의 정보만 있는데도 그 전 년도의 확진자 수를 훨씬 넘어서는 것을 확인할 수 있습니다.

날짜를 인덱스로 설정하기

datetime 을 인덱스로 지정하면 따로 조건을 지정하지 않아도 원하는 날짜 부분의 추출이 바로 가능합니다.
우선 합계 df 인 total_df가 최신 날짜 순으로 지정되어 있어 오래된 날짜 순으로 정렬한 뒤,
인덱스를 datetime 인 createDt열로 바꾸어 보겠습니다.

# 날짜를 인덱스로 설정하기
total_df = total_df.sort_values('createDt') #날짜 오래된 순으로 정렬
#인덱스를 createDt로 변경
total_df.index = total_df['createDt']
total_df

이렇게 createDt로 인덱스가 세팅 되었습니다.
또는 date_range를 사용해서도 날짜인덱스 설정이 가능한데,
주의할 점은 날짜형의 열을 index로 설정 후 reindex를 해야 한다는 점입니다.

pd.date_range(start = '시작날짜' , end= '종료날짜')
# 날짜 인덱스 설정 방법2(시간 범위): date_range
index_dates = pd.date_range(start=total_df['createDt'].min(), end=total_df['createDt'].max())
# or
index_dates = pd.date_range(start='2020-01-20', end='2022-02-23')
index_dates
# index_dates로 날짜형으로 변경하기
## reindex
# ※date range 사용 시에는 꼭 날짜형인 'createDt'열을 먼저 인덱스로 지정 후 아래와 같이 reindex 하기 ※
total_df.reindex(index_dates)
total_df

이렇게 인덱스가 날짜형으로 세팅되면 loc을 사용할 필요 없이 df['날짜']로 필요한 부분을 바로 추출할 수 있습니다.

#2020년도 12월 데이터만 추출하기
total_df['2020-12']

위의 .loc을 사용할 때보다 더욱 간편하게 원하는 기간의 추출이 가능합니다만,
위 사진 빨간 박스에 있는 경고메세지에서는 곧 이 기능이 사라진다고 하니 참고해 주시길 바랍니다.
loc을 사용하라고 권장하고 있네요.

시각화

마지막으로 위의 날짜로 인덱스를 세팅한 데이터프레임에서 전체 기간의 코로나 확진자 수(incDec)를 시각화 해보도록 하겠습니다.

# 확진자 수 시각화
#그래프 그리기
import matplotlib.pyplot as plt
ax = plt.subplots()
ax = total_df['incDec'].plot()
plt.show()

그래프도 볼 수 있듯이 2022년 들어서 확진자가 매우 가파르게 증가하는 것을 확인할 수 있습니다.

전체 코드
TimeSeriesData.ipynb
0.20MB
참고자료

1. pandas 공식문서
https://pandas.pydata.org/docs/user_guide/timeseries.html

Time series / date functionality — pandas 1.5.0 documentation

Time series / date functionality pandas contains extensive capabilities and features for working with time series data for all domains. Using the NumPy datetime64 and timedelta64 dtypes, pandas has consolidated a large number of features from other Python

pandas.pydata.org

2. [도서] Do it! 데이터 분석을 위한 판다스 입문(이지스 퍼블리싱)

마무리

이렇게 시계열 데이터 다루기 1편의 포스팅을 해 보았습니다.
생각보다 분량이 길어졌지만 시계열 데이터를 다룰 때 많은 도움이 될 것이라 생각합니다 :)

반응형
반응형

안녕하세요! 포스팅을 오랜만에 하네요.

오늘은 감성분석이 무엇인지, 또 어떤 방법으로 감성분석을 할 수 있는지 같이 알아보도록 하겠습니다.

 

감성분석(Sentiment Analysis)이란?

감성분석(Sentiment Analysis)은 여러 종류의 글(텍스트) 안에 있는 감성을 분석하는 것입니다.

그렇다면 감성(Sentiment)이란 무엇일까요?

감성(Sentiment)은 감정(Emotion)이라고도 할 수 있고, 감정은 주관적입니다.

예를 들어, 아래 나비사진을 봅시다.

출처: Pixabay

같은 나비를 보더라도 어떤 사람은 아름답다고 생각하는 긍정적인 반응을 보이는 한편,

곤충을 싫어하는 사람에게는 나비의 무늬가 징그럽다고 생각하는 부정적인 반응을 보일 수 도 있습니다.

또는 아무 생각 없이 그냥 나비구나 하는 사람도 있을 것입니다. 이것을 중립적으로 본다고 하겠습니다.

 

또한 감성은 텍스트에서도 나타납니다.

"해리포터 책은 마치 마법사가 실존하는 것처럼 생생하게 묘사한다." 라는 문장이 있을 때, 

어떤 사람은 이 문장을 긍정적으로 볼 것이며, 어떤  사람은 중립적으로 볼 수도 있을 것입니다.

반면, "이 영화는 재미가 없다."라는 문장은 부정적으로 볼 가능성이 클 것입니다.

 

감성 분류

텍스트를 감성 분석할 때,  감성을  3가지로 분류할 수 있습니다.

1. 이 텍스트가 긍정(Positive)인지

2. 중립(Neutral)인지

3. 아니면 부정(Negative) 인지

때로는 긍정, 부정 2가지만으로 분류하여 분석할 때도 있습니다.

이런 감성의 정도를 극성(Polarity)라고 합니다.

긍정, 중립, 부정 을 그림으로 나타내면 아래와 같이 표현할 수 있습니다.

0은 중립, +(양수)는 긍정, -(음수)는 부정을 나타냅니다.

 

감성 분석 방법

감성 분석을 하는 방법은 여러 가지가 있지만 크게 2가지로 나눈다면

1. 감성 사전을 활용한 감성분석

2. 기계학습(Machine Learning)을 사용하는 감성분석

으로 나눌 수 있습니다.

 

1. 감성사전을 활용한 감성분석

 

감성 사전은 명사, 형용사, 동사의 모든 단어들을 긍정 또는 부정으로 라벨링 하여 구축할 수 있습니다.

감성 사전은 직접 구축할 수도 있고(시간이 많이 걸립니다.), 기존에 만들어진 사전을 사용할 수도 있습니다.

감성사전을 기반으로 문장의 감성을 파악할 때는 아래와 같이 분류할 수 있습니다.

어떤 문장이 있다면 그 문장을 명사, 동사, 형용사 기준으로 형태소 분석을 하여

사전에 구축된 감성 사전에서 해당 단어와 매칭 되는 점수를 찾아

전부 더해서 최종적으로 나온 점수를 기준으로 문장의 감성을 판단할 수 있습니다.

그렇다면 아래의 문장은 어떻게 판단될까요?

"해리포터 영화의 영상미가 좋다고 해서 영화가 좋은 것은 아니다."

위에서 단순히 사전에서 매칭 되는 단어를 찾아 그 점수를 각각 더한다면 

위와 같이 최종적으로 1점이 되어 긍정 문장으로 판단이 될 것입니다.

하지만 문장 전체를 보면 과연 긍정적이라고 판단할 수 있을까요?

이러한 오류를 방지하고자 문장을 구로 묶어서 단계적으로 감성을 판단하는 방법을 사용할 수도 있습니다.

이를 청킹(chunking)이라고 합니다.

아래는 스탠포드에서 제공하는 트리 감성분석 입니다.

파란색일수록 긍정적이며, 붉을수록 부정적임을 나타냅니다.

https://nlp.stanford.edu/sentiment/treebank.html?w=this%2Cmovie&na=3&nb=10 

 

Recursive Deep Models for Semantic Compositionality Over a Sentiment Treebank

Displaying N sentences that satisfy the following criteria.

nlp.stanford.edu

 

출처: nlp.stanford.edu

 

2. 기계학습(Machine Learning)을 사용하는 감성분석

기계학습을 이용하여 감성분석을 할 때에는 학습 셋이 필요합니다.

머신러닝의 지도학습으로, 각 텍스트 데이터에는 긍정인지 부정인지를 구분하는 라벨링을 해주어야 합니다.

각 테스트 데이터는 문제, 라벨 데이터는 답이라고 할 수 있습니다.

그 후 train, test 데이터로 나누어 train 데이터를 가지고 학습을 하고, test 데이터로 학습이 잘 되었는지 검증합니다.

또한 train데이터를 가지고 여러 알고리즘을 사용하여 최적의 학습이 된 모델로

새로운 문장이 긍정인지 부정인지 예측할 수도 있습니다.

기계학습 기반의 감성분석은 학습데이터가 매우 중요합니다.

만약 영화 리뷰를 가지고 학습을 한 모델을 가지고 영화 리뷰와 관련된 문장을 예측하는 것은 예측 정확도가 높을 수 있습니다.

하지만 그 모델을 그대로 의료 서비스 리뷰에 적용을 한다면 이때의 문장 감성분석 정확도는 장담할 수 없습니다.

이럴 때는 주제에 맞게 모델을 수정하여 예측하여야 합니다.

 

참고자료

위키북스 - 파이썬 텍스트 마이닝 완벽 가이드: 자연어처리 기초부터 딥러닝 기반 BERT 모델까지

마무리

이렇게 간단하게 감성분석에 대해 알아보았습니다.

다음 포스팅에는 python 감성 분석 실습과 관련하여 글을 업로드하도록 하겠습니다.

 

반응형
반응형

안녕하세요. 정말 오랜만의 포스팅입니다.

그동안 올렸던 네이버 뉴스 크롤러 최신 버전을 공유드리고자 합니다.

그 전 버전인 아래 포스팅에서는

https://wonhwa.tistory.com/46#comment18426434

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

 

Selenium을 사용하여 크롤러를 만들었지만 날짜 내용이 빠지고 Selenium을 가동 시간이 길어 다소 불편한 점도 있었습니다.

그래서 이번에는 Selenium을 사용하지 않고 바로 주소를 파싱하여 네이버 뉴스 크롤링 하는 방법을 공유드리고자 합니다.

크롤링 제작의 자세한 내용은 그 전 포스팅에 나와있으니

자세한 설명이 필요하신 분들은 제 블로그 Crawling 카테고리에서 확인해 주시면 됩니다:)

그럼 시작하도록 하겠습니다.

1. 라이브러리 불러오기
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import datetime
from tqdm import tqdm
import sys

 

2. 크롤링 시 필요한 함수 만들기
# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)

# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)

def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls    

# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

#html생성해서 기사크롤링하는 함수 만들기(url): 링크를 반환
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")

    url_naver = html.select("div.group_news > ul.list_news > li div.news_area > div.news_info > div.info_group > a.info")
    url = news_attrs_crawler(url_naver,'href')
    return url

 

3. 크롤링할 네이버 뉴스 URL 추출하기
#####뉴스크롤링 시작#####

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   


# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
news_dates = []
for i in url:
    url = articles_crawler(url)
    news_url.append(url)


#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist

    
#제목, 링크, 내용 담을 리스트 생성
news_url_1 = []

#1차원 리스트로 만들기(내용 제외)
makeList(news_url_1,news_url)

#NAVER 뉴스만 남기기
final_urls = []
for i in tqdm(range(len(news_url_1))):
    if "news.naver.com" in news_url_1[i]:
        final_urls.append(news_url_1[i])
    else:
        pass

 

4.뉴스 본문 및 날짜 크롤링하기
# 뉴스 내용 크롤링

for i in tqdm(final_urls):
    #각 기사 html get하기
    news = requests.get(i,headers=headers)
    news_html = BeautifulSoup(news.text,"html.parser")
    # 뉴스 제목 가져오기
    title = news_html.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    if title == None:
        title = news_html.select_one("#content > div.end_ct > div > h2")
    
    # 뉴스 본문 가져오기
    content = news_html.select("article#dic_area")
    if content == []:
        content = news_html.select("#articeBody")
        
    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=str(title))
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    news_titles.append(title)
    news_contents.append(content)

    try:
        html_date = news_html.select_one("div#ct> div.media_end_head.go_trans > div.media_end_head_info.nv_notrans > div.media_end_head_info_datestamp > div > span")
        news_date = html_date.attrs['data-date-time']
    except AttributeError:
        news_date = news_html.select_one("#content > div.end_ct > div > div.article_info > span > em")
		news_date = re.sub(pattern=pattern1,repl='',string=str(news_date))
    # 날짜 가져오기
    news_dates.append(news_date)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(final_urls)
print("\n[뉴스 내용]")
print(news_contents)

print('news_title: ',len(news_titles))
print('news_url: ',len(final_urls))
print('news_contents: ',len(news_contents))
print('news_dates: ',len(news_dates))

 

5. 데이터 프레임 만들고 CSV파일로 저장하기
###데이터 프레임으로 만들기###
import pandas as pd

#데이터 프레임 만들기
news_df = pd.DataFrame({'date':news_dates,'title':news_titles,'link':final_urls,'content':news_contents})
news_df

#중복 행 지우기
news_df = news_df.drop_duplicates(keep='first',ignore_index=True)
print("중복 제거 후 행 개수: ",len(news_df))

#데이터 프레임 저장
now = datetime.datetime.now() 
news_df.to_csv('{}_{}.csv'.format(search,now.strftime('%Y%m%d_%H시%M분%S초')),encoding='utf-8-sig',index=False)

 

이렇게 업그레이드 된 크롤러가 완성이 되었습니다 :)

예시 실행화면

전체 코드
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import datetime
from tqdm import tqdm
import sys

# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)

# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)

def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls    

# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

#html생성해서 기사크롤링하는 함수 만들기(url): 링크를 반환
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")

    url_naver = html.select("div.group_news > ul.list_news > li div.news_area > div.news_info > div.info_group > a.info")
    url = news_attrs_crawler(url_naver,'href')
    return url


#####뉴스크롤링 시작#####

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   


# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
news_dates = []
for i in url:
    url = articles_crawler(url)
    news_url.append(url)


#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist

    
#제목, 링크, 내용 담을 리스트 생성
news_url_1 = []

#1차원 리스트로 만들기(내용 제외)
makeList(news_url_1,news_url)

#NAVER 뉴스만 남기기
final_urls = []
for i in tqdm(range(len(news_url_1))):
    if "news.naver.com" in news_url_1[i]:
        final_urls.append(news_url_1[i])
    else:
        pass

# 뉴스 내용 크롤링

for i in tqdm(final_urls):
    #각 기사 html get하기
    news = requests.get(i,headers=headers)
    news_html = BeautifulSoup(news.text,"html.parser")

    # 뉴스 제목 가져오기
    title = news_html.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    if title == None:
        title = news_html.select_one("#content > div.end_ct > div > h2")
    
    # 뉴스 본문 가져오기
    content = news_html.select("article#dic_area")
    if content == []:
        content = news_html.select("#articeBody")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=str(title))
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    news_titles.append(title)
    news_contents.append(content)

    try:
        html_date = news_html.select_one("div#ct> div.media_end_head.go_trans > div.media_end_head_info.nv_notrans > div.media_end_head_info_datestamp > div > span")
        news_date = html_date.attrs['data-date-time']
    except AttributeError:
        news_date = news_html.select_one("#content > div.end_ct > div > div.article_info > span > em")
        news_date = re.sub(pattern=pattern1,repl='',string=str(news_date))
    # 날짜 가져오기
    news_dates.append(news_date)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(final_urls)
print("\n[뉴스 내용]")
print(news_contents)

print('news_title: ',len(news_titles))
print('news_url: ',len(final_urls))
print('news_contents: ',len(news_contents))
print('news_dates: ',len(news_dates))

###데이터 프레임으로 만들기###
import pandas as pd

#데이터 프레임 만들기
news_df = pd.DataFrame({'date':news_dates,'title':news_titles,'link':final_urls,'content':news_contents})

#중복 행 지우기
news_df = news_df.drop_duplicates(keep='first',ignore_index=True)
print("중복 제거 후 행 개수: ",len(news_df))

#데이터 프레임 저장
now = datetime.datetime.now() 
news_df.to_csv('{}_{}.csv'.format(search,now.strftime('%Y%m%d_%H시%M분%S초')),encoding='utf-8-sig',index=False)

 

참고자료

https://wonhwa.tistory.com/8?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 크롤링하기(1)

오늘은 전에 알려드린 오픈 API 사용과 더불어 파이썬으로 크롤러 만드는 방법을 소개하도록 하겠습니다. 네이버 오픈 API의 경우 사용하는 방법을 알면 간편하게 뉴스, 블로그, 카페 등등을 크롤

wonhwa.tistory.com

https://wonhwa.tistory.com/11?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 크롤링하기(2)

오늘은 저번에 올린 네이버 뉴스 크롤링(1)에서 한 단계 업그레이드된 뉴스 크롤러를 공유하려 합니다 :) 1편에서는 뉴스 1페이지만 크롤링을 할 수 있었는데요 2편에서는 여러 페이지를 크롤링

wonhwa.tistory.com

https://wonhwa.tistory.com/46?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

 

마무리

크롤링 하실때 더 빠르고 편하게 하시길 바랍니다:)

기타 의견이 있으시거나 안되는 부분이 있으시다면 댓글 남겨주세요!

++08/10 날짜 크롤링 에러 오류 및 title 길이 수정

++08/11 오류 수정 및 df 중복 행 제거 추가

++23/08/26 뉴스 내용 가져오는 selector 수정

반응형
반응형

저번 포스팅에 이어서 mecab이라는 가상환경을 만들어 jupyter lab에서 연결하는 방법을 알아보도록 하겠습니다.

 

1. 가상 환경 생성하기

anaconda prompt를 열고 아래의 명령어를 입력하여 사용할 가상환경을 만들어 주세요.

이미 가상환경이 있는 분들은 이 단계를 건너 뛰어도 됩니다.

conda create -n 가상환경 이름 python=파이썬 버전
사용 예) conda create -n mecab python=3.8.0

그 후 가상환경을 사용할 폴더로 'cd 폴더'를 사용하여 이동하여 줍니다.

 

2. 가상 환경 활성화하기

다음으로 가상환경을 활성화 하여 보겠습니다.

 conda activate 가상환경 이름
사용 예)  conda activate mecab

이렇게 하면 맨 왼쪽 부분에 (base)에서 (가상환경 이름)으로 바뀌면서 활성화됩니다.

 

3. 가상 환경 커널 추가를 도와주는 패키지파일 설치하기

jupyter lab에 가상환경을 연동할 때 필요한 ipykernel 패키지를 설치해 줍니다.

conda install ipykernel

 

4. ipykernel로 jupyterlab 커널 가상환경 추가하기

이제 아래의 명령어를 입력하여 가상환경 커널을 추가해 줍니다.

(이 때, 사용하고자 하는 가상환경이 프롬프트에서 활성화되어있어야 합니다.)

python -m ipykernel install --user --name 가상환경이름 --display-name 가상환경이름
사용 예)
python -m ipykernel install --user --name mecab --display-name mecab

 

5. jupyter lab에서 가상환경 커널 연결하기

커널 추가가 완료되었으면 아래의 명령어로 가상환경을 비활성화하여 줍니다.

conda deactivate

다음으로, jupyter lab을 실행하여 줍니다.

jupyter lab

그 후 .ipynb 파일을 하나 만든 후 , 아래쪽에 Python3 (ipykernel)| Idle 을 클릭 해 줍니다.

그러면 아래와 같이 커널 선택 창이 나오는데

커널 목록에서 아까 추가한 가상환경을 클릭 한 후 select 를 눌러 적용해 주면 완료됩니다.

이렇게 하면 jupyter notebook에서 원하는 환경으로 코딩이 가능합니다 :)

반응형
반응형

안녕하세요.

형태소 분석기 중 하나인 MeCab을 Linux 환경에서만 사용 가능한 줄 알았는데

Windows에서도 사용할 수 있는 방법이 있어 설치 방법을 공유하고자 합니다.

python, R 각각의 설치 방법을 알려 드리도록 하겠습니다.

Python에 MeCab 설치하기

1.  "C:\"에 mecab 폴더 만들어 주기

C:\mecab 안에 다운로드 파일들을 집어넣을 수 있도록 mecab 폴더를 생성해 줍니다.

2.  mecab-ko-msvc 설치 및 mecab 폴더에 압축해제

https://github.com/Pusnow/mecab-ko-msvc/releases/tag/release-0.9.2-msvc-3

 

Release release-0.9.2-msvc-3 · Pusnow/mecab-ko-msvc

Fix: -r 옵션을 지정해 주지 않았을 때 레지스트리에서 이상한 값을 가져와 오류가 나는 문제 해결

github.com

위 링크에서 컴퓨터 버전에 맞는 zip파일을 다운받아 주세요.

저는 mecab-ko-msvc-x64.zip 을 다운받았습니다.

그 후 1번에서 만든 mecab 폴더에 zip 파일을 옮긴 후 압축해제해 주세요.

압축해제시 여기에 풀기를 눌러 압축해제해 주세요.

3. mecab-ko-dic-msv 설치 및 mecab 폴더에 압축해제

https://github.com/Pusnow/mecab-ko-dic-msvc/releases/tag/mecab-ko-dic-2.1.1-20180720-msvc-2

 

Release mecab-ko-dic-2.1.1-20180720-msvc-2 · Pusnow/mecab-ko-dic-msvc

코스트 반영을 위한 compile-win.ps1 추가

github.com

위의 링크에서 mecab-ko-dic-msvc.zip을 다운받아 주세요.

그 후 2번과 같이 전에 만든 mecab 폴더에 zip 파일을 옮긴 후 압축해제해 주세요.

압축해제시 여기에 풀기를 눌러 압축해제해 주세요.

4. anaconda로 가상환경 만들고 파이썬 3.8버전으로 설치하기

지금 제가 사용하는 python은 3.9버전이어서 3.8버전으로 파이썬을 설치하기 위해 가상환경을 만들어 주도록 하겠습니다.

anaconda prompt를 열어 아래의 명령어를 입력해 주세요.

conda create -n mecab python=3.8.0

mecab이라는 가상환경을 만들어 python 3.8.0버전을 설치해 줍니다.

중간에 proceed? y/n 라고 커맨드 창에 뜨는데 y를 입력 후 엔터를 눌러주면 완료됩니다.

5. python wheel 다운받고 설치하기

https://github.com/Pusnow/mecab-python-msvc/releases/tag/mecab_python-0.996_ko_0.9.2_msvc-3

 

Release mecab_python-0.996_ko_0.9.2_msvc-3 · Pusnow/mecab-python-msvc

Add Python 3.8 support

github.com

위의 링크에서 컴퓨터와 파이썬 버전에 맞는 파일을 다운받아 주세요.

파이썬 가장 최신버전이 3.8이어서 저는 3.8버전이고 64bit인

'mecab_python-0.996_ko_0.9.2_msvc-cp38-cp38-win_amd64.whl' 을 다운받았습니다.

다운받은 'mecab_python-0.996_ko_0.9.2_msvc-cp38-cp38-win_amd64.whl' 파일을 mecab 가상환경 site-packages에 위치시켜 주세요.

정확한 경로는 아래와 같습니다.

C:\Users\I사용자\anaconda3\envs\mecab\Lib\site-packages

그 후 Anaconda prompt를 열고

 conda activate mecab

을 입력하여 mecab 가상환경을 활성화 해 줍니다.

그 후 아래의 명령어를 입력하여 whl파일이 있는 곳으로 터미널을 위치시켜 줍니다.

cd C:\Users\사용자\anaconda3\envs\mecab\Lib\site-packages

다음으로 whl파일을 설치해 줍니다.

pip install mecab_python-0.996_ko_0.9.2_msvc-cp38-cp38-win_amd64.whl

파일이름이 너무 길면 pip install mecab을 친 후 tab키를 누르면 자동으로 나머지 부분이 입력이 됩니다.

이렇게 python에서 mecab을 설치가 모두 끝났습니다.

6. python에서 mecab 가상환경의 interpreter 설정해 주기

python에서 mecab을 사용하기 전에 interpreter를 가상환경의 interpreter로 설정해 주어야 합니다.

python 프로젝트를 위한 폴더를 만든 후 파이참에서 폴더를 open해 주세요.

저는 mecab_ko라는 폴더를 생성하여 open해 주었습니다.

그 후 파이참 우측 상단의 톱니바퀴를 눌러 Settings로 들어가 줍니다.

나타난 settings창에서 python interpreter를 누르고

Virtualenv Environment > Existing environment >

C:\Users\사용자\anaconda3\envs\mecab\python.exe > OK 를 눌러 설정을 완료해 줍니다.

그러면 아래와 같이 mecab-python이 설치되어 있는 것을 확인할 수 있습니다.

7. python에서 mecab 사용해 보기

파이썬 파일을 하나 만들고 그 위에다 아래와 같이 예제 코드를 입력하여 실행이 잘 되는지 확인해 봅시다.

import MeCab
mecab = MeCab.Tagger()
out = mecab.parse("오늘은 맑은 날씨이다.")
print(out)

Cntl+Shift+F10을 눌러 코드를 실행 시켜서

위와 같이 결과가 잘 출력되면 설치가 완료되었습니다. 이후 필요한 형태소 분석에 mecab을 사용하면 됩니다.

가상 환경을 종료하고 싶을 때는 명령창에 conda deactivate를 입력하여 종료해 주면 됩니다.

 

R에서 MeCab 설치하기

이번에는 R에서 MeCab을 설치해 보도록 하겠습니다.

RStudio를 열어 준 후 r스크립트를 하나 만들어 줍니다.

그 후 아래의 명령어를 입력 후 Ctrl + Enter를 눌러 실행해 줍니다.

#mecab설치
install.packages('remotes')
library(remotes)
install_github("junhewk/RcppMeCab")

설치가 완료 되었으면 문장 하나를 임의로 입력하여 분석을 해 줍니다.

여기서 주의할 점은 text를 utf8로 변경해 주어야 출력시 한글이 깨지지 않고 출력됩니다.

library(RcppMeCab)
#text를 utf8로 변경 후 분석
text = enc2utf8('오늘은 날씨가 맑습니다.')
pos(sentence = text)

위의 사진처럼 출력이 잘 되면 R에서도 mecab을 사용하여 분석이 가능합니다.

그럼 모두들 즐거운 형태소 분석 하시길 바랍니다 ㅎㅎ

반응형
반응형

pycharm에 아나콘다 연동하는 방법을 알아보도록 하겠습니다.

먼저 파이참 프로젝트를 열고,

우측 상단에 설정을 클릭해 줍니다.

(또는 Ctrl + Alt + S 클릭하여 설정 창을 띄어 줍니다.)

그 후 아래와 같이 설정창에서

창에서 'Project: 프로젝트이름' > Python interpreter를 클릭하여 줍니다. 

그 후 나오는 화면 왼쪽의 톱니바퀴 모양을 클릭한 후, 'Add' 누릅니다.

Existing environment 선택 후 옆에 있는 '...' 을 클릭합니다.

그 후 anaconda가 설치된 파일의 폴더로 들어가 python.exe를 클릭하여 준 후 OK를 눌러 줍니다.

(아나콘다 설치 시 따로 경로지정을 하지 않았다면, 보통 Users\users\anaconda3\python.exe에있습니다.)

다시 OK를 눌러 저장해 줍니다.

다시 OK를 누르면 설정이 완료됩니다.

그후 파이참을 닫고 다시 실행해 주면 아나콘다 설정이 완료됩니다 :)

궁금한 것은 댓글로 남겨주세요 ㅎㅎ

반응형
반응형

안녕하세요!

오랜만입니다 ㅎㅎ

오늘은 티스토리 블로그에 html 파일을 넣어서 보여줄 수 있는 방법을 알아보도록 하겠습니다.

티스토리 블로그에는 html로 블로그 글을 편집할 수 있는데요,

html 편집기로 iframe을 사용하여 html파일을 블로그에 보여줄 수 있습니다.

그럼 시작하도록 하겠습니다.

 

Step1. github 가입

https://github.com

 

GitHub: Where the world builds software

GitHub is where over 73 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, review code like a pro, track bugs and feat...

github.com

Github에 가입을 해 주세요.

구글 아이디가 있으면 회원가입을 간편하게 할 수 있습니다.

Step2. repository 생성하기

로그인 후 우측 상단의 Your repositories를 클릭 해 주세요.

그 후 new를 눌러 repository를 생성해 줍니다. repository는 저장소인데요, 간단하게 폴더라고 생각하면 됩니다.

new를 클릭하면 새로운 창이 뜨는데, 거기서 저장소 이름 설정 후 Create repository를 클릭하면 만들어집니다.

Step3. html 파일 추가하기

그 후 Add file > Upload files 를 클릭하여 필요한 html파일을 업로드 해 줍니다.

파일을 드래그하여 업로드 또는 선택하여 업로드 후 Commit changes를 클릭하면 업로드가 완료됩니다.

Step4. html 배포하기

이제 Settings > Pages 를 클릭한 후 

Source설정에서 main으로 설정 후 Save를 누르면 page가 만들어집니다.

위처럼 만들어진 페이지 뒤에 해당 html 파일 이름을 넣어 주면,

예) https://elenalim.github.io/Visualizations/YONGSAN.html

이렇게 html 파일이 페이지로 배포됩니다.

이제 이 링크를 가지고 티스토리 블로그에 넣어 보도록 합시다.

Step5. 티스토리 블로그에 html 페이지 삽입하기

이제 마지막 단계입니다! 

github에서 배포한 페이지를 블로그에 나타내어서 사람들이 볼 수 있도록 해 봅시다.

티스토리 블로그에 로그인 후, '글쓰기'를 눌러 제목을 적어 줍니다.

그 후, 좌측상단의 기본모드를 클릭하여 HTML로 바꾸어 줍니다.

그러면 위와 같이 정체모를 영어들과 중간중간 한글이 보일 것입니다.

여기에서 내가 원하는 위치에 iframe으로 github html 페이지를 넣어 줍니다.

아래와 같이 입력하면 됩니다.

<iframe src="html페이지 주소" width="가로 사이즈(숫자)" height="세로 사이즈(숫자)"></iframe>

예) <iframe src="https://elenalim.github.io/Visualizations/YONGSAN.html" width="1000" height="500"></iframe>

이런 식으로 <p>태그 안에 <iframe>태그를 넣어 준 후,

다시 기본모드로 변경해 줍니다.

그러면 아래와 같이 html페이지가 iframe안에 잘 들어가 있음을 눈으로 쉽게 확인할 수 있습니다 :)

그 후 저장을 눌러 글을 업로드 하면 됩니다ㅎㅎ

 

마무리

처음에는 어려워 보일 수 있지만, 하다보면 생각보다는 쉽고 간단한 방법입니다 ㅎㅎ

html을 몰라도 <iframe>한줄만 추가해 주면 되기 때문에 초보자도 할 수 있습니다 :)

이제 여러분들은 html 파일을 보여주려고 사진을 캡처하거나 하지 않고

정성스럽게 만든 html 파일을그대로 보여 줄 수 있을 뿐만 아니라

세련된 블로그 게시물을 다른 사람에게 보여줄 수 있습니다.

추가적으로 다른 방법을 알고있거나 이번 게시물과 관련한 문의가 있으면 댓글 남겨주세요 :)

반응형
반응형

안녕하세요!
크롤링 포스팅을 오랜만에 진행하네요~
이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다.
지난 크롤러에서 아쉬웠던점은
언론마다 홈페이지 html 구조가 달라 내용을 제대로 가져오기 어려웠는데요
이번에는 이러한 문제점을 극복하고자
검색 결과로 나오는 기사 중 '네이버 뉴스(news.naver.com)'에 해당 기사가 존재하는 url만 가져와서
내용도 깔끔하게 크롤링할 수 있도록 만들어 보았습니다.

그럼 시작하겠습니다!

1. 필요한 라이브러리들 불러오기
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager

#웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

2. 크롤링시 필요한 함수 만들기
# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)


# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search,start_pg,end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ",url)
        return url
    else:
        urls= []
        for i in range(start_pg,end_pg+1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ",urls)
        return urls

3. 검색어를 받아서 네이버 검색 결과 페이지 url생성하기
##########뉴스크롤링 시작###################

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")

#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   

# naver url 생성
search_urls = makeUrl(search,page,page2)

여기까지는 그전 크롤러와 같습니다.
이제 본격적으로 네이버 뉴스의 기사들만 가져와 보겠습니다.

4. Selenium 사용하여 네이버 기사 url 추출하기

그 전에는 네이버 검색 결과 페이지 html에 필요한 url, 기사제목 등이 다 나와있어 바로 가져올 수 있었는데요,
네이버 뉴스만 가져오기 위해서는 다른 방법으로 접근해야 합니다.

예를 들어, '코로나' 관련 하여 검색을 진행했을 때, 하이라이트가 있는 '네이버 뉴스'를 클릭해야
아래처럼 네이버 뉴스 페이지의 해당 기사로 접속할 수 있습니다.

그래서 해당 url을 가져오기 위해
개발자 도구를 사용해 (F12)
검색 결과 페이지에 있는 네이버 뉴스 링크를 바로 가져와 파싱하려고

위의 사진 url인
('/p/crd/rd?m=1&px=405&py=267&sx=405&sy=167&p=hmkWFlprvTVssTYb7QKssssstb0-460233&q=%EC%BD%94%EB%A1%9C%EB%82%98&ie=utf8&rev=1&ssc=tab.news.all&f=news&w=news&s=rswJojqRF8DwzkNAwDOPrg%3D%3D&time=1645669880103&abt=%5B%7B%22eid%22%3A%2210%22%2C%22vid%22%3A%2218%22%7D%5D&a=nws*h.nav&r=1&i=88000127_000000000000000011024241&u=https%3A%2F%2Fnews.naver.com%2Fmain%2Fread.naver%3Fmode%3DLSD%26mid%3Dsec%26sid1%3D102%26oid%3D003%26aid%3D0011024241')
위 링크를 가져와 바로 주소창에 넣어보았으나....

제대로 열 수 없었습니다.
그 이유는

onclick, 즉 클릭을 하여야 링크가 올바른 링크로 바뀌어서 불러오게 되는 구조이기 때문입니다.
그래서 셀레니움을 사용하여 클릭 동작을 주고 해당 기사의 올바른 url 을 가져오는 방법을 선택하였습니다.
그래서 구글 웹드라이버도 크롬 버전에 맞게 설치하여 준 후,
웹드라이버로 해당 요소들을 가져와 클릭동작을 넣고 창 전환 후 현재 url을 가져왔습니다.
그리고 네이버 기사들만 naver_url 리스트에 추가해 주었습니다.

## selenium으로 navernews만 뽑아오기##
# 버전에 상관 없이 os에 설치된 크롬 브라우저 사용
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)


# selenium으로 검색 페이지 불러오기 #

naver_urls=[]

for i in search_urls:
    driver.get(i)
    time.sleep(1) #대기시간 변경 가능

    # 네이버 기사 눌러서 제목 및 본문 가져오기#
    # 네이버 기사가 있는 기사 css selector 모아오기
    a = driver.find_elements(By.CSS_SELECTOR,'a.info')

    # 위에서 생성한 css selector list 하나씩 클릭하여 본문 url얻기
    for i in a:
        i.click()

        # 현재탭에 접근
        driver.switch_to.window(driver.window_handles[1])
        time.sleep(3) #대기시간 변경 가능

        # 네이버 뉴스 url만 가져오기

        url = driver.current_url
        print(url)

        if "news.naver.com" in url:
            naver_urls.append(url)
  
        else:
            pass
        
        # 현재 탭 닫기
        driver.close()

        # 다시처음 탭으로 돌아가기(매우 중요!!!)
        driver.switch_to_window(driver.window_handles[0])

print(naver_urls)

selenium을 사용할 때는 동작 후 로딩 시간을 고려해 time.sleep()을 넣어 주는 것이 좋습니다.
(때문에 이 부분에서 크롤링 할 내용이 많으면 많을수록 시간이 걸립니다.)
여차저차 해서 드디어 우리에게 필요한 네이버 뉴스 기사들을 얻을 수 있었습니다!

5. 기사 제목 및 본문 크롤링하기

그 다음에는 네이버뉴스의 html구조만 파악하여 제목과 본문을 가져오면 됩니다.
네이버뉴스의 경우, 기사 제목은

div#ct > div.media_end_head.go_trans > div.media_end_head_title > h2

여기에 들어가 있고,
기사 내용은

div#dic_area

이곳에 들어가 있습니다.
위의 경로를 확인하려면 웹페이지에서 F12를 누른 후, 해당 요소를 클릭 후 우클릭 > Copy > Copy selector로 보다 편하게 확인할 수 있습니다.
그리고 내용을 가져올때, html태그도 다 가져오기 때문에
html 태그를 정규식 패턴을 이용하여 제거 후 각각 저장해 주도록 하겠습니다.

###naver 기사 본문 및 제목 가져오기###

# ConnectionError방지
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102" }

titles = []
contents=[]
for i in naver_urls:
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과확인시
    #print(html)
    
    #뉴스 제목 가져오기
    title = html.select("div.content > div.article_header > div.article_info > h3")
    # list합치기
    title = ''.join(str(title))
    # html태그제거
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1,repl='',string=title)
    titles.append(title)

    #뉴스 본문 가져오기

    content = html.select("div.content > div#articleBody > div#articleBodyContents")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))
    
    #html태그제거 및 텍스트 다듬기
    content = re.sub(pattern=pattern1,repl='',string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2,'')

    contents.append(content)

print(titles)
print(contents)

확실히 전과 비교하여 깔끔하게 출력이 됩니다.
나머지 필요한 전처리들은 분석시 진행하면 됩니다.

6. DataFrame으로 만들기

그 후 결과물을 데이터프레임으로 정리하여 보기 좋게 해 줍니다.

#데이터프레임으로 정리(titles,url,contents)
import pandas as pd

news_df = pd.DataFrame({'title':titles,'link':naver_urls,'content':contents})

news_df.to_csv('NaverNews_%s.csv'%search,index=False,encoding='utf-8-sig')

보통 데이터 프레임을 csv 파일로 저장할 때,
encoding 설정을 따로 해 주지 않으면 기본으로 utf-8로 저장이 되는데
이 파일을 그냥 열면 한글이 다 깨져 보입니다.
그래서 이를 방지하기 위해 encoding='utf-8-sig'로 설정을 해 줍니다.
그러면

이렇게 csv파일을 열어도 한글이 깨지지 않습니다 ㅎㅎ
전체 내용을 보고 싶으시다면 아래 파일을 확인해 주세요:)
저는 코로나 검색어로 2페이지만 크롤링 하였습니다.

NaverNews_코로나.csv
0.05MB
전체 코드
# 크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager


# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)


# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
# 입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num + 1
    else:
        return num + 9 * (num - 1)


# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(
            start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls


##########뉴스크롤링 시작###################

# 검색어 입력
search = input("검색할 키워드를 입력해주세요:")

# 검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):"))  # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ", page, "페이지")
# 검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):"))  # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ", page2, "페이지")

# naver url 생성
search_urls = makeUrl(search, page, page2)

## selenium으로 navernews만 뽑아오기##

# 버전에 상관 없이 os에 설치된 크롬 브라우저 사용
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)

# selenium으로 검색 페이지 불러오기 #

naver_urls = []

for i in search_urls:
    driver.get(i)
    time.sleep(1)  # 대기시간 변경 가능

    # 네이버 기사 눌러서 제목 및 본문 가져오기#
    # 네이버 기사가 있는 기사 css selector 모아오기
    a = driver.find_elements(By.CSS_SELECTOR, 'a.info')

    # 위에서 생성한 css selector list 하나씩 클릭하여 본문 url얻기
    for i in a:
        i.click()

        # 현재탭에 접근
        driver.switch_to.window(driver.window_handles[1])
        time.sleep(3)  # 대기시간 변경 가능

        # 네이버 뉴스 url만 가져오기

        url = driver.current_url
        print(url)

        if "news.naver.com" in url:
            naver_urls.append(url)

        else:
            pass
        # 현재 탭 닫기
        driver.close()
        # 다시처음 탭으로 돌아가기(매우 중요!!!)
        driver.switch_to.window(driver.window_handles[0])

print(naver_urls)

###naver 기사 본문 및 제목 가져오기###

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

titles = []
contents = []
for i in naver_urls:
    original_html = requests.get(i, headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과확인시
    # print(html)

    # 뉴스 제목 가져오기
    title = html.select("div#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    # list합치기
    title = ''.join(str(title))
    # html태그제거
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=title)
    titles.append(title)

    # 뉴스 본문 가져오기

    content = html.select("div#dic_area")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    contents.append(content)

print(titles)
print(contents)

# 데이터프레임으로 정리(titles,url,contents)
import pandas as pd

news_df = pd.DataFrame({'title': titles, 'link': naver_urls, 'content': contents})

news_df.to_csv('NaverNews_%s.csv' % search, index=False, encoding='utf-8-sig')

코드 파일
naver_news_0517.py
0.00MB
마무리

이번 포스팅도 금방 작성할 거라 생각했으나...
항상 예상을 벗어나는 법이죠 ㅠㅠ 네.. 오래걸렸어요...
그래도 저번보다 훨씬 좋은 크롤러로 업그레드한 것 같아 좋네요ㅎㅎ
앞으로도 지속적으로 리뷰 및 업그레이드 하겠습니다.
좋은 아이디어나 질문이 있다면 댓글 남겨주세요:)
------------------------------------------------------------------------------------
+04/05 셀레니움 오류 수정하였습니다.
+04/15 driver 오류 수정 완료.
+05/03 bs4 select 수정 완료.
+05/17 driver 및 창닫기 추가.
+ 07/14 최신버전을 확인하고 싶다면...?
https://wonhwa.tistory.com/m/52

[python] 네이버 뉴스 크롤러(날짜,링크,제목, 본문)

안녕하세요. 정말 오랜만의 포스팅입니다. 그동안 올렸던 네이버 뉴스 크롤러 최신 버전을 공유드리고자 합니다. 그 전 버전인 아래 포스팅에서는 https://wonhwa.tistory.com/46#comment18426434 [python] 원하

wonhwa.tistory.com

반응형

+ Recent posts